Private/Wissen/C_Advance/C13a_GUI.ps1
<#
# GUI GUI (Graphical User Interface) zu deutsch ein Windows-Fenster als Benutzerschnittstelle. Mittels .NET können verschiedene GUI's programmiert werden. Hierzu gehören u.a. Web-Interfaces via .ASPX, WinForm oder WPF um moderne Fenster für Windows-System zu erzeugen. PowerShell hat vollen Zugriff auf .NET und so können auch Windows-Fenster aus PowerShell heraus erzeugt werde. Rechnet Euros in Nicht-Euro-Währungen gem. den Vorgaben der EZB. - **Hashtags** CLR WPF WinForm GUI Window Fenster - **Version** 2020.6.12 #> # READ Eine Variante um mit PowerShell eine GUI zu erstellen bietet WPF und XAML. Das Layout kann dabei ähnlich einer HTML-Datei erstellt werden. Das kostenlose [Visual Studio Express](https://visualstudio.microsoft.com/de/vs/express/) bietet zudem die Möglichkeit, das Layout einfach in einem grafischen Editor zu erstellen. Der eigentliche Programmcode wird mit PowerShell Script umgesetzt. Diese Beispiel demonstriert eine GUI für das Cmdlet AKPT\Get-EuroExchange. Dieses Skript ist autark lauffähig da der Code für Get-EuroExchange unter 5. implementiert ist. Daher ist ein installieren und importieren des Module AKPT nicht nötig. #region 1. Benötigte Modules importieren # ! Hier werden Module importiert die nicht standardmäßig mit der PowerShell installiert werden um Dritten zu zeigen welche Module evtl. zuerst installiert werden müssten. using module Microsoft.PowerShell.Management using module Microsoft.PowerShell.Utility #endregion #region 2. Benötigte Namespaces bekannt machen # ! Für eine übersichtlichere Schreibweise im weiteren Verlauf können die benötigten Namespaces bekannt gemacht werde. using namespace System.Management.Automation using namespace System.PresentationFramework using namespace System.Windows.Markup #endregion #region 3. Laufzeitverhalten des Scripts festlegen # ! Im Fehlerfall sollte das Script genau an dieser Stelle stoppen und um u.a. Tippfehler bei Variablennamen zu vermeiden die Zuweisung vor der ersten Verwendung eingeschaltet. $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = [ActionPreference]::Stop Set-StrictMode -Version 'Latest' #endregion #region 4. Benötigte Assemblies laden # ! Assemblies die nicht standardmäßig geladen werden müssen manuell geladen werden. Add-Type -AssemblyName 'PresentationFramework' # Für WPF #endregion #region 5. Benötigte private Funktionen / Cmdlets definieren # ! Für dieses Cmdlet Get-EuroExchange soll im weiteren Verlauf eine GUI erstellt werden. function Get-EuroExchange { <# .SYNOPSIS EZB-Währungsrechner .DESCRIPTION Rechnet Euros in Nicht-Euro-Währungen gem. den Vorgaben der EZB. .INPUTS System.String mit Währungssymbolen PSCustomObject mit folgenden Properties Currency <string> und Euros <decimal> .OUTPUTS PSCustomObject .PARAMETER Currency Währungssymbol der EZB. .PARAMETER Euros Euro-Betrag der umgerechnet werden soll. .PARAMETER ListCurrency Eine Übersicht aller möglichen Währungssymbole. .EXAMPLE Get-EuroExchange -Currency USD Ermittelt den Wechselkurs für US-Dollar .EXAMPLE Get-EuroExchange -Currency USD -Euros 100 .EXAMPLE Get-EuroExchange -ListCurrency .EXAMPLE "USD", "RUB", "AUD" | Get-EuroExchange .EXAMPLE "USD", "RUB", "AUD" | Get-EuroExchange -Euros 100 .EXAMPLE "USD,10", "RUB,100", "AUD,1000" | ConvertFrom-Csv -Header Currency, Euros | Get-EuroExchange #> [CmdletBinding(DefaultParameterSetName = 'Calculate')] param ( [Parameter(ParameterSetName = "Calculate", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('AUD', 'BGN', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK', 'GBP', 'HKD', 'HRK', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'JPY', 'KRW', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'USD', 'ZAR')] [Alias("Währung")] [string]$Currency, [Parameter(ParameterSetName = "Calculate", ValueFromPipelineByPropertyName = $true)] [ValidateRange(0.0001, 2147483647)] [Alias("Euronen")] [decimal]$Euros = 1, [Parameter(ParameterSetName = "Overview", Mandatory=$true)] [switch]$ListCurrency ) begin { [datetime]$StartTime = Get-Date #region Update local cache and read it # ! Build Cachefile: [string]$EuroExchangeCacheFile = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'EcbEuroExchangeCache.xml' # ! Exist Cachefile read timestamp: [timespan]$ECBCacheDifferenceSpan = New-TimeSpan -Hours 999 if ((Test-Path -Path $EuroExchangeCacheFile)) { 'ECB-EuroExchange-Cache-File found!' | Write-Verbose [xml]$EuroExchangeContent = Get-Content -Path $EuroExchangeCacheFile [datetime]$EuroExchangeTime = $EuroExchangeContent.Envelope.Cube.Cube | Select-Object -ExpandProperty 'time' [timespan]$ECBCacheDifferenceSpan = (Get-Date) - $EuroExchangeTime } # ! Is Cache-Difference-TimeSpan greater 39h than update from ECB: if($ECBCacheDifferenceSpan.TotalHours -ge 39) { 'The ECB-EuroExchange-Cache-File is updated because the file was not found or is older than 39 hours.' | Write-Verbose Invoke-WebRequest -Uri "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml" | Select-Object -ExpandProperty Content | Set-Content -Path $EuroExchangeCacheFile -Force } # ! Read Cachefile for the next stapes: [xml]$EuroExchangeContent = Get-Content -Path $EuroExchangeCacheFile $EuroExchangeCubes = $EuroExchangeContent.Envelope.Cube.Cube.Cube "ECB-EuroExchange-Cache-File from $($EuroExchangeContent.Envelope.Cube.Cube | Select-Object -ExpandProperty 'time') read it." | Write-Verbose #endregion switch ($PSCmdlet.ParameterSetName) { 'Overview' { 'Get-EuroExchange works in Overview-Mode.' | Write-Verbose $EuroExchangeCubes | ForEach-Object -Process { [PSCustomObject]@{ Currency = $_.currency } } | Sort-Object -Property 'Currency' } 'Calculate' { 'Get-EuroExchange works in Calculate-Mode.' | Write-Verbose } } } process { if($PSCmdlet.ParameterSetName -eq 'Calculate') { [decimal]$CurrencyRate = $EuroExchangeCubes | Where-Object -Property 'currency' -EQ -Value $Currency | Select-Object -ExpandProperty 'rate' [PSCustomObject]@{ Currency = $Currency.ToUpper() Rate = $CurrencyRate Euros = $Euros SumCurrency = $CurrencyRate * $Euros } } } end { [timespan]$Duration = (Get-Date) - $StartTime "Done in $($Duration.TotalMilliseconds) ms!" | Write-Verbose } } #endregion #region 6. Ereignis-Routinen # ! Vor dem öffnen des Fensters bzw. vor dem ersten Auslösen eines Steuerelement-Events müssen dessen Routinen deklariert werden. function EuroRateCalculate([string]$Currency, [double]$Euros) { try { $Result = Get-EuroExchange -Currency $Currency -Euros $Euros [double]$Rate = $Result.Rate [double]$SumCurrency = $Result.SumCurrency } catch { [double]$Rate = [double]::NaN [double]$SumCurrency = [double]::NaN } $Script:My.RateControl.Text = "{0:#,##0.0000} {1}" -f $Rate , $Currency $Script:My.SummeControl.Text = "{0:#,##0.0000} {1}" -f $SumCurrency, $Currency } #endregion #region 7. WPF Window erzeugen # ! Nun gilt das WPF-Window zu materialisieren. $Script:My = [HashTable]::Synchronized(@{}) # Die XAML-WPF-Window-Struktur läßt sich am einfachsten mittels Visual Studio Express im Designer erzeugen, siehe dazu auch C13b_VisualStudio_Handout.png. $Script:My.WindowXaml = @' <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="€ CALCULATOR" Width="336" Height="220" FontFamily="Consolas" FontSize="14" WindowStartupLocation="CenterScreen"> <Viewbox Margin="15" Stretch="Uniform" StretchDirection="Both"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="AUTO" /> <RowDefinition MinHeight="10" /> <RowDefinition MinHeight="40" /> <RowDefinition MinHeight="40" /> <RowDefinition MinHeight="40" /> </Grid.RowDefinitions> <ComboBox x:Name="WährungenControl" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" HorizontalContentAlignment="Center" FontSize="20" SelectedValue="USD" /> <TextBlock Grid.Row="0" Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"> Währungssymbol </TextBlock> <TextBox x:Name="RateControl" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" FontWeight="Bold" IsReadOnly="True" TextAlignment="Right" /> <TextBlock Grid.Row="2" Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center" FontWeight="Bold"> Rate </TextBlock> <TextBox x:Name="EurosControl" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" TextAlignment="Right" /> <TextBlock Grid.Row="3" Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"> € (EUR) </TextBlock> <TextBox x:Name="SummeControl" Grid.Row="4" Grid.Column="0" MinWidth="195" VerticalAlignment="Center" FontWeight="Bold" IsReadOnly="True" TextAlignment="Right" /> <TextBlock Grid.Row="4" Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center" FontWeight="Bold"> SUMME = Rate * € </TextBlock> </Grid> </Viewbox> </Window> '@ # Das Window-Objekt wird über die XAML-Struktur erstellt: $Script:My.Window = [XamlReader]::Parse($Script:My.WindowXaml) # Aus dem Window-Objekt werden die benötigten Steuerelemente über das Attribute X:Name lokalisiert und referenziert: $Script:My.WährungenControl = $Script:My.Window.FindName('WährungenControl') $Script:My.RateControl = $Script:My.Window.FindName('RateControl') $Script:My.EurosControl = $Script:My.Window.FindName('EurosControl') $Script:My.SummeControl = $Script:My.Window.FindName('SummeControl') # Die ComboBox erhält ihre Werte die vom Benutzer ausgewählt werden können: Get-EuroExchange -ListCurrency | ForEach-Object -Process { $Script:My.WährungenControl.Items.Add($_.Currency) | Out-Null } # Die ComboBox stößt die Berechnung der Ausgabe (Rate, Summe) an, wenn die Auswahl wechselt: $Script:My.WährungenControl.Add_SelectionChanged({ EuroRateCalculate -Currency $Script:My.WährungenControl.SelectedItem -Euros $Script:My.EurosControl.Text }) # Das erste Element der ComboBox wird ausgewählt: $Script:My.WährungenControl.SelectedIndex = 0 # Die TextBox stößt die Berechnung der Ausgabe (Rate, Summe) an, wenn der Euro-Text sich ändert: $Script:My.EurosControl.Add_TextChanged({ EuroRateCalculate -Currency $Script:My.WährungenControl.SelectedItem -Euros $Script:My.EurosControl.Text }) # In der TextBox wird der Default-Wert auf 1 Euro gesetzt: $Script:My.EurosControl.Text = "1" # Das WPF-Window wird angezeigt: $Script:My.Window.Topmost = $true $Script:My.Window.ShowDialog() | Out-Null #endregion $ErrorActionPreference = $BackupErrorActionPreference # TIPP Egal was Sie vorhaben bleibt in grunde der Ablauf gleich. Bedenken Sie jedoch das das Wissen über WPF und OOP nicht unerheblich ist und einen gro0en Teil ausmacht. |