This post will take a look at providing Powershell script output within a WPF GUI. The code will display a log of messages that would usually be sent to the console, but are easily missed by users when interacting with a GUI. This also makes it simple to display output from other runspaces when using multiple sessions.

An Example GUI

A previous post looked at how to get a WPF GUI working with Powershell. We will modify the code created back then by adding a section to display the log messages for this example. If you are not familiar with data binding in WPF it would be helpful to take a look at the overview here.

WPF data binding

To make this work all that is needed is an ObservableCollection of type string with a single item that will hold the log messages. When this is bound to a TextBox control any updates to the value of the string will trigger a CollectionChanged event, which WPF controls listen for by default and dynamically update their content.

Below is a simplified section from the full script which creates and binds the ObservableCollection.

$Sync.LogDataContext = New-Object -TypeName System.Collections.ObjectModel.ObservableCollection[string]
$Sync.LogDataContext.Add('')
$Sync.Gui.LogTextBox.DataContext = $Sync.LogDataContext

# append to the first item of the ObservableCollection to write to the TextBox
$Sync.LogDataContext[0] += "Message one.`r`n"

By setting the DataContext for the text box control we can reference the first item of the collection with just [0] in the XAML definition. This completes the data binding.

<TextBox x:Name="LogTextBox" Text="{Binding [0], Mode=OneWay}" />

Displaying the log messages

As new log messages are added to the TextBox they should be always visible to the user. One way to do this is by making the ScrollViewer control automatically scroll down when a message is added. However, if the user has intentionally scrolled up the auto-scroll will not happen, and allows previous sections of the log to be viewed. The ScrollChanged event is used to run the code which handles this behaviour.

Below is the section from the full script.

# save the state of $AutoScroll outside the event context so it is not reset on every event
$global:AutoScroll = $true
$Sync.Gui.LogScrollViewer.add_ScrollChanged({
    if ($_.ExtentHeightChange -eq 0)
    {
        if ($Sync.Gui.LogScrollViewer.VerticalOffset -eq $Sync.Gui.LogScrollViewer.ScrollableHeight)
        {
            # if the ScrollViewer is scrolled to the end/bottom enable "auto-scroll"
            $global:AutoScroll = $true
        }
        else
        {
            $global:AutoScroll = $false
        }
    }

    if ($AutoScroll -eq $true -and $_.ExtentHeightChange -ne 0)
    {
        # scroll the ScrollViewer to the end/bottom
        $Sync.Gui.LogScrollViewer.ScrollToVerticalOffset($Sync.Gui.LogScrollViewer.ExtentHeight)
    }
})

And here is an example of the ScrollViewer behaviour.


Comments

comments powered by Disqus