tzabbi
Goto Top

WPF Progressbar in Powershell aktualisieren

Hallo Zusammen,

ich hab schon viele Einträge hier gelesen, aber noch nicht so ganz verstanden, wieso es bei mir nicht funktioniert.

Das Problem was ich habe ist, dass ich via Powershell ein Backupskript geschrieben habe was super funktioniert. Ein Kollege möchte jetzt noch eine Progressbar haben die den aktuellen Stand anzeigt.
Also habe ich eine XAML-WPF-Gui zusammengeklickt und möchte das jetzt via Runspace aktualisieren, je nachdem wie viele Daten schon kopiert wurden.

Anbei der Sourcecode:

#region XAML window definition
# Right-click XAML and choose WPF/Edit... to edit WPF Design
# in your favorite WPF editing tool
$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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"  
   MinWidth="200"  
   Width ="400"  
   SizeToContent="Height"  
   Title="Backup"  
   Topmost="True" Height="250.77">  
    <Grid>
        <Label Content="Gesamtfortschritt:" HorizontalAlignment="Left" Margin="20,3,0,0" VerticalAlignment="Top"/>  
        <ProgressBar HorizontalAlignment="Left" Height="20" Margin="20,34,0,0" VerticalAlignment="Top" Width="351" Minimum="0" Maximum="100" Value="60" Name="pbStatusTotal"/>  
        <TextBlock Text="{Binding ElementName=pbStatusTotal, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="188,36,182,168" />  

        <Label Content="Aktueller Ordner:" HorizontalAlignment="Left" Margin="20,59,0,0" VerticalAlignment="Top"/>  
        <ProgressBar HorizontalAlignment="Left" Height="20" Margin="20,90,0,0" VerticalAlignment="Top" Width="351" Minimum="0" Maximum="100" Value="60" Name="pbStatusCurrentDirectory"/>  
        <TextBlock Text="{Binding ElementName=pbStatusCurrentDirectory, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="188,92,182,112" RenderTransformOrigin="0.31,0.521" />  

    </Grid>
</Window>

'@  
#endregion

#region Code Behind
function Convert-XAMLtoWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [string]
    $XAML
  )
  
  Add-Type -AssemblyName PresentationFramework
  
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  $result = [Windows.Markup.XAMLReader]::Load($reader)
  $reader.Close()
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  while ($reader.Read())
  {
      $name=$reader.GetAttribute('Name')  
      if (!$name) { $name=$reader.GetAttribute('x:Name') }  
      if($name)
      {$result | Add-Member NoteProperty -Name $name -Value $result.FindName($name) -Force}
  }
  $reader.Close()
  $result
}

function Show-WPFWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [Windows.Window]
    $Window
  )
  
  $result = $null
  $null = $window.Dispatcher.InvokeAsync{
    $result = $window.ShowDialog()
    Set-Variable -Name result -Value $result -Scope 1
  }.Wait()
  $result
}
#endregion Code Behind

#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml 
#endregion

#region Manipulate Window Content
$pbStatusTotal = $window.FindName('pbStatusTotal')  
$pbStatusTotal.Value = $i
#endregion

$syncHash = [hashtable]::Synchronized(@{})
$syncHash.pbStatusTotal = $pbStatusTotal

$newPowershell = [powershell]::Create()

$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"  
$newRunspace.ThreadOptions = "ReuseThread"   
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)  

$code = { foreach ($i in (1..100))
          {
              Start-Sleep -Milliseconds 100
              Write-Host $i
              $syncHash.pbStatusTotal.Value = 50
          }
        } 

$newPowershell.AddScript({
                              # in this expample we set the progress-bar value every 500 ms
                              for ($i = 1;$i -le 10;$i++){
                                  sleep -Milliseconds 500
                                  $syncHash.pbStatusTotal.Value = $i * 10
                              }
                         })

$newPowershell.Runspace = $newRunspace
$handle = $newPowershell.BeginInvoke()

# Show Window
$result = Show-WPFWindow -Window $window

Für Tipps bin ich euch sehr Dankbar!

VG
tzabbi

Content-ID: 558008

Url: https://administrator.de/contentid/558008

Ausgedruckt am: 19.12.2024 um 15:12 Uhr

