Windows-Anwendungen mit PowerShell und Windows Presentation Framework (WPF) – Teil 3: Zugriff auf Steuerelemente mit PowerShell

Die Artikelserie wendet sich an alle die Interesse an dem Thema PowerShell und damit bereits ein wenig rumexperimentiert haben. Da ich jeden Schritt in der Anleitung erklären werde, sollte Sie dies schnell und einfach auf Ihre eigene Aufgabenstellung adaptieren können.

Im Rahmen dieser Artikelserie sind folgende Teile geplant bzw. wurden bereits veröffentlicht:

Wie bereits im Teil 1 der Artikelserie beschrieben, geht es mir nicht darum, dieses Thema abstrakt zu behandeln, sondern ich habe mir eine konkrete Aufgabenstellung dafür gesucht (siehe Abschnitt “Aufgabenstellung” im ersten Teil). Nachdem Sie am Ende des zweiten Teils dazu in der Lage waren, die Windows Anwendung mit den erforderlichen Steuerelementen zu gestalten, zeige ich Ihnen in diesem Teil der Serie, wie Sie in einem PowerShell-Skript

  • Eigenschaften von Steuerelementen ändern und abfragen
  • Funktionen an Steuerelemente binden
  • Mit Kontextmenüs arbeiten können
  • Tastenkombinationen an eine Windows-Anwendung bzw. ein Steuerelement zuordnen

Als Ausgangsbasis für diesen Blogeintrag verwende ich den Programmcode, den ich Ihnen am Ende des zweiten Teils eingefügt habe und erweitere das XAML um das zusätzliche Label-Steuerelement.

$Style=@" 
<Window.Resources> 
    <Style TargetType="{x:Type Label}"> 
        <Setter Property="VerticalAlignment" Value="Top" /> 
        <Setter Property="Margin" Value="0,0,0,10" /> 
        <Setter Property="Background" Value="Transparent" /> 
        <Setter Property="BorderBrush" Value="Transparent" /> 
        <Setter Property="FontWeight" Value="Bold" /> 
        </Style> 
    <Style TargetType="{x:Type DataGrid}"> 
        <Setter Property="AutoGenerateColumns" Value="True" /> 
        <Setter Property="IsReadOnly" Value="True" /> 
        <Setter Property="RowBackground" Value="#FFE6E6E6" /> 
        <Setter Property="AlternatingRowBackground" Value="White" /> 
        <Setter Property="AlternationCount" Value="2"/> 
        <Setter Property="CanUserReorderColumns" Value="True" /> 
        <Setter Property="CanUserResizeColumns" Value="True" /> 
        <Setter Property="CanUserResizeRows" Value="True" /> 
        <Setter Property="CanUserSortColumns" Value="True" /> 
    </Style> 
    <Style TargetType="{x:Type DataGridColumnHeader}"> 
        <Setter Property="VerticalContentAlignment" Value="Center" /> 
        <Setter Property="Background" Value="LightGray" /> 
        <Setter Property="FontWeight" Value="Bold" /> 
        </Style> 
</Window.Resources> 
"@             
[xml] $xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test-Anwendung" Height="200" Width="200">
$Style
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Menu Height="22" Name="menu1" VerticalAlignment="Top" Grid.Row="0">
            <MenuItem Header="_File" Name="File">
                <MenuItem Header="_Open" Name="Open" InputGestureText="Ctrl+O" />
                <MenuItem Header="_Close" Name="Close" InputGestureText="Ctrl+T" />
                <MenuItem Header="_Print" Name="Print" InputGestureText="Ctrl+P" />
                <MenuItem Header="E_xport" Name="Export" InputGestureText="Ctrl+X" />
                <Separator/>
                <MenuItem Header="_Exit" Name="Exit" InputGestureText="ALT+F4" />
                </MenuItem>
            <MenuItem Header="_Options" Name="Options" />
            <MenuItem Header="_Help" Name="Help" />
        </Menu>
        <ScrollViewer Grid.Row="1">
            <DockPanel Name="DP" Background="LightBlue"
                ScrollViewer.CanContentScroll="True"
                ScrollViewer.HorizontalScrollBarVisibility="Auto"
                ScrollViewer.VerticalScrollBarVisibility="Auto">                
		<Label Name="LB1" /> 
                <DataGrid Name="DG1" />
            </DockPanel>
        </ScrollViewer>
    </Grid>
