none
Animierten Ladebalken mit WindowsForms darstellen bzw. schließen RRS feed

  • Frage

  • Hallo zusammen,

    ich sitze momentan an einem Code, der eine (eventuell) auftretende Wartezeit in einem Script überbrücken soll. Je nach Client hat das Script bis zu einigen Sekunden Verzögerung, teilweise ist es augenblicklich ausgeführt.

    Dazu wollte ich während der Wartezeit eine GUI anzeigen lassen, in der ein Ladebalken zu sehen ist. Write-Progress in der Konsole finde ich aber nicht besonders hübsch und so habe ich mich für ProgressBar von WindowsForms entschieden. Ich habe allerdings keinen Endzeitpunkt o.ä. festgelegt, da ich nicht weiß, wie das in meinem Fall zu realisieren wäre und ich meine, dass es anders besser ginge. Deswegen läuft der Ladebalken quasi ohne Ende immer wieder durch, bis er vom Script geschlossen wird. Leider bekomme ich die Ladebalken-GUI nicht geschlossen, und das Ganze sieht in der ISE mal wieder völlig anders aus als in der Konsolenausführung.

    Zum Hauptskript: Das macht im Grunde nichts anderes, als über die WMI des Rechners alle Netzwerkkarten auszulesen und alle Realtek-Karten in eine Variable zu speichern. Über diese läuft dann eine Schleife, die die Properties AdapterType und MACAddress von jeder Karte überprüft und die Netzwerkkarte aktiviert, wenn beide Felder leer sind oder sie deaktivert, wenn beide Felder ausgefüllt sind. Das geschieht über die Enable()- bzw. Disable()-Methode. Über die ReturnValue-Eigenschaft der Methode selber überprüft das Script dann den Erfolg der Ausführung und gibt eine entsprechende Meldung aus.

    Code:

    if ($LANs){clv LANs}
    if ($Ergebnis){clv Ergebnis}
    
    $LANs = Get-WmiObject -Class Win32_NetworkAdapter | Where {$_.Name -like "*Realtek*"}
    ForEach ($LAN in $LANs)
    {
        if ($LAN -like "")
            {
                # Fehler, keine Netzwerkkarte gefunden
            }
        else
            {
                if ($LAN.MACAddress -like "" -and $LAN.AdapterType -like "")
                {
                    $LANac = $LAN.Enable()
                    $LANst = $LANac.ReturnValue
                    if ($LANst -eq "0")
                        {
                            # erfolgreich aktiviert
                        }
                    else
                        {
                            # Fehler, nicht aktiviert
                        }
                }
                else
                {
                    $LANac = $LAN.Disable()
                    $LANst = $LANac.ReturnValue
                    if ($LANst -eq "0")
                        {
                            # erfolgreich deaktiviert
                        }
                    else
                        {
                            # Fehler, nicht deaktiviert
                        }
                }
                # Endmeldung
            }
    }

    Bis die Endmeldung erscheint, soll eine GUI mit Ladebalken her. Ich habe mich für das endlose Durchlaufen entschieden, weil ich es für einfacher hielt, die GUI per Methode kurz vor oder nach Abschluss zu schließen statt einen Endzeitpunkt zu definieren. Falls das einfacher/sicherer geht, immer her damit! Deswegen der BorderStyle "Marquee", dazu sollen noch sowohl die drei Buttons oben in der Ecke deaktiviert sein; das Fenster soll mittig zu sehen sein und sich in der Größe nicht verändern lassen.

    Code:

    [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
     [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
        $LoadForm = New-Object System.Windows.Forms.Form
        $LoadForm.Text = 'Loading'
        $LoadForm.Width = 350
        $LoadForm.Height = 144
        $LoadForm.MaximizeBox = $False
        $LoadForm.MinimizeBox = $False
        $LoadForm.ControlBox = $False
        $LoadForm.ShowIcon = $False
        $LoadForm.StartPosition = 1
        $LoadForm.Visible = $False
    
        $InText = New-Object System.Windows.Forms.Label
        $InText.Text = 'Loading...'
        $InText.Location = '18,26'
        $InText.Size = New-Object System.Drawing.Size(330,18)
    
        $progressBar1 = New-Object System.Windows.Forms.ProgressBar
        $ProgressBar1.Name = 'LoadingBar'
        $ProgressBar1.Style = "Marquee"
        $ProgressBar1.Location = "17,61"
        $ProgressBar1.Size = "300,18"
        $ProgressBar1.MarqueeAnimationSpeed = 20
    
        $LoadForm.Controls.Add($InText)
        $LoadForm.Controls.Add($ProgressBar1)
        
        $LoadForm.ShowDialog() | Out-Null

    Problem: Das Fenster lässt sich nicht schließen. Ich habe es mit ShowDialog, Show und Visible = $True als Aufruf versucht.

    ShowDialog(): Ich habe gelesen, dass sich so ein modales Dialog-Feld nur durch das Schließen-Symbol schließen lässt oder durch Events/EventHandler. Wenn ja, wie mache ich so etwas? ShowDialog wartet doch auf Eingaben, also stoppt das Skript. Ich müsste also vorher einen Timer einbauen. Ich kenne aber die Zeitspanne nicht... Außerdem müsste ich dann testen, ob es in der Konsole, mit der es später ausgeführt wird genauso aussieht.

    Show(): Wenn ich die GUI per Show() aufrufe, läuft das Skript - soweit ich weiß - im Hintergrund weiter. Das hat aber zur Folge, dass selbst in der ISE nur das Fenster zu sehen ist, im Ladebalken bewegt sich leider nichts. Auch der Mauszeiger wird dann zum Ladekreis. Kann man den Ladebalken und die Animation alternativ als Job starten, sodass der im Hintergrund läuft und sich so animieren lässt? Als ich es versucht habe, habe ich die Konsolen-Version zu Gesicht bekommen... Und abgesehen von der eckigeren Optik, die nichts neues ist, war der Ladebalken ebenfalls nicht animiert und das Fenster trotz entsprechender Property (s.o.) verkleinerbar und vergrößerbar! Die powershell.exe führt das Ganzen später ja aus, also sollte das auch wie in der ISE nicht veränderbar sein.

    .Visible = $True : Das Fenster wird geöffnet und kann nur durch .Visible = $False wieder geschlossen werden. Allerdings besteht hier dasselbe Problem wie bei Show(), kein animierter Balken etc.

    So langsam bin ich mit meinem Latein am Ende. Bin für jede Idee dankbar.

    Scriptex

    • Bearbeitet Scriptex Freitag, 25. Januar 2013 18:58
    Freitag, 25. Januar 2013 18:49

Antworten

  • Wenn du abends an der Kinokasse in einer Schlange stehst. Dann verstehst du wie PowerShell arbeitet ;-)
    PowerShell kann immer nur einen Kunden nach dem anderen bearbeiten (Seriell oder Synchrone Arbeit).
    Die einzige Möglichkeit schneller zu werden oder zwei Dinge gleichzeitig zu tun ist es eine Zweite Kasse aufzumachen (parallel oder asynchron zu arbeiten).

    Damit der Computer (die PowerShell) 2 Dinge gleichzeitig machen kann, muss man entweder einen zweiten Prozess aufmachen (Start-Job) oder man erzeugt innerhalb eines vorhandenen Prozesses einen zweiten Handlungsfaden (Thread). Das Dumme ist nur das die Kommunikation von einem Prozess oder Thread zu einem anderen sehr schwierig ist.
    Siehe: http://de.wikipedia.org/wiki/Multithreading
    Siehe: http://openbook.galileocomputing.de/visual_csharp/visual_csharp_09_001.htm

    Hier ist meine Lösung:
    Siehe: http://cjoprey.wordpress.com/archived/asynchronicity-in-powershell/

    # Funktion um die Form in einem neuen Thread zu erstellen
    Function EndlesProgressForm {
    
    [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
    [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
        $ProgressForm = New-Object System.Windows.Forms.Form
        $ProgressForm.Text = 'Loading'
        $ProgressForm.Width = 350
        $ProgressForm.Height = 144
        $ProgressForm.MaximizeBox = $False
        $ProgressForm.MinimizeBox = $False
        $ProgressForm.ControlBox = $False
        $ProgressForm.ShowIcon = $False
        $ProgressForm.StartPosition = 1
        $ProgressForm.Visible = $False
    
        $InText = New-Object System.Windows.Forms.Label
        $InText.Text = 'Loading...'
        $InText.Location = '18,26'
        $InText.Size = New-Object System.Drawing.Size(330,18)
    
        $progressBar1 = New-Object System.Windows.Forms.ProgressBar
        $ProgressBar1.Name = 'LoadingBar'
        $ProgressBar1.Style = "Marquee"
        $ProgressBar1.Location = "17,61"
        $ProgressBar1.Size = "300,18"
        $ProgressBar1.MarqueeAnimationSpeed = 20
    
        $ProgressForm.Controls.Add($InText)
        $ProgressForm.Controls.Add($ProgressBar1)
    
        # Die Form in die Synchronisierte Hashtable schreiben
        # damit die Form ausserhalb dieses Threads verwaltet werden kann    
        $sharedData.Form = $ProgressForm
        
        $ProgressForm.ShowDialog() | Out-Null
            
        }
       
       
    # --------------------------------------------------------
    # BEGIN: Neuen PowerShell Tread erstellen und Form Starten >>>
       
       
       # Eine Synchronisierte Hashtable erstellen.
       # Nur diese können In mehreren Thread gleichzeitigh benutzt werden
       # um Daten zu übergeben
       $sharedData = [HashTable]::Synchronized(@{})
       # In die Hashtable einen Leeren Wert einfügen der die Windows Form enthalten soll
       $sharedData.Form = $Null
       
       # Für den PowerShell Thread einen Arbeitsumgebung erstellen
       $newRunspace = [RunSpaceFactory]::CreateRunspace()
       $newRunspace.ApartmentState = "STA"
       $newRunspace.ThreadOptions = "ReuseThread"
       $newRunspace.Open()
       # die Synchronisierte Hashtable in dem runspace veröffentlichen (einfügen)
       $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
       
       # eine neue PowerShell Pipeline (Thread) innerhalb des neuen Runspaces erzeugen
       $PS = [PowerShell]::Create()
       $PS.Runspace = $newRunspace
       # Funktionsaufruf in die Pipeline schreiben
       $PS.AddScript($Function:EndlesProgressForm)
    
       # Den Neuen PowerShell Thread (pipeline) asynchron (parallel) starten
       # damit die erste PowerShell weiterarbeiten kann   
       $AsyncResult = $PS.BeginInvoke()
    
    # <<< ENDE: Neuen PowerShell Tread erstellen und Form Starten
    # --------------------------------------------------------
     
        
    
    # --------------------------------------------------------
    # BEGIN: Arbeit erledigen >>>
       For ($i = 0 ; $i -lt 5; $I++) {
            "Runde Nr.: $I"
            Start-Sleep 2
       } 
    
    # <<< ENDE: Arbeit ist erledigt   
    # --------------------------------------------------------   
    
    
    
    # --------------------------------------------------------
    # BEGIN: Neuen PowerShell Tread und Form beenden und aufräumen >>>
       
       # Wenn die Form erstellt wurde  ist sie in der Synchronisierte Hashtable
       # Dann können wir das Fenster Schliessen 
       If($sharedData.Form) {
            $sharedData.Form.close()
       }
       
       # Den erstellten PowerShell Thread ordentlich Beenden
       $PS.Endinvoke($AsyncResult)
       # Das Objekt mit der erstellten PowerShell aus dem
       # Speicher entfernen und beenden
       $PS.dispose()
       
    # <<< ENDE: Neuen PowerShell Tread und Form beenden und aufräumen 
    # --------------------------------------------------------     


    Please click “Mark as Answer” if my post answers your question and click “Vote As Helpful” if my Post helps you.
    Bitte markiere hilfreiche Beiträge von mir als “Als Hilfreich bewerten” und Beiträge die deine Frage ganz oder teilweise beantwortet haben als “Als Antwort markieren”.
    My PowerShell Blog http://www.admin-source.info
    [string](0..21|%{[char][int]([int]("{0:d}" -f 0x28)+('755964655967-86965747271757624-8796158066061').substring(($_*2),2))})-replace' '
    German ? Come to German PowerShell Forum!


    Montag, 28. Januar 2013 08:50

Alle Antworten

  • Wenn du abends an der Kinokasse in einer Schlange stehst. Dann verstehst du wie PowerShell arbeitet ;-)
    PowerShell kann immer nur einen Kunden nach dem anderen bearbeiten (Seriell oder Synchrone Arbeit).
    Die einzige Möglichkeit schneller zu werden oder zwei Dinge gleichzeitig zu tun ist es eine Zweite Kasse aufzumachen (parallel oder asynchron zu arbeiten).

    Damit der Computer (die PowerShell) 2 Dinge gleichzeitig machen kann, muss man entweder einen zweiten Prozess aufmachen (Start-Job) oder man erzeugt innerhalb eines vorhandenen Prozesses einen zweiten Handlungsfaden (Thread). Das Dumme ist nur das die Kommunikation von einem Prozess oder Thread zu einem anderen sehr schwierig ist.
    Siehe: http://de.wikipedia.org/wiki/Multithreading
    Siehe: http://openbook.galileocomputing.de/visual_csharp/visual_csharp_09_001.htm

    Hier ist meine Lösung:
    Siehe: http://cjoprey.wordpress.com/archived/asynchronicity-in-powershell/

    # Funktion um die Form in einem neuen Thread zu erstellen
    Function EndlesProgressForm {
    
    [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
    [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
        $ProgressForm = New-Object System.Windows.Forms.Form
        $ProgressForm.Text = 'Loading'
        $ProgressForm.Width = 350
        $ProgressForm.Height = 144
        $ProgressForm.MaximizeBox = $False
        $ProgressForm.MinimizeBox = $False
        $ProgressForm.ControlBox = $False
        $ProgressForm.ShowIcon = $False
        $ProgressForm.StartPosition = 1
        $ProgressForm.Visible = $False
    
        $InText = New-Object System.Windows.Forms.Label
        $InText.Text = 'Loading...'
        $InText.Location = '18,26'
        $InText.Size = New-Object System.Drawing.Size(330,18)
    
        $progressBar1 = New-Object System.Windows.Forms.ProgressBar
        $ProgressBar1.Name = 'LoadingBar'
        $ProgressBar1.Style = "Marquee"
        $ProgressBar1.Location = "17,61"
        $ProgressBar1.Size = "300,18"
        $ProgressBar1.MarqueeAnimationSpeed = 20
    
        $ProgressForm.Controls.Add($InText)
        $ProgressForm.Controls.Add($ProgressBar1)
    
        # Die Form in die Synchronisierte Hashtable schreiben
        # damit die Form ausserhalb dieses Threads verwaltet werden kann    
        $sharedData.Form = $ProgressForm
        
        $ProgressForm.ShowDialog() | Out-Null
            
        }
       
       
    # --------------------------------------------------------
    # BEGIN: Neuen PowerShell Tread erstellen und Form Starten >>>
       
       
       # Eine Synchronisierte Hashtable erstellen.
       # Nur diese können In mehreren Thread gleichzeitigh benutzt werden
       # um Daten zu übergeben
       $sharedData = [HashTable]::Synchronized(@{})
       # In die Hashtable einen Leeren Wert einfügen der die Windows Form enthalten soll
       $sharedData.Form = $Null
       
       # Für den PowerShell Thread einen Arbeitsumgebung erstellen
       $newRunspace = [RunSpaceFactory]::CreateRunspace()
       $newRunspace.ApartmentState = "STA"
       $newRunspace.ThreadOptions = "ReuseThread"
       $newRunspace.Open()
       # die Synchronisierte Hashtable in dem runspace veröffentlichen (einfügen)
       $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
       
       # eine neue PowerShell Pipeline (Thread) innerhalb des neuen Runspaces erzeugen
       $PS = [PowerShell]::Create()
       $PS.Runspace = $newRunspace
       # Funktionsaufruf in die Pipeline schreiben
       $PS.AddScript($Function:EndlesProgressForm)
    
       # Den Neuen PowerShell Thread (pipeline) asynchron (parallel) starten
       # damit die erste PowerShell weiterarbeiten kann   
       $AsyncResult = $PS.BeginInvoke()
    
    # <<< ENDE: Neuen PowerShell Tread erstellen und Form Starten
    # --------------------------------------------------------
     
        
    
    # --------------------------------------------------------
    # BEGIN: Arbeit erledigen >>>
       For ($i = 0 ; $i -lt 5; $I++) {
            "Runde Nr.: $I"
            Start-Sleep 2
       } 
    
    # <<< ENDE: Arbeit ist erledigt   
    # --------------------------------------------------------   
    
    
    
    # --------------------------------------------------------
    # BEGIN: Neuen PowerShell Tread und Form beenden und aufräumen >>>
       
       # Wenn die Form erstellt wurde  ist sie in der Synchronisierte Hashtable
       # Dann können wir das Fenster Schliessen 
       If($sharedData.Form) {
            $sharedData.Form.close()
       }
       
       # Den erstellten PowerShell Thread ordentlich Beenden
       $PS.Endinvoke($AsyncResult)
       # Das Objekt mit der erstellten PowerShell aus dem
       # Speicher entfernen und beenden
       $PS.dispose()
       
    # <<< ENDE: Neuen PowerShell Tread und Form beenden und aufräumen 
    # --------------------------------------------------------     


    Please click “Mark as Answer” if my post answers your question and click “Vote As Helpful” if my Post helps you.
    Bitte markiere hilfreiche Beiträge von mir als “Als Hilfreich bewerten” und Beiträge die deine Frage ganz oder teilweise beantwortet haben als “Als Antwort markieren”.
    My PowerShell Blog http://www.admin-source.info
    [string](0..21|%{[char][int]([int]("{0:d}" -f 0x28)+('755964655967-86965747271757624-8796158066061').substring(($_*2),2))})-replace' '
    German ? Come to German PowerShell Forum!


    Montag, 28. Januar 2013 08:50
  • Hallo Peter,

    vielen Dank für die Mühe(n)! Ich werde allerdings erst zu Beginn des Wochenendes Zeit haben, das Ganze auszuprobieren. Hast du es in der Konsole getestet? Da schien ja die ControlBox-Property nicht zu wirken..

    Für eine Art kleinen "Wartebalken" eine ganze Menge Code, die die Powershell haben will, wenn ich das mal so feststelle... ;-)

    Gruß Scriptex


    • Bearbeitet Scriptex Donnerstag, 31. Januar 2013 11:56
    Montag, 28. Januar 2013 16:45
  • Gibt es alternativ ein Cmdlet, mit dem Powershell animierte GIFs anzeigen kann?
    Donnerstag, 31. Januar 2013 11:58
  • PowerShell ist nicht dafür entwickelt worden Grafische Benutzeroberflächen oder Bilder anzuzeigen!
    Man kann aber trotzdem alles damit machen! Nur hat man dann den SELBEN Entwicklungsaufwand wie ein Programmierer!
    Auch ein ausgewachsener .NET Programmierer muss sich mir dem Multithreading in diesem Fall herumschlagen!

    Kurze such im Internet erster Treffer: https://gist.github.com/969855

    Müsste auch mit .gif gehen siehe Dokumentation :
    http://msdn.microsoft.com/de-de/library/stf701f5.aspx


    Please click “Mark as Answer” if my post answers your question and click “Vote As Helpful” if my Post helps you.
    Bitte markiere hilfreiche Beiträge von mir als “Als Hilfreich bewerten” und Beiträge die deine Frage ganz oder teilweise beantwortet haben als “Als Antwort markieren”.
    My PowerShell Blog http://www.admin-source.info
    [string](0..21|%{[char][int]([int]("{0:d}" -f 0x28)+('755964655967-86965747271757624-8796158066061').substring(($_*2),2))})-replace' '
    German ? Come to German PowerShell Forum!

    Donnerstag, 31. Januar 2013 13:16
  • Hallo ist bereits ein paar Jahre her, aber ich muss meinen Senf dazu geben:-)

    $LoadForm.ControlBox = $False

    auf true setzen und schon erscheint das X rechts oben...
    Sonntag, 17. Oktober 2021 12:21