canlot
canlot 15.03.2020 um 01:03:04 Uhr
Goto Top
Hi,

warum benutzt du nicht die eingebaute Powershell Progressbar?
Der Befehl dazu ist Write-Progress
tzabbi
tzabbi 15.03.2020 um 01:07:27 Uhr
Goto Top
Hi,

weil mein Kollege gern eine schönere GUI haben will und weil ich diese dann noch erweitern kann.

Viele Grüße
tzabbi
colinardo
Lösung colinardo 15.03.2020 aktualisiert um 09:58:00 Uhr
Goto Top
Servus,
wieso es bei mir nicht funktioniert.
WPF ist hier ein wenig anders gestrickt was das Multithreading betrifft, dein kopierter Code aus einem meiner Beiträge hier war nur für einfache Windows-Forms gedacht, nicht für WPF face-wink.

#region XAML window definition
# Right-click XAML and choose WPF/Edit... to edit WPF Design
# in your favorite WPF editing tool
$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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"  
   MinWidth="200"  
   Width ="400"  
   SizeToContent="Height"  
   Title="Backup"  
   Topmost="True" Height="250.77">  
    <Grid>
        <Label Content="Gesamtfortschritt:" HorizontalAlignment="Left" Margin="20,3,0,0" VerticalAlignment="Top"/>  
        <ProgressBar Height="20" Margin="20,34,20,0" VerticalAlignment="Top" Minimum="0" Maximum="100" Value="0" Name="pbStatusTotal"/>  
        <TextBlock Text="{Binding ElementName=pbStatusTotal, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" Margin="188,36,182,168" />  
        <Label Content="Aktueller Ordner:" HorizontalAlignment="Left" Margin="20,59,0,0" VerticalAlignment="Top"/>  
        <ProgressBar Height="20" Margin="20,90,20,0" VerticalAlignment="Top" Minimum="0" Maximum="100" Value="60" Name="pbStatusCurrentDirectory"/>  
        <TextBlock Text="{Binding ElementName=pbStatusCurrentDirectory, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" Margin="188,92,182,112" RenderTransformOrigin="0.31,0.521" />  
    </Grid>
</Window>

'@  
#endregion

#region Code Behind
function Convert-XAMLtoWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [string]
    $XAML
  )
  
  Add-Type -AssemblyName PresentationFramework
  
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  $result = [Windows.Markup.XAMLReader]::Load($reader)
  $reader.Close()
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  while ($reader.Read())
  {
      $name=$reader.GetAttribute('Name')  
      if (!$name) { $name=$reader.GetAttribute('x:Name') }  
      if($name)
      {$result | Add-Member NoteProperty -Name $name -Value $result.FindName($name) -Force}
  }
  $reader.Close()
  $result
}

function Show-WPFWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [Windows.Window]
    $Window
  )
  
  $result = $null
  $null = $window.Dispatcher.InvokeAsync{
    $result = $window.ShowDialog()
    Set-Variable -Name result -Value $result -Scope 1
  }.Wait()
  $result
}
#endregion Code Behind

#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml 
#endregion

#region Manipulate Window Content
$pbStatusTotal = $window.FindName('pbStatusTotal')  
$pbStatusTotal.Value = 0
#endregion

$syncHash = [hashtable]::Synchronized(@{})
$syncHash.pbStatusTotal = $pbStatusTotal

$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"  
$newRunspace.ThreadOptions = "ReuseThread"   
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)  

$newPowershell = [powershell]::Create()
$newPowershell.AddScript({
    # in this expample we set the progress-bar value every 500 ms
    for ($i = 1;$i -le 10;$i++){
        sleep -Milliseconds 100
        $syncHash.pbStatusTotal.Dispatcher.Invoke("Normal", [action]{$syncHash.pbStatusTotal.Value=$i *10})  
    }
}) | out-null

$newPowershell.Runspace = $newRunspace

$window.add_ContentRendered({
    $handle = $newPowershell.BeginInvoke()
})

# Show Window
$result = Show-WPFWindow -Window $window
Das wäre die eine Möglichkeit, die andere arbeitet mit einem Dispatcher-Timer, das kannst du dir hier abschauen
http://tiberriver256.github.io/powershell/PowerShellProgress-Pt3/


Grüße Uwe
tzabbi
tzabbi 15.03.2020 um 10:37:57 Uhr
Goto Top
Hallo Uwe,

super vielen Dank! Ich werde mir nochmal genau anschauen, wie das mit dem Dispatcher genau funktioniert!

Schönen Sonntag dir noch!

Gruß
tzabbi
colinardo
Lösung colinardo 15.03.2020 aktualisiert um 11:01:24 Uhr
Goto Top
Zitat von @tzabbi:
Schönen Sonntag dir noch!
Wünsche ich dir ebenso.

Hier noch als Ergänzung dein obiges Beispiel mit der oben schon angesprochenen alternativen Variante Dispatcher-Timer Variante umgesetzt. Den TimeSpan für den Timer kann man natürlich an seine Gegebenheiten anpassen, ist jetzt nur für das schnelle Beispiel auf eine zehntel Sekunde gesetzt.

#region XAML window definition
# Right-click XAML and choose WPF/Edit... to edit WPF Design
# in your favorite WPF editing tool
$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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"  
   MinWidth="200"  
   Width ="400"  
   Title="Backup"  
   Topmost="True" Height="200">  
    <Grid>
        <Label Content="Gesamtfortschritt:" HorizontalAlignment="Left" Margin="20,3,0,0" VerticalAlignment="Top"/>  
        <ProgressBar Height="20" Margin="20,34,20,0" VerticalAlignment="Top" Minimum="0" Maximum="100" Value="0" Name="pbStatusTotal"/>  
        <TextBlock Text="{Binding ElementName=pbStatusTotal, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" Margin="20,36,20,0" />  

        <Label Content="Aktueller Ordner:" HorizontalAlignment="Left" Margin="20,59,0,0" VerticalAlignment="Top"/>  
        <ProgressBar Height="20" Margin="20,90,20,0" VerticalAlignment="Top" Minimum="0" Maximum="100" Value="60" Name="pbStatusCurrentDirectory"/>  
        <TextBlock Text="{Binding ElementName=pbStatusCurrentDirectory, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" Margin="20,92,20,0"/>  

    </Grid>
</Window>

'@  
#endregion

#region Code Behind
function Convert-XAMLtoWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [string]
    $XAML
  )
  
  Add-Type -AssemblyName PresentationFramework
  
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  $result = [Windows.Markup.XAMLReader]::Load($reader)
  $reader.Close()
  $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
  while ($reader.Read())
  {
      $name=$reader.GetAttribute('Name')  
      if (!$name) { $name=$reader.GetAttribute('x:Name') }  
      if($name)
      {$result | Add-Member NoteProperty -Name $name -Value $result.FindName($name) -Force}
  }
  $reader.Close()
  $result
}

function Show-WPFWindow
{
  param
  (
    [Parameter(Mandatory=$true)]
    [Windows.Window]
    $Window
  )
  
  $result = $null
  $null = $window.Dispatcher.InvokeAsync{
    $result = $window.ShowDialog()
    Set-Variable -Name result -Value $result -Scope 1
  }.Wait()
  $result
}
#endregion Code Behind

#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml 
#endregion

#region Manipulate Window Content
$pbStatusTotal = $window.FindName('pbStatusTotal')  
$pbStatusTotal.Value = 0
#endregion

$syncHash = [hashtable]::Synchronized(@{})
$syncHash.pbValue = 0

$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"  
$newRunspace.ThreadOptions = "ReuseThread"   
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)  

$newPowershell = [powershell]::Create()
$newPowershell.AddScript({
    # sampe for long running action with update to progressbar
    1..10 |  %{$syncHash.pbValue = $_ * 10; sleep -Milliseconds 250}
}) | out-null

# create dispatcher timer on window
$window.add_SourceInitialized({
    $timer = New-Object System.Windows.Threading.DispatcherTimer
    $timer.Interval = [Timespan]'0:0:0.10'  
    # timer action update progressbar value
    $timer.Add_Tick({$pbStatusTotal.Value = $syncHash.pbValue})
    $timer.Start()
})

# assign runspace and start script
$newPowershell.Runspace = $newRunspace
$handle = $newPowershell.BeginInvoke()

# Show Window
$result = Show-WPFWindow -Window $window

Grüße Uwe