Update WPF DataGrid ItemSource from Another Thread in PowerShell

℡╲_俬逩灬. 提交于 2020-01-17 05:08:03

问题


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.


回答1:


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

})


来源:https://stackoverflow.com/questions/34009752/update-wpf-datagrid-itemsource-from-another-thread-in-powershell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!