Refresh WPF DataGrid ItemSource from another thread in PowerShell
I have a datagrid that is updated when a button is clicked. However, in some cases, the data returns 30 seconds + and the window freezes. I want to be able to receive data and populate the datagrid from another thread, so as not to hang the main window. The DataGrid is read-only. After all, you want the undo button and animation to indicate progress, but for now, we just want it to work.
I made a sample program that can demonstrate the problem, which was based on http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a- different-runspace /
I've already tried using Work-Job / Receive Job and .NET Background worker with no success. The script will be used in PowerShell v4 on Server 2012 R2 and PowerShell v5 on Windows 10.
$syncHash = [hashtable]::Synchronized(@{})
$syncHash.AutoResetEvent = New-Object System.Threading.AutoResetEvent($false)
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$psCmd = [PowerShell]::Create().AddScript({
[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:local="clr-namespace:DataTool"
Name="mainWindow"
Title="Data Tool" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Grid Margin="10" Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid Name="myDataGrid" SelectionUnit="FullRow" IsReadOnly="True" Margin="5" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" AutoGenerateColumns="True" Height="200" MaxWidth="900">
</DataGrid>
<Button Name="buttonRefresh" Margin="10" Padding="20,5,20,5" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch">Refresh</Button>
</Grid>
</Window>
"@
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )
$xaml.SelectNodes("//*[@Name]") | %{
$syncHash[$_.Name] = $syncHash.Window.FindName($_.Name)
}
$syncHash.AutoResetEvent.Set()
$syncHash.Window.ShowDialog() | Out-Null
$syncHash.Error = $Error
})
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
$syncHash.AutoResetEvent.WaitOne()
$syncHash.buttonRefresh.add_Click({
Write-Host "Click Triggered!"
$syncHash.mainWindow.Dispatcher.Invoke([Action] {$syncHash.myDataGrid.ItemsSource = Get-Process },"Normal")
Write-Host "DataGrid Updated!"
})
Note:
$syncHash.mainWindow.Dispatcher.Invoke([Action] {$syncHash.myDataGrid.ItemsSource = Get-Process },"Normal")
Works great on its own, just not when triggered from a click event.
source to share
Stay open for better solutions, it works, but I suspect it is by design. I fixed this by creating a space in the click event. This loads an animated gif c: \ scripts \ throbber.gif to confirm that the window is not hanging. Start-Sleep was used to simulate a longer time to return data.
$syncHash = [hashtable]::Synchronized(@{})
$syncHash.AutoResetEvent = New-Object System.Threading.AutoResetEvent($false)
$syncHash.AutoResetEventClick = New-Object System.Threading.AutoResetEvent($false)
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$psCmd = [PowerShell]::Create().AddScript({
[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:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:winForms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:local="clr-namespace:DataTool"
Name="mainWindow"
Title="Data Tool" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Grid Margin="10" Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid Name="myDataGrid" SelectionUnit="FullRow" IsReadOnly="True" Margin="5" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" AutoGenerateColumns="True" Height="200" MaxWidth="900">
</DataGrid>
<Button Name="buttonRefresh" Margin="10" Padding="20,5,20,5" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch">Refresh</Button>
<wfi:WindowsFormsHost Name="wfiThrobber" Grid.Row="3" Grid.Column="0" Visibility="Visible" VerticalAlignment="Center" HorizontalAlignment="Center" >
<winForms:PictureBox Name="imgThrobber">
</winForms:PictureBox>
</wfi:WindowsFormsHost>
</Grid>
</Window>
"@
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )
$xaml.SelectNodes("//*[@Name]") | %{
$syncHash[$_.Name] = $syncHash.Window.FindName($_.Name)
}
$syncHash.imgThrobber = $syncHash.wfiThrobber.Child[0]
$syncHash.imgThrobber.Image = [System.Drawing.Image]::FromFile("c:\scripts\throbber.gif");
$syncHash.AutoResetEvent.Set()
$syncHash.Window.ShowDialog() | Out-Null
$syncHash.Error = $Error
})
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
$syncHash.AutoResetEvent.WaitOne()
$syncHash.buttonRefresh.add_Click({
$clickRunspace =[runspacefactory]::CreateRunspace()
$clickRunspace.ApartmentState = "STA"
$clickRunspace.ThreadOptions = "ReuseThread"
$clickRunspace.Open()
$clickRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$psClickCmd = [PowerShell]::Create().AddScript({
Start-Sleep 15
$items = Get-Process
$syncHash.Window.Dispatcher.Invoke([Action]{ $syncHash.myDataGrid.ItemsSource = $items })
})
$psClickCmd.Runspace = $clickRunSpace
$psClickCmd.BeginInvoke()
})
source to share