0
votes

I am limiting drag and drop actions to a WPF listbox control in Powershell to only allow text files to be dropped. I would like to use the System.Windows.DragDropEffects property to prevent the drop action on the DragEnter event as it also changes the mouse cursor providing user feedback for the denied drop action. I can still limit the action taken on the dropped file by validating the file extension on the Drop event. But I would prefer to prevent the drop action all together for smoother user interaction.

In debugging I've verified that the DragDropEffect property is being set correctly, however the event handler does not seem to reflect the change. I believe it might be a limitation trying to use the DragEventArgs Class to monitor the event through the Powershell pipeline.

Code for the WPF listbox DragEnter event is below. I noticed that the object passed in the $_ pipeline is of the System.Windows.DragEventArgs class.

$listbox.Add_DragEnter({
if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
    foreach ($filename in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
        if(([System.IO.Path]::GetExtension($filename).ToUpper() -eq ".TXT")) {
            $_.Effects = [System.Windows.DragDropEffects]::All
            Write-Host 'Dropfile is a .TXT'
        }
        else {
            $_.Effects = [System.Windows.DragDropEffects]::None
            Write-Host 'Dropfile is NOT a .TXT'
        }
    }
}
})

Setting DragDropEffect property using WinForms listbox works as expected. The mouse changes and the drop event is prevented. However here, the object passed in the $_ pipeline is of the System.Windows.Forms.DragEventArgs class.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object Windows.Forms.Form
$listbox = New-Object Windows.Forms.ListBox
$listbox.AllowDrop = $true
$listbox.Add_DragEnter({
$_.Effect = [Windows.Forms.DragDropEffects]::None
})

$form.Controls.Add($listbox)
$form.ShowDialog()

Full test code below for WPF:

[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

[xml]$xaml = @'
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Remote Execution Toolkit" Height="300" Width="300">
<Grid>
    <ListBox x:Name="listBox" AllowDrop="True" Height="250" HorizontalAlignment="Center" Width="250">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}">
                    <TextBlock Text="{Binding}" TextAlignment="Left" Width="Auto" />
                </CheckBox>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>
</Window>
'@

# Load XAML Reader
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

# Map XAML Controls
$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]")  | ForEach {
New-Variable  -Name $_.Name -Value $Window.FindName($_.Name) -Force
}

# Drag Event to validate file extensions for drop effect
$listbox.Add_DragEnter({
if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
    foreach ($filename in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
        if(([System.IO.Path]::GetExtension($filename).ToUpper() -eq ".TXT")) {
            $_.Effects = [System.Windows.DragDropEffects]::All
            Write-Host 'Dropfile is a .TXT'
        }
        else {
            $_.Effects = [System.Windows.DragDropEffects]::None
            Write-Host 'Dropfile is NOT a .TXT'
        }
    }
}
})

$Window.ShowDialog()

Any thoughts or suggestions is appreciated!!

2

2 Answers

0
votes

After tons of Google-Foo, running Snoop WPF to monitor events, and trial and error, I realized that I was simply subscribing to the wrong Drag Event. To achieve the result of continuously displaying the operation not allowed cursor, you must use the DragOver Event.

$listbox.Add_DragOver({
    ...
    $_.Effects = [System.Windows.DragDropEffects]::None
    ...
})

Apparently, when using WPF code in Powershell, the DragEnter event only fires once allowing the cursor to change back, whereas the DragOver event continuously fires while the mouse is over the control maintaining display of the operation not allowed cursor.

Hope this is able to save another fellow developer down the road save some time.

0
votes

This worked for me by adding $_.Handled = $true after changing the $_.Effects.

This was taken from here: https://stackoverflow.com/a/44321363/8262102

# Drag and drop UI example to a list box.
Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration

[xml]$xaml = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Drop Form" Height="300" Width="500">
<Grid>
<ListBox x:Name="listBox" AllowDrop="True" Height="250" HorizontalAlignment="Center" Width="475">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}">
<TextBlock Text="{Binding}" TextAlignment="Left" Width="Auto" />
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
'@

# Load XAML Reader
$reader = New-Object System.Xml.XmlNodeReader $xaml
$Form   = [Windows.Markup.XamlReader]::Load($reader)

# Map XAML Controls
$formNamedNodes = $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | Sort
$formNamedNodes | ForEach-Object {
  Set-Variable -Name $_.Name -Value $Form.FindName($_.Name) # Set the variable names to the same as that of the controls.
}

# Drop event to add the files to the list box.
$listbox.Add_Drop({

  if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
    foreach ($filename in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
      if (([System.IO.Path]::GetExtension($filename).ToUpper() -eq ".TXT")) {
        Write-Host "Dropped file extension: $filename is .TXT"
        $listBox.Items.Add($filename)
      }
      else {
        Write-Host "Dropped file extension: $filename is NOT .TXT"
      }
    }
  }

  })

# The DragOver event is there to handle changing the dropped effects.
$listbox.Add_DragOver({

  if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
    foreach ($filename in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
      if (([System.IO.Path]::GetExtension($filename).ToUpper() -eq ".TXT")) {
        $_.Effects = [System.Windows.DragDropEffects]::All
        Write-Host "$filename is a .TXT"
      }
      else {
        $_.Effects = [System.Windows.DragDropEffects]::None
        Write-Host "$filename is NOT a .TXT"
      }
      $_.Handled = $true # This is there to handle the effect. This needs to be below $_.Effect.
    }
  }

  })

$Form.WindowStartupLocation = "CenterScreen"
$Form.ShowDialog()