从PowerShell中的另一个线程更新WPF DataGrid ItemSource [英] Update WPF DataGrid ItemSource from Another Thread in PowerShell

查看:332
本文介绍了从PowerShell中的另一个线程更新WPF DataGrid ItemSource的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我点击按钮时,我有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没有成功。
该脚本将在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 =0Horizo​​ntalAlignment =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 =0Horizo​​ntalAlignment =Stretch> Refresh< ; / Button>
< wfi:WindowsFormsHost Name =wfiThrobberGrid.Row =3Grid.Column =0Visibility =VisibleVerticalAlignment =CenterHorizo​​ntalAlignment =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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