0

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.

Malcolm McCaffery
  • 2,468
  • 1
  • 22
  • 43

1 Answers1

0

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

})
Malcolm McCaffery
  • 2,468
  • 1
  • 22
  • 43