[SOLVED] WPF window not updating from PowerShell

Issue

I’m having trouble with having a WPF-window do continues update from PowerShell, and also not freeze.

The idea is that when I click the button, some function is to be run that updates the value for the progressbar. But which ever method or guide I use, I can’t get it to work. The progressbar is being updated and is filled to 100% when the task is done. But during the operation, the window freezes. The only way I can have a window that is not freezing, is if I do a simple value addition in the updateblock. But I need to be able to run different functions and update the progressbar again and again.

What do I need to do to run a function or scriptblock from the Click-event so the window doesn’t freeze and the progressbar updates?

PowerShell-code:

Add-Type -Assembly PresentationFramework

$syncHash = [hashtable]::Synchronized( @{} )
$syncHash.Counter = 0.0
$syncHash.Do = {
    param ( $syncHash, $ToDo )
    $rsR = [runspacefactory]::CreateRunspace()
    $rsR.ApartmentState = "STA"
    $rsR.ThreadOptions = "ReuseThread"
    $rsR.Open()
    $rsR.SessionStateProxy.SetVariable( "syncHash", $syncHash )
    $c = [powershell]::Create().AddScript( $ToDo )
    $c.Runspace = $rsR
    $h = $c.BeginInvoke()
    do { start-sleep -millisecond 10 } until ( $h.IsCompleted -eq $true )
    $c.EndInvoke( $h )
    $rsR.Close()
    $rsR.Dispose()
}

$syncHash.Window    = [Windows.Markup.XamlReader]::Load( ( New-Object System.Xml.XmlNodeReader ( [xml]( Get-Content .\tt.xml ) ) ) )
$syncHash.Button    = $syncHash.Window.FindName( "Button" )
$syncHash.Progress  = $syncHash.Window.FindName( "Progress" )
$Timer = New-Object System.Windows.Threading.DispatcherTimer

$Task = { 1..100 | % { start-sleep -millisecond 10; $syncHash.Counter = $_ } }
$syncHash.Button.Add_Click( { Something } )
$syncHash.Window.Add_SourceInitialized( {
    $Timer.Start()
    $Timer.Add_Tick( $UpdateBlock )
    $Timer.Interval = [TimeSpan]"0:0:0.01"
} )

function Something
{
    & $syncHash.Do $syncHash $Task
}

$UpdateBlock =`
{
    $syncHash.Window.Resources["Progress"] = [double]$syncHash.Counter
    $syncHash.Window.Resources["PbText"] = "$( $syncHash.Counter ) %"
}
[void]$syncHash.Window.ShowDialog()

XAML-code:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        Topmost="True"
        WindowStartupLocation="CenterScreen"
        SizeToContent="WidthAndHeight">
   <Window.Resources>
      <system:Double x:Key="Progress">0.0</system:Double>
      <system:String x:Key="PbText"></system:String>
   </Window.Resources>

    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
        <Button Grid.Row="0" Content="Click" Name="Button"/>
        <Grid Grid.Row="1">
            <ProgressBar Name="Progress" Minimum="0" Value="{DynamicResource Progress}"  Width="230"/>
            <TextBlock Text="{DynamicResource PbText}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Grid>
    </Grid>
</Window>

Solution

I finally found what to do. Hade to rethink some stuff, and removed the runspace functionality all together.

Thanks to binding, it all got simpler.

Here is what I came to, also added binding for maxvalue of the progressbar:

Add-Type -Assembly PresentationFramework

$syncHash = @{}
$syncHash.Window    = [Windows.Markup.XamlReader]::Load( ( New-Object System.Xml.XmlNodeReader ( [xml]( Get-Content .\tt.xml ) ) ) )
$syncHash.Button    = $syncHash.Window.FindName( "Button" )
$syncHash.Progress  = $syncHash.Window.FindName( "Progress" )

$Something = { param( $d )
    function SomethingElse { gci }

    $d[1] = ( SomethingElse ).Count
    [system.windows.messagebox]::show($d[1])
    foreach ( $i in ( 1..$d[1] ) ) { Start-Sleep -MilliSeconds 300; $d[0] = [double]( ( $i/$d[1] ) * 100 ) }
}

$syncHash.Button.Add_Click( { ( [powershell]::Create().AddScript( $Something ).AddArgument( $DataContext ) ).BeginInvoke() } )

$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$DataContext.Add( 0.0 )
$DataContext.Add( 0.0 )
$syncHash.Progress.DataContext = $DataContext

$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[0]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding( $syncHash.Progress, [System.Windows.Controls.ProgressBar]::ValueProperty, $Binding )

$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[1]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding( $syncHash.Progress, [System.Windows.Controls.ProgressBar]::MaximumProperty, $Binding )

[void]$syncHash.Window.ShowDialog()

Answered By – Smorkster

Answer Checked By – Robin (BugsFixing Admin)

Leave a Reply

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