</Window>
"@            
Add-Type -Assembly PresentationFramework             
$reader=(New-Object System.Xml.XmlNodeReader $xaml)            
$Form=[Windows.Markup.XamlReader]::Load( $reader )            
$c = $Form.ShowDialog()

Die entsprechende (kleine) Code-Änderung zum Teil 2 habe ich Ihnen in der XAML-Struktur rot markiert.

Eigenschaften von Steuerelementen ändern

Jede Anpassung an der Windows-Anwendung muss jedoch passiert sein, bevor Sie die Methode ShowDialog des Windows aufrufen und nachdem die XAML-Struktur geladen wurde:

$Form=[Windows.Markup.XamlReader]::Load( $reader )             
# Anpassungen an der Windows-Anwendung             
$c = $Form.ShowDialog()

Das sich die weiteren Anpassungen ausschließlich auf den grün markierten Bereich in dem vorhergehenden PowerShell-Code-Abschnitt beziehen und nicht mehr auf das gesamte Programm, stelle ich der Einfachheit halber im Folgenden nur noch diesen dar. Damit Sie das Programm testen können, müssen Sie jedoch jeweils den vollständigen Programmcode inkl. XAML-Struktur verwenden.

Label Content

Um ein Steuerelement mit PowerShell ansprechen zu können, müssen Sie es zuerst in der Struktur finden. Dazu stellt Ihnen der System.Windows.Window-Klasse (das ist der Typ der $Form-Variable in diesem Beispiel), die Methode “FindName” zur Verfügung.

Hinweis: Diese Methode ist auch innerhalb anderer Container-Klassen (wie z. B. Grid) verfügbar. Deshalb müssen Sie nicht immer über die gesamte Struktur suchen, sondern können sich auf einen bestimmten Bereich begrenzen, in dem das Steuerelement sich befindet.

Die Verwendung dieser Methode ist sehr einfach, da Sie nur den Namen des Steuerelements als Zeichenkette übergeben müssen, auf das Sie zugreifen wollen:

$lb = $Form.FindName("LB1")

Hinweis: Das ist auch der Grund, warum Sie Steuerelementen sinnvolle Namen geben sollten. Ich habe mir angewöhnt mich an ein Namenskonvention zu halten. Doch da es dafür keine einheitlichen Standards gibt, muss sich wohl jeder selbst dazu Gedanken machen.

Nach dem Aufruf wird der Variablen $lb das entsprechende Steuerelement übergeben und Sie können auf die Eigenschaften und Methoden dieses Steuerelements entsprechend des jeweiligen Typs zugreifen. Zum Ändern des Textes in einem Label-Steuerelement existiert die Eigenschaft “Content”. Geben Sie nun folgenden Befehl ein:

$lb.Content = "Prozesse:"


Der leere Textbereich des Label-Steuerelements wird nun mit dem unter Content angegebenen Wert gefüllt. Dabei kann es sich um eine feste Zeichenkette (wie in diesem Beispiel) oder auch um eine Variable wie z. B.

$lb.Content = $Env:Computername

handeln.

Der Programmcode (gekürzt) sieht nun folgendermaßen aus:

$Form=[Windows.Markup.XamlReader]::Load( $reader )            
$lb = $Form.FindName("LB1")            
$lb.Content = "Prozesse:"            
$c = $Form.ShowDialog()

Im Ergebnis sieht die Anwendung nun so aus:

image

Sie können die Eigenschaften natürlich nicht nur setzen, sondern diese auch Abfragen:

$lbContent = $lb.Content
Dategrid ItemSource

Nachdem das Label bereits einen sinnvollen Inhalt erhalten hat, sollten wir auch dem DataGrid einen sinnvollen Inhalt zuweisen. Im ersten Schritt müssen Sie jedoch wieder das DataGrid einer Variablen zuweisen. Eine Möglichkeit dem DataGrid Leben einzuhauchen ist es zeilen- und spaltenweise mit Daten zu füllen. Doch dies ist sehr aufwendig und übernimmt die PowerShell gerne für uns (und spart uns so eine Menge Arbeit und das Risiko Fehler zu programmieren).

Die Eigenschaft eines DataGrid-Steuerelements, die für den Inhalt verantwortlich ist, ist ItemsSource. Zur Demonstration verwende ich dafür das Get-Process CmdLet. Somit brauchen wir genau zwei Zeilen, um alle aktuell auf Ihrem PC laufenden Prozesse mit allen Eigenschaften in das DataGrid zu füllen:

