Powershell WPF ComboBox to update ListBox - wpf

I have a ComboBox that doesn't quite work.
It has 3 static selections for essentially sorting a long list of connection profiles plus a 'Select One' which is automatically selected.
No matter what selection I choose, nothing happens until I click on another selection. Now this is the interesting part. No matter what I choose next, the listbox populates with my previous selection.
<ComboBox Name="CBox" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" >
<ComboBoxItem Name="cbSelect" IsSelected="True">Select Profile</ComboBoxItem>
<ComboBoxItem Name="cbRemote">Remote</ComboBoxItem>
<ComboBoxItem Name="cbLab">Lab</ComboBoxItem>
<ComboBoxItem Name="cbTomcats">Tomcats</ComboBoxItem>
</ComboBox>
<ListBox Name="ListBox" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" />
$syncHash.CBox.Add_SelectionChanged({
If($syncHash.CBox.Text -eq "Select Profile")
{
$syncHash.ListBox.Items.Clear()
}
If($syncHash.CBox.Text -eq "Remote")
{
$syncHash.ListBox.Items.Clear()
$profiles = New-Object System.Collections.ArrayList
$profiles = (Get-ChildItem "C:\Profiles")
foreach ($profile in $profiles)
{
if ($profile -match 'Remote \-')
{
[void]$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
If($syncHash.CBox.Text -eq "Lab")
{
$syncHash.ListBox.Items.Clear()
$profiles = New-Object System.Collections.ArrayList
$profiles = (Get-ChildItem "C:\Profiles")
foreach ($profile in $profiles)
{
if ($profile -match 'Lab \-')
{
[void]$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
If($syncHash.CBox.SelectedItem -eq "Tomcats")
{
$syncHash.ListBox.Items.Clear()
$profiles = New-Object System.Collections.ArrayList
$profiles = (Get-ChildItem "C:\Profiles")
foreach ($profile in $profiles)
{
if ($profile -match 'Tomcats \-')
{
[void]$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
})
Final Solution using Rohin Sidharth's answer:
Add-Type -Path 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll'
$Global:syncHash = [hashtable]::Synchronized(#{})
[xml]$xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="ComboBox Test" Height="500" Width="350">
<Grid Width="340" Height="470">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Height="0.1*" />
<RowDefinition Height="0.9*" />
</Grid.RowDefinitions>
<ComboBox Name="CBox" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" >
<ComboBoxItem Name="cbSelect" IsSelected="True">Select Profile</ComboBoxItem>
<ComboBoxItem Name="cbRemote">Remote</ComboBoxItem>
<ComboBoxItem Name="cbLab">Lab</ComboBoxItem>
<ComboBoxItem Name="cbTomcats">Tomcats</ComboBoxItem>
</ComboBox>
<ListBox Name="ListBox" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
"#
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )
[xml]$XAML = $xaml
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | %{
$syncHash.Add($_.Name,$syncHash.Window.FindName($_.Name) )
}
function Get-Profiles
{
$profiles = (Get-ChildItem "C:\Profiles")
$syncHash.ListBox.Items.Clear()
If ($syncHash.CBox.SelectedIndex -eq 1)
{
foreach ($profile in $profiles)
{
if ($profile -match 'Remote \-')
{
$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
ElseIf ($syncHash.CBox.SelectedIndex -eq 2)
{
foreach ($profile in $profiles)
{
if ($profile -match 'Lab \-')
{
$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
ElseIf ($syncHash.CBox.SelectedIndex -eq 3)
{
foreach ($profile in $profiles)
{
if ($profile -match 'Tomcats \-')
{
$syncHash.ListBox.Items.Add($profile.BaseName)
}
}
}
}
$syncHash.CBox.Add_SelectionChanged({Get-Profiles})
$null = $syncHash.Window.ShowDialog()
$syncHash.Error = $Error
$Error

I haven't quite worked with WPF in powershell but in Winforms, I think to select an item on the combobox, you have to set the SelectedIndex property and not the text of the item. An index of 0 will select the 1st item on your dropdownlist

Related

Get clicked MenuItem header value from datagrid context menu in WPF/PowerShell

In a WPF DataGrid via PowerShell, I have added a context menu and MenuItems from an Array. I would like to get the header value of clicked MenuItem to handle the click event further. I had tried add_Click event to the MenuItem object but it does return any value. I'm looking for some ideas to get the Header value in this scenario (or) if there is any different better approach to achieve this goal. Thanks in advance.
[xml]$inputXML=#"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="275" Width="375">
<Window.InputBindings>
<KeyBinding x:Name="keyTest" Modifiers="Control" Key="N" Command="{Binding CreateCustomerCommand}" />
</Window.InputBindings>
<Grid>
<DataGrid x:Name="dg" Margin="5,5,0,0" Height="250" Width="350" ColumnWidth="Auto" AlternationCount="1" IsReadOnly="True" SelectionMode="Extended" SelectionUnit="Cell" Background="White" ClipboardCopyMode="IncludeHeader" >
<DataGrid.ContextMenu >
<ContextMenu x:Name="cxmenu" />
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Window>
"#
[xml]$XAML = $inputXML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load($reader)
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'x:Name')]]") | %{Set-Variable -Name "$($_.Name)" -Value $Window.FindName($_.Name)}
#sample data
$DataSet = New-Object System.Data.DataSet
$Table = $DataSet.Tables.Add("Table")
$Properties = #("Country","Capital","Population")
$Properties | foreach {
$Column = New-Object System.Data.DataColumn($_)
$Table.Columns.Add($Column)
}
$Null=$Table.Rows.Add("USA","Washington, D.C","658,893")
$Null=$Table.Rows.Add("China PR","Beijing","20,693,000")
$Null=$Table.Rows.Add("India","New Delhi","16,787,949")
$Null=$Table.Rows.Add("Japan","Tokyo","13,189,000")
$Null=$Table.Rows.Add("Philippines","Manila","12,877,253")
$Null=$Table.Rows.Add("Russia","Moscow","11,541,000")
$Null=$Table.Rows.Add("Egypt","Cairo","10,230,350")
#populate datagrid
$DataView = New-Object System.Data.DataView($Table)
$array = New-Object System.Collections.ArrayList
[void] $array.AddRange($DataView)
$dg.clear()
$dg.ItemsSource = $array
$dg.IsReadOnly = $true
#add MenuItem to context menu
$cxMenuitem = New-Object Windows.Controls.MenuItem
$header = ('Test1','Test2','Test3')
for ($i = 0; $i -le $header.Count -1; $i++)
{
$cxMenuitem.Header = [string]$header[$i]
$cxMenu.Items.Add($cxMenuitem.Header)
}
$cxMenuitem.Add_Click({
#Get the Header of the clicked MenuItem from Context Manu here for further handling
[System.Windows.MessageBox]::Show($cxMenu.Items)
})
#Display Form
$Window.ShowDialog() | Out-Null
You should be able to cast $args[0] to a MenuItem and access its Header property directly:
$cxMenuitem.Add_Click({
$sender = [System.Windows.Controls.MenuItem]$args[0]
# Use $sender.Header ...
})

How do you display options from an array in PowerShell when creating a ComboBox in XAML

I am creating a wpf ui in powershell using xaml. I am attempting to fill my combo box with an array and whenever I load the window/combobox, no options appear for me to select from. I have tried a few methods to resolve this, including using the ItemsSource element in xaml scriptblock, using DisplayMemberPath inside and outside the xaml block, creating a function to help display the window and then passing the variables from inside the window to the scriptblock.
Please let me know what I am doing wrong - any and all help is appreciated.
Do {
Connect-AzAccount -ErrorAction SilentlyContinue
$AzureAccount = Get-AzContext
$CurrentAccount = $AzureAccount.Account.Id
$CurrentAccount
} While ([String]$AzureAccount::isnullorempty)
Do {
# Select Subscription
Try {
$azureSubscription = (Get-AzSubscription | Sort-Object Name | Out-GridView -Title "Choose your Azure subscription and click OK." -PassThru)
Write-host "Switching to Azure subscription: $($azureSubscription.Name)" -ForegroundColor Green;
$azureSubscriptionInfo = Select-AzSubscription -SubscriptionId $azureSubscription.Id
} Catch {
Write-Output "Script Terminated"
}
# Select Location
Try {
$azureLocation = (Get-AzLocation | Sort-Object Name | Out-GridView -Title "Chosose your Azure location and click OK." -PassThru)
Write-Host "Switching to Azure location: $($azureLocation.DisplayName)" -ForegroundColor Green
} Catch {
Write-Output "Script Terminated"
}
if ([String]$azureSubscription::isnullorempty) {
$Primary = "False"
} elseif ([String]$azureLocation::isnullorempty) {
$Primary = "False"
}
} while ($Primary -eq "False")
# Get Virtual Networks and associated Subnets
$vnet = Get-AzVirtualNetwork
$vnetList = #()
$subnetsList = #()
foreach ($vn in $vnet) {
if ($vnet.Location -eq $azureLocation.Location) {
$vnetList += $vnet.Name
$subnetsList += $vnet.Subnets.Name }
}
$vnetList = $vnetList | Where-Object { $_ } | Select-Object -Unique
$subnetsList = $subnetsList | Where-Object { $_ } | Select-Object -Unique
Add-Type -AssemblyName PresentationFramework
[xml]$xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window" Title="Virtual Machine Deployment Tool" WindowStartupLocation="CenterScreen"
SizeToContent="WidthAndHeight" >
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="10">
<ComboBox x:Name="vnselect" Margin="5" Width="100" />
</StackPanel>
</Grid>
</Window>
"#
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
$window.ShowDialog() | Out-Null
$vnselect = $window.FindName("vnselect")
foreach ($item in $vnetList) {
$vnselect.Items.Add($item)
}
The code that added the items to the ComboBox should be placed before $window.Showdialog(). That's the resolution.

WPF XAML form generated on the fly - how to reference xaml elements later

I have some powershell code where i am leveraging a xaml template to generate a grid of servers. Since the number of servers and names will change (fed from a spreadsheet) i am not able to hardcode the names of the XAML elements.
I am able to generate the grid (server items grouped by types of servers) successfully without any issues. my issue now is how to reference the XAML items generated 'on-the-fly' later. I feel like this is a relatively easy question based on the complexity of what i am already doing, but i am new to working with XAML, especially generating a XAML form on the fly.
Here is the XAML template and the two functions that generate the xaml items on the fly.
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title='MainWindow' Height='Auto' Width='Auto' FontSize='9'>
<Window.Resources>
<Style x:Key="GroupBoxStyle1" TargetType="{x:Type GroupBox}">
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.ColumnSpan="2"
BorderThickness="1"
BorderBrush="#25A0DA"
Background="#25A0DA">
<Label Foreground="White">
<ContentPresenter Margin="4"
ContentSource="Header"
RecognizesAccessKey="True" />
</Label>
</Border>
<Border Grid.Row="1"
BorderThickness="1,0,1,1"
BorderBrush="#25A0DA">
<ContentPresenter Margin="4" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility ='Auto' ScrollViewer.HorizontalScrollBarVisibility ='Auto'>
<Grid ShowGridLines="False" Name='StartingGrid'>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button
Name='Refresh_Btn'
Content='Refresh'
Width='70'
Height='30'
FontSize="9"
FontWeight="Bold"
Grid.Column="0"
Grid.Row="0"/>
<Button
Name='Reboot_Btn'
Content='Reboot'
Width='70'
Height='30'
FontSize="9"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="0"/>
</Grid>
</ScrollViewer>
</Window>
'#
Function CreateServerGrid{
$srv = $Srv.name
$grd = New-Object system.windows.controls.grid
$grd.Name = "Server" + ($counter)
#$grd.ShowGridLines = 'True'
$row1 = new-object system.windows.controls.rowdefinition
$row1.height = "Auto"
$row2 = new-object system.windows.controls.rowdefinition
$row2.height = "Auto"
$row3 = new-object system.windows.controls.rowdefinition
$row3.height = "Auto"
$col1 = new-object system.windows.controls.columndefinition
$col1.width = "Auto"
$col2 = new-object system.windows.controls.columndefinition
$col2.width = "Auto"
$col3 = new-object system.windows.controls.columndefinition
$col3.width = "Auto"
$grd.RowDefinitions.add($row1)
$grd.RowDefinitions.add($row2)
$grd.RowDefinitions.add($row3)
$grd.ColumnDefinitions.add($col1)
$grd.ColumnDefinitions.add($col2)
$grd.ColumnDefinitions.add($col3)
$ServerLabel = New-Object System.Windows.Controls.Label
$ServerLabel.Name = "$srv"
$ServerLabel.FontWeight = "UltraBold"
$ServerLabel.Content = $srv
[System.Windows.Controls.Grid]::SetRow($ServerLabel,0)
[System.Windows.Controls.Grid]::SetColumn($ServerLabel,0)
$ServerLabel.HorizontalAlignment = 'Right'
#RegisterName($srv.name,$ServerLabel)
#$GUIhash.Add($_.TargetType,$_)
$ItemLabel0 = New-Object System.Windows.Controls.Label
$ItemLabel0.Name = "Label_LastDeployment_$srv"
#$ItemLabel1.Height = "Auto"
$ItemLabel0.Content = "Last Deployment: "
[System.Windows.Controls.Grid]::SetRow($ItemLabel0,1)
[System.Windows.Controls.Grid]::SetColumn($ItemLabel0,1)
$ItemText0 = New-Object System.Windows.Controls.textBox
$ItemText0.Name = "LD_$srv"
#$ItemLabel1.Height = "Auto"
$ItemText0.text = "OtherData1 " + ($counter + 2 )
[System.Windows.Controls.Grid]::SetRow($ItemText0,1)
[System.Windows.Controls.Grid]::SetColumn($ItemText0,2)
$ItemLabel1 = New-Object System.Windows.Controls.Label
$ItemLabel1.Name = "Label_LastReboot_$srv"
#$ItemLabel1.Height = "Auto"
$ItemLabel1.Content = "Last Reboot: "
[System.Windows.Controls.Grid]::SetRow($ItemLabel1,2)
[System.Windows.Controls.Grid]::SetColumn($ItemLabel1,1)
$ItemText1 = New-Object System.Windows.Controls.textBox
$ItemText1.Name = "LR_$srv"
#$ItemLabel1.Height = "Auto"
$ItemText1.text = "OtherData1 " + ($counter + 2 )
[System.Windows.Controls.Grid]::SetRow($ItemText1,2)
[System.Windows.Controls.Grid]::SetColumn($ItemText1,2)
$grd.AddChild($ServerLabel)
$grd.AddChild($ItemLabel0)
$grd.AddChild($ItemText0)
$grd.AddChild($ItemLabel1)
$grd.AddChild($ItemText1)
return $grd
}
Function StartTheFun {
$script:counter = 1
foreach ($grp in $grouping){
$WPFStartingGrid.RowDefinitions.add((new-object System.Windows.Controls.RowDefinition -property #{ Height = 'Auto'}))
If($Counter -eq 3){
$script:cnt ++
$WPFStartingGrid.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$script:counter = 1
}
$grid =#()
$newStkPnl = new-object System.Windows.Controls.Grid
$NewGB = New-Object System.Windows.Controls.GroupBox
$NewGB.Header = $grp
$NewGB.Margin = "0"
$init = 0
$ct = 0
$srvCnt = ($list | ?{$_.'Computer_Description' -match $grp}).count
#Write-Host $grp
#Write-Host "ServerCount: " $srvCnt
$CntCheck = $false
if ($srvCnt -gt 4){$CntCheck = $true}
#Write-Host "CntCheck: " $CntCheck
foreach ($srv in $list | ?{$_.'Computer_Description' -match $grp}) {
Write-Host 'Server: ' $srv.name
Write-Host 'Init: ' $init
$newStkPnl.RowDefinitions.add((new-object System.Windows.Controls.RowDefinition -property #{ Height = 'Auto'}))
#$ControlObj.Add($srv.Name,$_)
$grid = CreateServerGrid $srv
[System.Windows.Controls.Grid]::SetRow($grid,$init)
[System.Windows.Controls.Grid]::SetColumn($grid,$ct)
$newStkPnl.AddChild($grid)
$init ++
$srvCnt --
if($srvCnt -gt 0){
If ($init -gt 3){
$newStkPnl.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$ct ++
$init = 0
}
If (($srvCnt -gt 0) -and ($init -eq 0)){
$newStkPnl.ColumnDefinitions.add((new-object System.Windows.Controls.ColumnDefinition -property #{ Width = 'Auto'}))
$ct ++
$init = 0
}
}
Write-Host 'NewSrvCnt: ' $srvCnt
}
$NewGB.Content = $newStkPnl
$NewGB.Name = 'Group' + ($counter)
$NewGB.Style = $GuiHash.Window.FindResource("GroupBoxStyle1")
[System.Windows.Controls.Grid]::SetRow($NewGB,$counter)
[System.Windows.Controls.Grid]::SetColumn($NewGB,$cnt)
$Script:counter ++
$WPFStartingGrid.AddChild($NewGB)
}
}
Any ideas on how to best reference these elements? I am providing an image so people can see what it looks like when it runs.

Powershell script issue handling drag and drop effects for WPF listbox

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!!
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.
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()

Add "Enter" and "Escape" key presses to Powershell WPF form

I used ISE-Sterioids template to create a simple 3 field WPF form that asks for three things:
- ID
- Email
- Reference
I have it sucessfully working with these fields when using the OK and Cancel buttons, but I would like to capter "Enter" to submit the form and "Escape" to cancel the form, but I am having difficulty adding the events.
I have tried similar code to the technet article here; but as this isn't using WPF I think I am missing something
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objTextBox.Text;$objForm.Close()}})
My code here:
#region XAML window definition
# Right-click XAML and choose WPF/Edit... to edit WPF Design
# in your favorite WPF editing tool
# Default Form Values
$123 = 'ID'
$toEmail = 'email address'
$ref = "ref"
$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
MinWidth="200"
Width ="400"
SizeToContent="Height"
Title="Proofing script"
Topmost="True">
<Grid Margin="10,2,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- <TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="5">Please enter your details:</TextBlock> -->
<TextBlock Grid.Column="0" Grid.Row="1" Margin="5"><Run Text="Number:"/></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2" Margin="5"><Run Text="To Email :"/></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="3" Margin="5"><Run Text="Salesforce Ref:"/></TextBlock>
<TextBox x:Name="TxtName" Grid.Column="1" Grid.Row="1" Margin="5"/>
<TextBox x:Name="TxtEmail" Grid.Column="1" Grid.Row="2" Margin="5"/>
<TextBox x:Name="ref" Grid.Column="1" Grid.Row="3" Margin="5"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,5,0,0" Grid.Row="4" Grid.ColumnSpan="2">
<Button x:Name="ButOk" MinWidth="80" Height="22" Margin="5" Content="OK" />
<Button x:Name="ButCancel" MinWidth="80" Height="22" Margin="5" Content="Cancel" IsCancel="True" />
</StackPanel>
</Grid>
</Window>
'#
#endregion
#region Code Behind
function Convert-XAMLtoWindow
{
param
(
[Parameter(Mandatory)]
[string]
$XAML,
[string[]]
$NamedElement=$null,
[switch]
$PassThru
)
Add-Type -AssemblyName PresentationFramework
$reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
$result = [Windows.Markup.XAMLReader]::Load($reader)
foreach($Name in $NamedElement)
{
$result | Add-Member NoteProperty -Name $Name -Value $result.FindName($Name) -Force
}
if ($PassThru)
{
$result
}
else
{
$null = $window.Dispatcher.InvokeAsync{
$result = $window.ShowDialog()
Set-Variable -Name result -Value $result -Scope 1
}.Wait()
$result
}
}
function Show-WPFWindow
{
param
(
[Parameter(Mandatory)]
[Windows.Window]
$Window
)
$result = $null
$null = $window.Dispatcher.InvokeAsync{
$result = $window.ShowDialog()
Set-Variable -Name result -Value $result -Scope 1
}.Wait()
$result
}
#endregion Code Behind
#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml -NamedElement 'ButCancel', 'ButOk', 'ref', 'TxtEmail', 'TxtName' -PassThru
#endregion
#region Define Event Handlers
# Right-Click XAML Text and choose WPF/Attach Events to
# add more handlers
$window.ButCancel.add_Click(
{
$window.DialogResult = $false
}
)
$window.ButOk.add_Click(
{
$window.DialogResult = $true
}
)
#endregion Event Handlers
#region Manipulate Window Content
#$window.TxtName.Text = $env:username
$window.ref.Text = $ref
$window.TxtName.Text = $123
$window.TxtEmail.Text = $toEmail
$null = $window.TxtName.Focus()
#endregion
# Show Window
$result = Show-WPFWindow -Window $window
If i use ISE Steroids to add an event I get similar to this
$window.ButOk.add_KeyDown{
# remove param() block if access to event information is not required
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
# add event code here
}
Working with key down events can be a bit tricky since these are depending on the current UIFocus, therefor your button does not act on any keydown event since it is not in the current focus scope when you´re just editing the Textboxes. Since you only plan on using this simple form, I would suggest adding an event handler to your window that handles this. So your code behind section should look something like this:
$window.add_KeyDown{
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
if($e.Key == $Key.Return)
{
$window.DialogResult = $true
}
if($e.Key -eq $Key.Escape)
{
$window.DialogResult = $false
}
}
The accepted answer didn't work for me yet but it helped me a lot so I'm answering here.
I made a simple form with https://poshgui.com/Editor adding the code in the answer and got the error message:
System.Management.Automation.RuntimeException: Unable to find type [Windows.Input.KeyEventArgs].
My TextBox element has got the focus, so I created the event for it:
TextBoxSearchInput.Add_KeyDown{
param (
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][System.Windows.Forms.KeyEventArgs]$e
)
if($e.Key -eq $Key.Return){
$FormOkButton.PerformClick()
}
if($e.Key -eq $Key.Escape){
$Form.close()
}
}
There may be a better way than "PerformClick" but for now it works.
I know this thread here is old but I was also sucessfull with this:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Any Name"
$Form.Size = New-Object System.Drawing.Size(700,150) #use any size
$Form.StartPosition = "CenterScreen" # I prefer this
$Form.Topmost = $true
$form.KeyPreview = $true #This is the important part
$form.Add_KeyDown{
param (
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][System.Windows.Forms.KeyEventArgs]$e
)
if($_.KeyCode -eq "Escape"){
$Form.close()
}
}

Resources