derhoeppi
Goto Top

PowerShell GUI öffnen mit ShowDialog()

Hallo,

ich habe eine GUI für ein PowerShell Skript. Diesen wird via ShowDialog() geöffnet. In der öffnenden Oberfläche mit WPF Elementen habe ich einen Button. Dieser Button ruft über ShowDialog() ein weiteres Fenster auf. Das ganze funktioniert leider nur einmal zur Laufzeit des ganzen Skriptes. Wenn ich den Button zum öffnen des zweiten Fensters erneut betätige, erhalte ich die Meldung das ShowDialog() ohne Argumente aufgerufen wird.
Fehlermneldung:
Ausnahme beim Aufrufen von "ShowDialog" mit 0 Argument(en): "Visibility kann nicht festgelegt oder Show, ShowDialog oder WindowInteropHelper.EnsureHandle können nicht aufgerufen werden, nachdem ein Window geschlossen wurde."

Die Meldung hört sich logisch an, aber wie kann ich das zweite Fenster mehrmals öffnen. Ich möchte im übrigen von dem zweiten Fenster Daten ins erste Fenster schreiben. Dazu hätte ich als zweite Frage, wie ich das zweite Fenster OnTop öffnen kann, so dass es im Vordergrund ist?

Gruß
derhoeppi

Content-Key: 285213

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

Printed on: April 24, 2024 at 22:04 o'clock

Member: colinardo
Solution colinardo Oct 11, 2015, updated at Dec 30, 2015 at 19:33:04 (UTC)
Goto Top
Guten Morgen derhoeppi,
also zunächst mal zu den Grundlagen von Fenstern zur Laufzeit:
Zunächst erzeugst du ja mit diesem Code ein Fenster-Objekt:
$window = [Windows.Markup.XamlReader]::Parse($xaml)
Wenn du dieses jedoch entweder über deinen Code (z.B. über die Close()-Methode) oder über das X des Fensters wieder schließt, werden die Ressourcen des Fensters wieder freigegeben, also das Objekt zerstört. Deswegen bekommst du bei einem erneuten Aufruf deines zweiten Fensters die Meldung das es nicht aufgerufen werden kann, weil es eben schon zerstört worden ist. D.h. also im Endeffekt das du das Fenster mit obigen Code bei jedem neuen Aufruf erneut damit erzeugen musst. Die Alternative dazu ist das du statt das Fenster zu schließen es nur versteckst über die Hide() Methode des Fensters, dann müsstest du aber auch das Close-Event des Fensters abfangen weil der User ja auf das X klicken könnte.

Thema Nr. 2 ist das Owner-Prinzip von Fenstern, ein Dialog der zu deinem ersten Fenster gehört solltest du auch zu einem Child deines ersten Fensters machen damit Windows weiß das sie zusammengehören. Ist das gemacht erscheint das zweite Fenster auch automatisch im Vordergrund des ersten. Man kann ein Fenster auch TopMost machen, dann ist es aber wirklich über allen anderen Fenstern nicht nur über dem deiner eigenen Applikation! Für das setzen eines Fensters als TopMost setzt du die Eigenschaft Topmost="True" im XAML-Code des zweiten Fensters.
In meinem Beispiel-Code unten setzte ich zusätzlich die Eigenschaft WindowStartupLocation="CenterOwner" im XAML-Code damit das zweite Fenster zentriert zum ersten aufgerufen wird. Zur Erinnerung: Damit das funktioniert musst die Eigenschaft Owner des zweiten Fensters auf das des ersten gesetzt werden.

Hier ein komplettes Beispiel, mit Verändern von Eigenschaften im ersten Fenster aus dem Zweiten heraus:

Add-Type -AssemblyName Presentationframework

# FensterCode des Hauptfensters
[string]$win1 = @"  
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Window" Title="ListView Demo" WindowStartupLocation = "CenterScreen" ResizeMode="NoResize" ShowInTaskbar = "True" Width="448" Height="262">  
    <Grid Name="Grid1">  
        <ListView x:Name="LV_User" HorizontalAlignment="Left" Height="199" Margin="10,10,10,10" VerticalAlignment="Top" Width="253" ItemsSource="{Binding}">  
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Vorname" DisplayMemberBinding="{Binding }" />  
                    <GridViewColumn Header="Nachname" DisplayMemberBinding="{Binding [1]}"/>  
                    <GridViewColumn Header="Loginname" DisplayMemberBinding="{Binding [2]}" />  
                </GridView>
            </ListView.View>
        </ListView>
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,12,0,0" Name="txtVorname" VerticalAlignment="Top" Width="120" Text="Max" />  
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,41,0,0" Name="txtNachname" VerticalAlignment="Top" Width="120" Text="Mustermann" />  
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,70,0,0" Name="txtLoginname" VerticalAlignment="Top" Width="120" Text="maxmustermann" />  
        <Button Content="Add" Height="23" HorizontalAlignment="Left" Margin="322,99,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />  
        <Button Content="Remove" Height="23" HorizontalAlignment="Left" Margin="322,130,0,0" Name="Button2" VerticalAlignment="Top" Width="75" />  
        <Button Content="OpenWindow" Height="23" HorizontalAlignment="Left" Margin="322,161,0,0" Name="Button3" VerticalAlignment="Top" Width="75" />  

    </Grid>
    <Window.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">  
            <GradientStop Color="#FFC1C1C1" Offset="0" />  
            <GradientStop Color="White" Offset="1" />  
        </LinearGradientBrush>
    </Window.Background>
