PowerShell GUI – Update WPF Form / Controls

Sometimes you need to create simple PowerShell scripts to address a business problem and hand it over to the users’ community where a command line environment is not as welcomed as the point and click environment that many users have become accustomed to.

You have two options to create a GUI for your Powershell script: Windows Forms (WinForms) or Windows Presentation Foundation (WPF).

With different options comes some practical differences, and here I will demonstrate a way to update controls in a WPF/XAML Form.

The simple script here will update textbox and label text while going through for loop.


# Update-FormWPF.ps1
# NOTE:demonstrate updating form built with WPF
# Written by: Alex Dujakovic, Jan 2017

Add-Type -AssemblyName PresentationFramework

# =========|Functions|============================

Function LabelDisplayCounting{
   param( 
   [string]$Text,
   [int]$intNum
)
Switch($intNum){
1 {
    $MsgDisplayCount.Visibility="Visible"
    $MsgDisplayCount.Background="#00FF00"
    $Form.Dispatcher.Invoke([action]{
    $MsgDisplayCount.Content = "$($Text)"},"Render")
   }
5 {
    $MsgDisplayCount.Visibility="Visible"
    $MsgDisplayCount.Background="#FF00FF"
    $Form.Dispatcher.Invoke([action]{
    $MsgDisplayCount.Content = "$($Text)"},"Render")
   }
10 {
    $MsgDisplayCount.Visibility="Visible"
    $MsgDisplayCount.Background="#FFFF00"
    $Form.Dispatcher.Invoke([action]{
    $MsgDisplayCount.Content = "$($Text)"},"Render")
   }
15 {
    $MsgDisplayCount.Visibility="Visible"
    $MsgDisplayCount.Background="#FF0000"
    $Form.Dispatcher.Invoke([action]{
    $MsgDisplayCount.Content = "$($Text)"},"Render")
   }
Default{
    $MsgDisplayCount.Visibility="Visible"
    $Form.Dispatcher.Invoke([action]{
    $MsgDisplayCount.Content = "$($Text)"},"Render")
   }
}  
}

Function TextShowCounting{
   param( [string]$Text )
    $Form.Dispatcher.Invoke([action]{
    $TxtCountOneLine.AddText("$($Text)")},"Render")
}

Function ShowTextMultiLine{
   param(
    [string]$Text,
    [int]$intLine
    )
Switch($intLine){
5 {
    $Form.Dispatcher.Invoke([action]{
    $TxtTextMultiLine.AddText("Number: $($Text) line is blue. `n"); 
    $TxtTextMultiLine.Foreground="Blue"},"Render")
   }
10 {

    $Form.Dispatcher.Invoke([action]{
    $TxtTextMultiLine.AddText("Number: $($Text) line is red. `n"); 
    $TxtTextMultiLine.Foreground="Red" },"Render")
   }
15 {

    $Form.Dispatcher.Invoke([action]{
    $TxtTextMultiLine.AddText("Number: $($Text) line is green. `n"); 
    $TxtTextMultiLine.Foreground="Green"},"Render")
   }
Default{
    $Form.Dispatcher.Invoke([action]{
    $TxtTextMultiLine.AddText("$($Text)`n")},"Render")
   }
 }  
}

Function UpdateThisForm{
 for($i=1;$i-le 15; $i++){
    TextShowCounting -Text "$i "
    LabelDisplayCounting -Text "- $i -" -intNum $i
    ShowTextMultiLine -Text "$i" -intLine $i
    Start-Sleep -Seconds 1
  }
    TextShowCounting -Text "Counting Ended!!!"
    LabelDisplayCounting -Text "The End!!!" -intNum $i
    ShowTextMultiLine -Text "The End!!!"
}

$XAML = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="PowerShell WPF - Updating Form" Height="450" Width="450">
<Grid>
<Label Content="PowerShell WPF - Updating Form" Height="30" HorizontalAlignment="Left" Margin="50,5,0,0" Name="LabelTitle" VerticalAlignment="Top" Width="380" FontSize="18" FontWeight="Bold"/>
<TextBox Height="23" HorizontalAlignment="Left" Margin="30,50,0,0" Name="TxtDisplayCountOneLine" VerticalAlignment="Top" Width="380" />
<Label Content="" Height="25" HorizontalAlignment="Left" HorizontalContentAlignment="Center" Margin="30,80,0,0" Visibility="Hidden" Name="LabelDisplayCount" VerticalAlignment="Top" Width="380" FontWeight="Bold"/>
<TextBox HorizontalAlignment="Left" Margin="30,110,0,0" Name="TxtDisplayTextMultiLine" VerticalAlignment="Top" Width="380" AcceptsReturn="True" TextWrapping="Wrap"/>
<Button Content="COUNT" Height="23" HorizontalAlignment="Left" Margin="300,370,0,0" Name="BtnCount" VerticalAlignment="Top" Width="75" ToolTip="Start counting"/>
<Button Content="CLOSE" Height="23" HorizontalAlignment="Left" Margin="200,370,0,0" Name="BtnClose" VerticalAlignment="Top" Width="75" ToolTip="Close this form"/>
</Grid>
</Window>
"@

#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
$Form=[Windows.Markup.XamlReader]::Load( $reader )

#Controls
$Count = $Form.FindName('BtnCount')
$Close = $Form.FindName('BtnClose')
$MsgDisplayCount = $Form.FindName('LabelDisplayCount')
$TxtCountOneLine = $Form.FindName('TxtDisplayCountOneLine')
$TxtTextMultiLine = $Form.FindName('TxtDisplayTextMultiLine')

# -------------------|Events|------------------

$Count.Add_Click({
TextShowCounting -Text "Start counting "
LabelDisplayCounting -Text "Start" -intNum 0
ShowTextMultiLine -Text "Start counting "
UpdateThisForm
}) 

$Close.Add_Click({$Form.Close()})

$Form.ShowDialog() | out-null

updatewpf

Picture 1: updating textbox and label controls while going through for loop.

As presented in the code, the WPF form updates its controls in a different way than WinForms.
The source of the problem is that the WPF form is in the same thread as the PowerShell script, and in order to update controls in the form we need to control the thread by calling $Form.Dispatcher.Invoke into [action]… We have to use the overload and specify the DispatcherPriority as “Render” or it won’t update the controls on the WPF form consistently.
The script presented here could be found in the download section – under PowerShell folder.


Leave a Reply

Your email address will not be published. Required fields are marked *