In my first two posts on this subject, I was just getting started with learning responsive WPF form building. I’m here today to show you a better way to build a responsive WPF using runspaces that will do the exact same thing as my previous uploads showed. Just better.
This time, I won’t be putting the form into its own runspace. As I learnt you didn’t need to from JRV over on the TechNet forums. This has some benefits that I will very helpfully, briefly and probably incorrectly list below:
- Don’t have to use syncHash when updating the form
- One less runspace for the form
- Having a form in its own runspace creates additional overhead and possible errors
So to display the form I would use something like the below:
$xml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/> <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/> </Grid> </Window> "@ $Reader=(New-Object System.Xml.XmlNodeReader $xml) $Window=[Windows.Markup.XamlReader]::Load($Reader) $Label = $Window.FindName("Label") $Button = $Window.FindName("Button") $Window.ShowDialog() | Out-Null
Which will give us the below form:
But what if I want the button press to make the label number increase? I would use something like this on the button press:
$xml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/> <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/> </Grid> </Window> "@ $Reader=(New-Object System.Xml.XmlNodeReader $xml) $Window=[Windows.Markup.XamlReader]::Load($Reader) $Label = $Window.FindName("Label") $Button = $Window.FindName("Button") $Button.Add_Click({ $counter = 1 do{ Start-Sleep -Milliseconds 5 $label.content = $counter [System.Windows.Forms.Application]::DoEvents() $counter += 1 }while ($counter -le 5000) }) $Window.ShowDialog() | Out-Null
This produces a form which increases the label up to 5000 when the button is pressed. You can see this below:
But what if I want to actually run something in a runspace. For example, test the connection to google.com? Then I would use the below code:
$xml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/> <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/> </Grid> </Window> "@ $Reader=(New-Object System.Xml.XmlNodeReader $xml) $Window=[Windows.Markup.XamlReader]::Load($Reader) $Label = $Window.FindName("Label") $Button = $Window.FindName("Button") $Button.Add_Click({ $syncHash = [hashtable]::Synchronized(@{}) $Runspace = [runspacefactory]::CreateRunspace() $Runspace.ApartmentState = "STA" $Runspace.ThreadOptions = "ReuseThread" $Runspace.Open() $Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash) $powershell = ::Create().AddScript({ $connection = Test-Connection -ComputerName google.com -Count 5 $syncHash.output = [math]::Round(($connection.ResponseTime | Measure-Object -Average).Average) }) $powershell.Runspace = $Runspace $Object = $powershell.BeginInvoke() do { Start-Sleep -Milliseconds 50 [System.Windows.Forms.Application]::DoEvents() }while(!$Object.IsCompleted) $powershell.EndInvoke($Object) $powershell.Dispose() $label.Content = $syncHash.output }) $Window.ShowDialog() | Out-Null
All the above examples will stay responsive whilst the action is performed. There are a couple of different methods to do this as you can see above, for anything that takes some time to complete, a runspace is needed. But when you are updating the form quickly, like the counter, then no runspace is needed.
Enjoy!