</Window>

"@  
# FensterCode des zweiten Fensters
[string]$win2 = @"  
<Window x:Name="SecondWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        Title="SecondWindow" Height="156.871" Width="256.395" WindowStartupLocation="CenterOwner">  
    <Grid>
        <Button x:Name="btnSetText" Content="Add Item" HorizontalAlignment="Left" Margin="87,49,0,0" VerticalAlignment="Top" Width="75"/>  
    </Grid>
</Window>
"@  
# Markup des Hauptfensters laden 
$window = [Windows.Markup.XamlReader]::Parse($win1)

$dt = new-Object System.Data.DataTable
[void]$dt.Columns.Add("Vorname")  
[void]$dt.Columns.Add("Nachname")  
[void]$dt.Columns.Add("Loginname")  

$lv = $window.FindName('LV_User')  
$btnAdd = $window.FindName('Button1')  
$btnRemove = $window.FindName('Button2')  
$btnOpenWindow = $window.FindName('Button3')  
$txtVorname = $window.FindName('txtVorname')  
$txtNachname = $window.FindName('txtNachname')  
$txtLoginname = $window.FindName('txtLoginname')  

$lv.ItemsSource = $dt.DefaultView

$btnAdd.add_Click({
    $dt.Rows.Add(@($txtVorname.Text,$txtNachname.Text,$txtLoginname.Text))
})
$btnRemove.add_Click({
    if ($lv.SelectedIndex -ne -1){
        $dt.Rows.RemoveAt($lv.SelectedIndex)
    }
})

# Button der das zweite Fenster aufruft
$btnOpenWindow.add_Click({
    # erzeugen des Fenster-Objekts
    $window2 =[Windows.Markup.XamlReader]::Parse($win2)
    # Besitzer des zweiten auf das ersten festlegen
    $window2.Owner = $window
    # Button im Fenster referenzieren
    $btnSetText = $window2.FindName("btnSetText")  
    # Button-Click AKtion im zweiten Fenster festlegen
    $btnSetText.Add_Click({
         $dt.Rows.Add(@("Test","Test2","Test3"))  
    })    
    # Fenster 2 (Child-Window) aufrufen
    $window2.ShowDialog()
})

$async = $window.Dispatcher.InvokeAsync({$window.ShowDialog() | Out-Null})
$async.Wait() | Out-Null
Der Vollständigkeit halber noch meine oben bereits erwähnte Variante, welche statt das Fenster zu schließen es stattdessen über die Hide()-Methode versteckt.
Beachte die Platzierung des Codes zur Erzeugung des Fensters, und die Verwendung des Closing-Event des zweiten Fensters $window2.add_Closing().
Noch zur Info: Da das zweite Fenster als Child des ersten gesetzt wurde wird es bei dieser Methode beim Schließen des Haupt(Parent)-Fensters automatisch ebenfalls zerstört und der Speicher freigegeben.
back-to-topVariante 2 mit Hide()
Add-Type -AssemblyName Presentationframework
[string]$win1 = @"  
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Window" Title="ListView Demo" WindowStartupLocation = "CenterScreen" ResizeMode="NoResize" ShowInTaskbar = "True" Width="448" Height="262">  
    <Grid Name="Grid1">  
        <ListView x:Name="LV_User" HorizontalAlignment="Left" Height="199" Margin="10,10,10,10" VerticalAlignment="Top" Width="253" ItemsSource="{Binding}">  
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Vorname" DisplayMemberBinding="{Binding }" />  
                    <GridViewColumn Header="Nachname" DisplayMemberBinding="{Binding [1]}"/>  
                    <GridViewColumn Header="Loginname" DisplayMemberBinding="{Binding [2]}" />  
                </GridView>
            </ListView.View>
        </ListView>
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,12,0,0" Name="txtVorname" VerticalAlignment="Top" Width="120" Text="Max" />  
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,41,0,0" Name="txtNachname" VerticalAlignment="Top" Width="120" Text="Mustermann" />  
        <TextBox Height="23" HorizontalAlignment="Left" Margin="277,70,0,0" Name="txtLoginname" VerticalAlignment="Top" Width="120" Text="maxmustermann" />  
        <Button Content="Add" Height="23" HorizontalAlignment="Left" Margin="322,99,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />  
        <Button Content="Remove" Height="23" HorizontalAlignment="Left" Margin="322,130,0,0" Name="Button2" VerticalAlignment="Top" Width="75" />  
        <Button Content="OpenWindow" Height="23" HorizontalAlignment="Left" Margin="322,161,0,0" Name="Button3" VerticalAlignment="Top" Width="80" />  

    </Grid>
    <Window.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">  
            <GradientStop Color="#FFC1C1C1" Offset="0" />  
            <GradientStop Color="White" Offset="1" />  
        </LinearGradientBrush>
    </Window.Background>
