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.

0


source to share


1 answer


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

})

      

0


source







All Articles