我有一个datagrid,当单击一个按钮时会更新。但是在某些情况下,数据需要30秒+才能返回,窗口会冻结。我希望能够从另一个线程获取数据并填充datagrid,以免挂起主窗口。 DataGrid是只读的。最终希望有一个“取消”按钮和动画来表示进度,但现在只想让这个工作。
我制作了一个可以证明问题的示例程序,该程序基于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但没有成功。 该脚本将用于Server 2012 R2上的PowerShell v4和Windows 10上的PowerShell v5。
$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!"
})
注意:
$syncHash.mainWindow.Dispatcher.Invoke([Action] {$syncHash.myDataGrid.ItemsSource = Get-Process },"Normal")
单独工作正常,而不是在点击事件触发时。
答案 0 :(得分:0)
保持对更好的解决方案的开放,这是有效的,但我怀疑是过度设计。我通过在click事件中创建一个运行空间来解决这个问题。这将加载动画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.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()
})