从PowerShell中的另一个线程更新WPF DataGrid ItemSource [英] Update WPF DataGrid ItemSource from Another Thread in PowerShell
问题描述
我做了一个可以演示问题的示例程序,这是基于 http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/
我已经尝试使用Start-Job / Receive Job和.NET Background worker没有成功。
该脚本将在Windows 2012上的Server 2012 R2和PowerShell v5上的PowerShell v4上使用。
$ 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 ToolWindowStyle =ToolWindowSizeToContent =WidthAndHeightR esizeMode =CanMinimize>
< Grid Margin =10Name =mainGrid>
< Grid.RowDefinitions>
< RowDefinition Height =Auto>< / RowDefinition>
< RowDefinition Height =Auto>< / RowDefinition>
< /Grid.RowDefinitions>
< DataGrid Name =myDataGridSelectionUnit =FullRowIsReadOnly =TrueMargin =5Grid.Row =0Grid.Column =0Grid.ColumnSpan =5AutoGenerateColumns = TrueHeight =200MaxWidth =900>
< / DataGrid>
< Button Name =buttonRefreshMargin =10Padding =20,5,20,5Grid.Row =1Grid.Column =0HorizontalAlignment =Stretch> Refresh< ; / Button>
< / Grid>
< / Window>
@
$ reader =(New-Object System.Xml.XmlNodeReader $ xaml)
$ syncHash.Window = [Windows.Markup.XamlReader] :: Load($阅读器)
$ 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-HostClick Triggered!
$ syncHash.mainWindow.Dispatcher.Invoke([Action] {$ syncHash.myDataGrid $ item $ S $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
$ b 注意:
$ syncHash.mainWindow.Dispatcher.Invoke([Action] {$ syncHash .myDataGrid.ItemsSource = Get-Process},Normal)
独自工作正常,只是在点击事件触发时不会。
解决方案保持开放更好的解决方案,这个工作,但我怀疑是过度设计。我通过在点击事件中创建一个运行空间来修复这个问题。这将载入动画gif c:\scripts\throbber.gif以确认窗口未挂起。 Start-Sleep被用来模拟更长的时间来获取数据。
$ 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 。集成;组装= WindowsFormsIntegration
xmlns:winForms =clr-namespace:System.Windows.Forms; assembly = System.Windows.Forms
xmlns:local =clr-namespace:DataTool
Name =mainWindow
Title =Data ToolWindowStyle =ToolWindowSizeToContent =WidthAndHeightResizeMode =CanMinimize>
< Grid Margin =10Name =mainGrid>
< Grid.RowDefinitions>
< RowDefinition Height =Auto>< / RowDefinition>
< RowDefinition Height =Auto>< / RowDefinition>
< RowDefinition Height =Auto>< / RowDefinition>
< /Grid.RowDefinitions>
< DataGrid Name =myDataGridSelectionUnit =FullRowIsReadOnly =TrueMargin =5Grid.Row =0Grid.Column =0Grid.ColumnSpan =5AutoGenerateColumns = TrueHeight =200MaxWidth =900>
< / DataGrid>
< Button Name =buttonRefreshMargin =10Padding =20,5,20,5Grid.Row =1Grid.Column =0HorizontalAlignment =Stretch> Refresh< ; / Button>
< wfi:WindowsFormsHost Name =wfiThrobberGrid.Row =3Grid.Column =0Visibility =VisibleVerticalAlignment =CenterHorizontalAlignment =Center>
< winForms:PictureBox Name =imgThrobber>
< / winForms:PictureBox>
< / wfi:WindowsFormsHost>
< / Grid>
< / Window>
@
$ reader =(New-Object System.Xml.XmlNodeReader $ xaml)
$ syncHash.Window = [Windows.Markup.XamlReader] :: Load($阅读器)
$ 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()
$ cli ckRunspace.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()
})
I have datagrid that gets updated when a button is clicked. However in some cases the data is taking 30 seconds+ to return and the Window freezes. I want to be able to get the data and populate the datagrid from another thread so as not to hang the main window. The DataGrid is read only. Eventually want to have a "cancel" button and animation to indicate progress, but for now just want to get this working.
I made a sample program that can demonstrate the issue, which was based on http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/
I have already tried using Start-Job/Receive Job and .NET Background worker without success.
The script will be used on 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 fine on its own, just not when triggered from click event.
解决方案 Remain open to better solutions, this works, but I suspect is over engineered. I fixed this by creating a runspace within the click event. This loads an animated gif c:\scripts\throbber.gif to confirm the window is not hanging. Start-Sleep was used to simulate longer time to get data back.
$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()
})
这篇关于从PowerShell中的另一个线程更新WPF DataGrid ItemSource的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!