$dg = $Form.FindName("DG1")             
$dg.ItemsSource = Get-Process


Um die Ausgabe noch ein wenig aufzuhübschen, sortiere ich das Ergebnis noch nach CPU-Auslastung (absteigend) und verwende nur bestimmte Eigenschaften des Get-Process CmdLets:

$dg = $Form.FindName("DG1")             
$dg.ItemsSource = Get-Process |             
    Sort-Object -property CPU -Descending  |             
    Select-Object -property Name,Company,Path,CPU -first 10

image

In der XAML-Struktur habe ich für das Style des DataGrid die Eigenschaft “AutoGenerateColumns” auf True gesetzt. Damit werden alle Spalten des als ItemsSource übergebenen Objekts in dem Grid dargestellt.

Möchten Sie nur bestimmte Spalten darstellen bzw. diese individuell formatieren, so können Sie dies entweder mit dem angegebenen Select-Object Cmdlet durchführen oder die Bindung der Spalten des DataGrid direkt in der XAML-Struktur vornehmen:

<DataGrid.Columns>

<DataGridTextColumn Header="Name" Binding="{Binding ProcessName}" />

<DataGridTextColumn Header="Company" Binding="{Binding MainModule.FileVersionInfo.CompanyName}" />

<DataGridTextColumn Header="Path" Binding="{Binding MainModule.FileName}" />

<DataGridTextColumn Header="CPU" Binding="{Binding TotalProcessorTime.TotalSeconds}" />

</DataGrid.Columns>

Sie können jedoch auch jedes beliebige andere CmdLet (inkl. selbst geschriebene) verwenden, das eine Liste als Ausgaben hat.

Funktionen an Steuerelemente binden

Im nächsten Schritt sollen auch für die Menü-Einträge (MenuItems) Funktionen hinterlegt werden. Dafür möchte ich wieder mit einem einfachen Beispiel beginnen, indem ich dem Menüpunkt "<File><Exit>” eine Funktion zum Beenden der Anwendung übergebe:

$mi_exit= $Form.FindName("Exit")             
$mi_exit.Add_Click( {$Form.Close()} )

Wie Sie sehen, ist das sehr einfach, da Sie lediglich einem Ereignis, in diesem Fall dem Click-Event, eine Methode übergeben müssen. Die Methode ist in diesem Beispiel die Close-Methode des Windows. Da sich dies nicht an einem Bild vorführen lässt, probieren Sie es doch einfach mal aus.

Datei öffnen

Der nächste Menüpunkt, den ich mit einer Aktion füllen möchte, ist <File><Open>. Hier wird es etwas komplexer, da PowerShell kein CmdLet zum Selektieren einer Datei aus dem Dateisystem zur Verfügung stellt, müssen wir hier auf eine Klasse aus dem .NET-Framework (ab Version 3) des Namespaces Microsoft.Win32 zurückgreifen. Doch auch das ist von PowerShell aus kein Problem, wie ich Ihnen am folgenden Beispiel darstellen möchte:

$mi_open = $Form.FindName("Open")             
$mi_open.Add_Click({             
    $OpenFile = New-Object Microsoft.Win32.OpenFileDialog             
    $OpenFile.Filter = "Text (*.txt)|*.txt"             
    $OpenFile.CheckFileExists = $true             
    if ($OpenFile.ShowDialog() -eq $true) {             
    }             
})

Hinweis: Ein Klasse “OpenFileDialog” stellt Ihnen auch der Namespace “System.Windows.Forms” zur Verfügung. Da ich aber innerhalb der Anwendung versuchen werden, alles ohne Windows.Forms zu implementieren, habe ich mich für den Microsoft.Win32-Namespace für diese Aufgabe entschieden.

Die Verwendung dieser Klasse ist sehr einfach und mit wenigen Zeilen, in denen Sie Eigenschaften zu diesem Objekt zuweisen, können Sie einen Standard-DateiÖffnen-Dialog erstellen.

Klicken Sie nun auf den Menüpunkt "<File><Open>” wird der Dialog zum Öffnen einer Datei angezeigt:

image

Um es einfach zu halten, habe ich in diesem Schritt noch keine Aktion für die Datei hinterlegt. Jedoch werde ich Ihnen im nächsten Teil der Serie zeigen, wie Sie den Inhalt einer Datei mit der PowerShell-WPF-Anwendung weiter verarbeiten können.