</Window>

"@  
[string]$win2 = @"  
<Window x:Name="SecondWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        Title="SecondWindow" Height="156.871" Width="256.395" WindowStartupLocation="CenterOwner">  
    <Grid>
        <Button x:Name="btnSetText" Content="Add Item" HorizontalAlignment="Left" Margin="87,49,0,0" VerticalAlignment="Top" Width="75"/>  
    </Grid>
</Window>
"@  
# Markup laden 
$window = [Windows.Markup.XamlReader]::Parse($win1)
$window2 =[Windows.Markup.XamlReader]::Parse($win2)

$dt = new-Object System.Data.DataTable
[void]$dt.Columns.Add("Vorname")  
[void]$dt.Columns.Add("Nachname")  
[void]$dt.Columns.Add("Loginname")  

$lv = $window.FindName('LV_User')  
$btnAdd = $window.FindName('Button1')  
$btnRemove = $window.FindName('Button2')  
$btnOpenWindow = $window.FindName('Button3')  
$txtVorname = $window.FindName('txtVorname')  
$txtNachname = $window.FindName('txtNachname')  
$txtLoginname = $window.FindName('txtLoginname')  

$lv.ItemsSource = $dt.DefaultView

$btnAdd.add_Click({
    $dt.Rows.Add(@($txtVorname.Text,$txtNachname.Text,$txtLoginname.Text))
})
$btnRemove.add_Click({
    if ($lv.SelectedIndex -ne -1){
        $dt.Rows.RemoveAt($lv.SelectedIndex)
    }
})

$btnOpenWindow.add_Click({
    $window2.Owner = $window
    $window2.ShowDialog()
})

$btnSetText = $window2.FindName("btnSetText")  
$window2.add_Closing({
    $window2.Hide()
    $_.Cancel = $true
})

$btnSetText.Add_Click({
        $dt.Rows.Add(@("Test","Test2","Test3"))  
})    

$async = $window.Dispatcher.InvokeAsync({$window.ShowDialog() | Out-Null})
$async.Wait() | Out-Null
Hoffe das löst jetzt einen kleinen AHA-Effekt bei dir aus face-smile

Grüße und schönes Restwochenende
Uwe

Falls der Beitrag gefällt, seid so nett und unterstützt mich durch eine kleine Spende / If you like my contribution please support me and donate
Member: derhoeppi
derhoeppi Oct 11, 2015 at 13:07:11 (UTC)
Goto Top
Hallo Uwe,

vielen Dank für deine ausführliche Erklärung. Ich habe es verstanden und inzwischen in meinem Skript umgesetzt. An dieser Stelle würde mich noch ein Punkt interessieren. Mein Skript ruft per DotSourcing ein anderes PowerShell Skript auf - ebenfalls mit GUI (WPF). Kann ich dieser Form auch einen Owner mitgeben und ebenfalls sicherstellen dass dieses OnTop geöffnet wird. Bisher öffnet es mir zwar die GUI, aber wenn ich es über die Powershell ISE mache, erscheint die GUI meist im Hintergrund.

Gruß
derhoeppi
Member: colinardo
colinardo Oct 12, 2015 updated at 08:01:27 (UTC)
Goto Top
Zitat von @derhoeppi:
Mein Skript ruft per DotSourcing ein anderes PowerShell Skript auf - ebenfalls mit GUI (WPF). Kann ich dieser Form auch einen Owner mitgeben und ebenfalls sicherstellen dass dieses OnTop geöffnet wird.
Ja ,nach gleicher Vorgehensweise wie oben beschrieben. Nur sicherstellen das die Variablennamen im zweiten Skript anders lauten als im ersten, denn wie Dot-Sourcing schon sagt du bringst die Variablen des zweiten Skripts in die Session des ersten, deswegen doppelte VariablenNamen unbedingt vermeiden !!
Bisher öffnet es mir zwar die GUI, aber wenn ich es über die Powershell ISE mache, erscheint die GUI meist im Hintergrund.
ISE-Test kannst du mit GUI-Tests vergessen die hat in dieser Hinsicht einige Bugs ...

Grüße Uwe
Member: colinardo
colinardo Dec 30, 2015 at 19:05:32 (UTC)
Goto Top
Wenns das dann war, den Beitrag bitte noch auf gelöst setzen. Merci.

Guten Rutsch.