PowerShell WPF GUI - Voraussetzungen und erste GUI Anwendung

 

Eine sehr einfache Variante um mit PowerShell eine GUI zu erstellen bietet WPF-XAML. Das Layout kann dabei ähnlich einer HTML-Datei erstellt werden. Das kostenlose Visual Studio Express bietet zudem die Möglichkeit, das Layout einfach in einem grafischen Editor zu erstellen. Der eigentliche Programmcode wird mit PowerShell umgesetzt. 

Grafischer Editor: Visual Studio Express for Windows Desktop

Installation, siehe: Visual Studio Express Installation

Für die PowerShell GUI wähle ich "WPF Application"

Einfaches Beispiel: Text in das Fenster

Mit Hilfe der Toolbox können einzelne Fensterobjekte in die GUI gezogen werden, hier zum Beispiel "Label" für einen Text.

Im unteren Bereich von Visual Studio wird der für PowerShell benötigte XAML-Code erzeugt:

<Window x:Class="WpfApplication.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="768" Width="1024">
    <Label x:Name="label" Content="Text in das Fenster" HorizontalAlignment="Left" Height="24" Margin="20,29,0,0" VerticalAlignment="Top" Width="212"/>
</Window>

Der generierte XAML-Code kann zum Bearbeiten auch jederzeit von einem PowerShell Skript zurück in Visual-Studio kopiert werden.

XAML definiert das Aussehen unserer Anwendung, kombiniert mit folgendem PowerShell-Code kann diese gestartet werden:

#XAML Code kann zwischen @" und "@ ersetzt werden:
[xml]$XAML = @"
<Window x:Class="WpfApplication.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="768" Width="1024">
<Label x:Name="label" Content="Text in das Fenster" HorizontalAlignment="Left" Height="24" Margin="20,29,0,0" VerticalAlignment="Top" Width="212"/>
</Window>
"@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' #-replace wird benötigt, wenn XAML aus Visual Studio kopiert wird.
#XAML laden
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
try{
   $Form=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAML) )
} catch {
   Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mögliche Ursache: ungültige Syntax oder fehlendes .net"
}
#Fenster anzeigen:
$Form.ShowDialog()

Ich habe den Quellcode dazu in den Powershell ISE-Editor eingefügt:

Mit "F5" startet das Fenster: 

 

PowerShell XAML Variablen auslesen und manipulieren

Wir bleiben bei diesem Beispiel und versuchen den Text im Fenster zu ändern.

Mit folgendem Quellcode können die Fensterobjekte angezeigt werden:

$xaml.SelectNodes("//*[@Name]") | ft

Um den Fenstertext aus PowerShell zu ändern, müssen wir das Fensterobjekt laden und die Eigenschaft "Content" ändern:

$Form.FindName("label").Content= "hier ein neuer Text für das Fenster"

 

Damit Variablen in Zukunft nicht über $Form.FindName aufgerufen werden müssen, können alle Fensterobjekte als Variable geladen werden:

$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}

Unser Text kann jetzt auch mit $label.Content= "hier ein neuer Text für das Fenster" angesprochen und somit geändert werden.

Dynamisches Ansprechen der Objekte

In folgendem Beispiel wollte ich alle Textboxen aus XAML auslesen und den enthaltenen Text ändern.

Werden für eine Textbox immer Namen, beginnend mit zum Beispiel "txt" verwendet, können diese in einer ForEach-Schleife aufgerufen und geändert werden. In diesem Beispiel wird in allen vorhandenen Textboxen der Name der Textbox geschrieben:

ForEach( $textbox in (get-variable txt*) ) { $Form.FindName("$($textbox.name)").Text= $($textbox.name) }

GUI-Variable und zugehörige Datenbank-Spalte?

Durch die Möglichkeit die GUI-Objekte dynamisch anzusprechen, könnte ein GUI erstellt werden, in der die Textboxen gleich den Datenbank-Spalten benannt werden. Im PowerShell-Code müsste der Name der benötigten Spalten dann nicht zusätzlich erwähnt werden. Wenn wir noch einen Schritt weiter denken, könnten sogar die GUI-Elemente dynamisch erzeugt werden:

Nachträgliches Hinzufügen von GUI-Elementen

Mit folgendem Beispiel werden Buttons im Nachhinein zur GUI hinzugefügt:

#XAML Code kann zwischen @" und "@ ersetzt werden:
[xml]$XAML = @"
<Window x:Class="WpfApplication.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="768" Width="1024">
        <StackPanel x:Name="StackPanel" Margin = "50,50,50,50">                                          
        </StackPanel>
</Window>
"@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' #-replace wird benötigt, wenn XAML aus Visual Studio kopiert wird.
#XAML laden
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
try{$Form=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAML) )}
catch{Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mögliche Ursache: ungültige Syntax oder fehlendes .net"}
$StackPanel = $Form.FindName("StackPanel")

function add_button($strLabel) {
        $objButton = New-Object System.Windows.Controls.Button
        $objButton.Content = $strLabel
        $objButton.Background = 'Blue'
        $objButton.Foreground = 'White'
        $objButton.Name = $strLabel
        $objButton.Add_Click({
                write-host "$($this.content) pressed"
        }) 
        #insert it into the StackPanel
        $StackPanel.Children.Insert(($StackPanel.Children.count),$objButton)   

} 

#Aufruf der Funktion:
add_button "TextNewBTN"
add_button "TextNewBTN2"
add_button "TextNewBTN3"
#Fenster anzeigen:
$Form.ShowDialog()

siehe auch: PowerShell WPF GUI Auswahl - für PowerShell-Parameter 

Timer: Update des Fensters im Hintergrund

Mit folgendem Script-Block kann ein Timer für das regelmässige Update des Fenster gestartet werden:

$timer = new-object System.Windows.Threading.DispatcherTimer
$timer.Interval = [TimeSpan]"0:0:5.00"
$timer.Add_Tick($starttick)
$timer.Start()

 

In der Variable $starttick kann der eigentliche Task (Tick) hinterlegt werden. $timer definiert und startet den eigentlichen Timer. Mit $timer.Interval wird die Zeitspanne zwischen den Ticks, hier 5 Sekunden, festgelegt.

In folgendem Beispiel erhöhe ich in dem Tick eine Variable $i in jedem Durchgang und schreibe das Ergebnis, anstelle des vorhandenen Textes, in das Fenster:

$starttick={
   $Form.FindName("label").Content= $script:i++
}

Auch hier wird $script:i anstelle von $i verwendet, da $i in einem anderen Scope ausgeführt wird, siehe: Powershell-GUI#scope-Gültigkeitsbereich

 

 

 

 

 

Fortsetzung zu diesem Artikel:  PowerShell wpf gui tabControl oder mehrere Oberflächen 

positive Bewertung({{pro_count}})
Beitrag bewerten:
{{percentage}} % positiv
negative Bewertung({{con_count}})

DANKE für deine Bewertung!

Fragen / Kommentare


(sortiert nach Bewertung / Datum) [alle Kommentare(neueste zuerst)]

✍TK1987
07.10.2021 13:18 , geändert 07.10.2021 13:19
Hallo zusammen.

Beim replace der XAML führt "x:N","N" u.U. zu Fehlern, da dieser z. B. auch auf "x:Null" zutrifft - wo das "x:" jedoch zwingend davor stehen bleiben muss.

Für welchen Fall soll dieser überhaupt notwendig sein? Bei meinen Tests konnte ich diesen auch einfach weglassen und die XAML wurde problemlos geladen.
Sollte es doch iregendeinen Fall geben, so sollte der replace oben aber zumindestens in "x:N(?!ull)","N" abgeändert werden, um "x:Null" davon auszunehmen.

Gruß Thomas

✍anonym
07.10.2016 05:33
User: Lauch 
Danke, funktioniert super!