Druckfunktion

In der Anwendung ist eine Druckausgabe vorgesehen. Deshalb weise ich der entsprechenden Menüfunktion ebenfalls eine Klasse aus dem .NET-Framework (System.Windows.Controls.PrintDialog) zu.

$mi_Print= $Form.FindName("Print")            
$mi_Print.Add_Click( {            
    $printDlg = New-Object System.Windows.Controls.PrintDialog            
    if ($printDlg.ShowDialog() -eq $true) {
    }            
} )

Da die Vorgehensweise ähnlich der DateiÖffnen-Funktion ist, erläutere ich dies nicht weitergehend.

Individuelle Dialoge

Innerhalb der Windows-Anwendung lassen sich auch individuelle Dialoge öffnen, die z. B. wiederum auf einer XAML-Struktur basieren. Dies möchte ich Ihnen am Beispiel des Menüpunktes “Options” demonstrieren. Im ersten Schritt definieren Sie die XAML-Struktur (ähnlich wie beim Hauptfenster) und zeigen den Dialog dann an, wenn auf den entsprechenden Menüpunkt geklickt wird.

[xml] $xamlOptions = @" 
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Options"> 
$Style 
    <Label Name="Option_Label" Content="Testdialog innerhalb der PowerShell-Anwendung"/> 
</Window> 
"@             
$mi_Options= $Form.FindName("Options")             
$mi_Options.Add_Click( {             
    $OptionForm=[Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xamlOptions))              
    $OptionForm.ShowDialog() | Out-Null             
} )

image

Bei der Verwendung von längeren Funktionen für Ereignisse ist es sehr aufwendig, diese immer als Skriptblock dem Add_Click-Ereignis (Event) zuzuweisen. Eine Alternative dazu ist die Verwendung der Invoke-Methode. Dies möchte ich Ihnen am Beispiel des <Help>-Menüeintrags erläutern.

Damit eine Funktion durch das Ereignis aufgerufen werden kann, müssen wir diese natürlich erst einmal definieren. Da es bei einer Hilfe-Funktion immer sehr sinnvoll ist ein Fenster anzuzeigen, verwende ich der Einfachheit halber den Dialog, den ich bereits bei dem Option-Dialog verwendet habe. Die einzige Änderung an dem Dialog besteht darin, dass ich den Text (Content) des Label-Steuerelements entsprechend anpassen muss.

function Get-SampleHelp() {             
    $HelpForm=[Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xamlOptions))             
    $lb1 = $HelpForm.Findname("Option_Label")             
    $lb1.Content = "Das ist ein Hilfetext"             
    $HelpForm.ShowDialog() | Out-Null             
}


Das war es dann auch schon. Im nächsten Schritt das entsprechende Steuerelement einer Variablen zuweisen und das Ereignis “Add_Click” anfügen. Zum Schluss können Sie dann die entsprechende Funktion mit der Invoke-Methode aufrufen.

$mi_help = $Form.FindName("Help")             
$help_event=$mi_help.add_click             
$help_event.Invoke({Get-SampleHelp})

Kontextmenü

Aus vielen Anwendungen kennen Sie bestimmt die Verwendung von Kontextmenüs (Rechtsklick) um kontextbezogene Aufgaben zu erledigen. Am Beispiel dieser Anwendung möchte ich Ihnen die Verwendung eines Kontextmenüs in PowerShell zeigen.

Damit wir auch gleich etwas sinnvolles tun, habe möchte ich ein Kontextmenü einbauen, mit dem die Eigenschaften eines ausgewählten Prozesses angezeigt bzw. der Prozess beendet werden kann.

image

Dazu müssen Sie das geforderte Kontextmenü zuerst in die XAML-Struktur einfügen. Ich habe mir dazu das DataGrid folgendermaßen angepasst:

<DataGrid Name="DG1">

<DataGrid.ContextMenu>

<ContextMenu x:Name = ‚DataGridMenu‘>

<MenuItem x:Name = ‚_Kill‘ Header = ‚Kill’/>

<MenuItem x:Name = ‚_Properties‘ Header = ‚Properties’/>

</ContextMenu>

</DataGrid.ContextMenu>

</DataGrid>

Klicken Sie nun mit der rechten Maustaste innerhalb des Datagrids, wird Ihnen das Kontextmenü entsprechend der vorhergehenden Abbildung angezeigt. Soll nach der Auswahl eines Menüpunktes auch eine Aktion ausgelöst werden, so müssen Sie diese ebenfalls wieder implementieren –> hier am Beispiel des “Kill”-Menüpunktes:

$mi_Kill=$dg.FindName("Kill")            
$mi_Kill.Add_Click({             
    if ([System.Windows.MessageBox]::Show("folgende Prozesse:`n  $(($dg.SelectedItems | ForEach-Object {$psItem.ProcessName}) -join ""`n   "")",             
        "Prozesse beenden" ,            
        [System.Windows.MessageBoxButton]::YesNo,             
        [System.Windows.MessageBoxImage]::Information,             
        [System.Windows.MessageBoxResult]::Yes) -eq [System.Windows.MessageBoxResult]::Yes) {            
        $dg.SelectedItems | ForEach-Object {             
            (Get-Process -Name $PSItem.Name).Kill()            
        }            
    }            
})

Um auch mit diesem Ereignis einige Lösungen mit PowerShell zu zeigen, habe ich etwas mehr Logik in dieses Ereignis gepackt. Hier verwende ich die MessageBox-Klasse aus dem System.Windows-Namespace

Tastenkombinationen

Das Zuweisen von Tastenkombinationen in einer XAML-Struktur ist mit PowerShell leider nicht ganz so einfach, wie in anderen Programmiersprachen. Sie können die Tastenkombinationen zwar beispielsweise mit InputGestureText für einen Menüpunkt definieren, jedoch werden für die Tastenkombinationen erst dann Aktionen ausgelöst, wenn Sie diese auch über entsprechende Events auch definiert habe. Im Folgenden Beispiel habe ich drei verschiedene Tastenkombinationen über die STRG+Buchstaben[O,T,P] aktiviert.

$Form.Add_KeyDown({             
    $key = $_.Key             
    If ([System.Windows.Input.Keyboard]::IsKeyDown("RightCtrl") -OR [System.Windows.Input.Keyboard]::IsKeyDown("LeftCtrl")) {             
        Switch ($Key) {             
            "O" {                             
                    $OptionForm=[Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xamlOptions))            
                    $OptionForm.ShowDialog() | Out-Null                          
            }             
            "T" {             
                    $dg.SelectedItems | ForEach-Object {             
                                    
                    }             
                }             
            "P" {             
                    $printDlg = New-Object System.Windows.Controls.PrintDialog            
                    if ($printDlg.ShowDialog() -eq $true) {             
                    }             
                }            
                         
            Default {$Null}             
        }             
    }             
})

Zusammenfassung

Sicherlich gibt es zu diesem Thema noch sehr viel zu erzählen und Sie werden bei der Programmierung bestimmt noch über viele Hürden stolpern. Doch ich hoffe, ich konnte Ihnen mit diesem “kurzen” Blogeintrag an einem einfachen Beispiel zeigen, wie einfach es doch ist, eine WPF-Anwendung mit PowerShell zu erstellen und wie sie auf bestimmte Ereignisse wie z. B. das Klicken auf einen Menüpunkt reagiert. Die gesamte Beispielanwendung (ohne die XAML-Struktur) ist aktuell nicht einmal 100 Programmzeilen lang und beinhaltet schon viele Funktionalität.

Ich würde mich sehr freuen, wenn ich Ihnen damit weiterhelfen konnte. Sollten nach Fragen offen geblieben sein, dürfen Sie mich auch gerne direkt kontaktieren.

Beispiel-Code

Nachdem die Lösung wächst und wächst, habe ich mich entschlossen, nicht mehr das gesamte PowerShell-Skript in den Blog einzufügen, sondern die Datei als Anhang PowerShell Script (Sample) beizufügen. Da der Download von PowerShell oft durch entsprechende Sicherheitsmaßnahmen verhindert wird, habe ich die Datei als DOC-Datei gespeichert. Sie sollten die Erweiterung dieser Datei nach dem Download in PS1 umbenennen.

Advertisements
Über

Die IT-Welt wird immer komplexer und zwischen den einzelnen Komponenten gibt es immer mehr Abhängigkeiten. Nachdem ich durch meine tägliche Arbeit immer wieder vor der Herausforderung stehe, komplexe Probleme zu lösen, möchte ich diese Seite dafür verwenden, Euch den einen oder anderen Tipp zu geben, wenn Ihr vor ähnlichen Aufgabenstellungen steht.

Veröffentlicht in PowerShell, WPF

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: