Benutzer mit den meisten Antworten
ProgressBar in der Konsole in neuem Thread anzeigen

Frage
-
Hallo!
Ich möchte eine WindowsForms-ProgressBar anzeigen, während ein Skript aktiv ausgeführt wird. Das Problem habe ich schon einmal formuliert (siehe hier) und eine passende Lösung, basierend auf dem Erstellen eines neuen Threads/Runspace, erhalten. Mein Code ist etwas gekürzter:
# Grundform Function Form { [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null $ProgressForm = New-Object System.Windows.Forms.Form $ProgressForm.Text = 'Bitte warten...' $ProgressForm.Width = 350 $ProgressForm.Height = 144 $ProgressForm.MaximizeBox = $False $ProgressForm.MinimizeBox = $False $ProgressForm.ControlBox = $False $ProgressForm.ShowIcon = $False $ProgressForm.StartPosition = 1 $ProgressForm.Visible = $False $ProgressForm.FormBorderStyle = "FixedDialog" #$ProgressForm.WindowState = "Normal" $InText = New-Object System.Windows.Forms.Label $InText.Text = 'Die Anfrage wird bearbeitet...' $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 = 40 $ProgressForm.Controls.Add($InText) $ProgressForm.Controls.Add($ProgressBar1) $sharedData.Form = $ProgressForm # Speichern der Form in die Hashtable $ProgressForm.ShowDialog() | Out-Null } # Hashtable für Datenaustausch zwischen den Threads $sharedData = [HashTable]::Synchronized(@{}) $sharedData.Form = $Null # Thread eigener Runspace mit der Hashtable $newRunspace = [RunSpaceFactory]::CreateRunspace() $newRunspace.ApartmentState = "STA" $newRunspace.ThreadOptions = "ReuseThread" $newRunspace.Open() $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData) # Thread eigene asynchrone Powershell $PS = [PowerShell]::Create() $PS.Runspace = $newRunspace $PS.AddScript($Function:Form) $AsyncResult = $PS.BeginInvoke() # -------------------------------------------------------- # Eigentliches Script For ($i = 0 ; $i -lt 5; $I++) { "Runde Nr.: $I" Start-Sleep 2 } # -------------------------------------------------------- # Form schließen If($sharedData.Form) { $sharedData.Form.close() } # neue Powershell beenden und Objekt entfernen $PS.Endinvoke($AsyncResult) $PS.dispose()
Nun funktioniert diese Lösung leider nur in der ISE. Powershell.exe zeigt zwar das Wartefenster und führt die Schleife aus (beide Threads laufen also) aber der Balken wird nicht animiert und bleibt leer. Ich habe es schon mit verschiedenen Verknüpfungen mit unterschiedlichen Parametern versucht, auch das STA/MTA-Problem scheint nicht der Grund zu sein, zumal im Skript selbst der neue Thread als "STA" erstellt wird. Meine Vermutung war, dass der Erstellung des Threads eine Option fehlt, die in der ISE per default eingestellt ist. So wird in der ISE die ProgressBar animiert dargestellt und in der Konsole erscheint nur das unanimierte Fenster.
Wenn ich innerhalb des Skriptes den ApartmentState mit
[threading.thread]::CurrentThread.GetApartmentState()
abfrage, erhalte ich als Ausgabe "STA", egal ob ISE oder Konsole. Ich habe eben gerade testweise nur den Code extrahiert, der für die Anzeige der Form benötigt wird und diesen in ein Testskript geschrieben:
Function TestForm { [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null $ProgressForm = New-Object System.Windows.Forms.Form $ProgressForm.Text = 'Bitte warten...' $ProgressForm.Width = 350 $ProgressForm.Height = 144 $ProgressForm.MaximizeBox = $False $ProgressForm.MinimizeBox = $False $ProgressForm.ControlBox = $True $ProgressForm.ShowIcon = $False $ProgressForm.StartPosition = 1 $ProgressForm.Visible = $False $ProgressForm.FormBorderStyle = "FixedDialog" #$ProgressForm.WindowState = "Normal" $InText = New-Object System.Windows.Forms.Label $InText.Text = 'Die Anfrage wird bearbeitet...' $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 = 40 $ProgressForm.Controls.Add($InText) $ProgressForm.Controls.Add($ProgressBar1) $ProgressForm.ShowDialog() | Out-Null } $AptSt = [threading.thread]::CurrentThread.GetApartmentState() Write-Host "ApartmentState: $($AptSt)" Write-Host "Test der LoadingBar: <Enter>" Read-Host TestForm
Wenn ich diesen Code mit der Konsole ausführe oder ihn per Verknüpfung und -STA Parameter aufrufe, erscheint auch hier nur das Fenster ohne Animation. D.h. die Konsole an sich stellt keine Animation dar. Das würde erklären, warum es im neu erstellten Thread ähnlich aussieht.
Meine Frage ist also: Wie kann ich mit der normalen powershell.exe eine animierte ProgressBar (Marquee-Style) mit WindowsForms darstellen? Andere WindowsForms lassen sich problemlos darstellen, allerdings besitzen diese auch keine Animation. Ist es mit der Konsole als PowerShell-Host überhaupt möglich?
Vielen Dank und Gruße
Scriptex
- Bearbeitet Scriptex Montag, 9. September 2013 09:29
Antworten
-
Sorry ich hatte so viel ausprobiert....
Das sollte nun funzen (Getestet!):
1. Der PowerShell Prozess MUSS schon im STA mode laufen
2. die Visual Styles müssen geladen und eingeschaltet werden
3. message loop starten# startet das Skript im STA-Mode neu, wenn dies erforderlich sein sollte. If ($host.Runspace.ApartmentState -ne 'STA') { write-host "Script is not running in STA mode. Switching " $Script = $MyInvocation.MyCommand.Definition Start-Process powershell.exe -ArgumentList "-sta $Script" Exit } # Grundform Function Form { [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.VisualStyles") | Out-Null $ProgressForm = New-Object System.Windows.Forms.Form $ProgressForm.Text = 'Bitte warten...' $ProgressForm.Width = 350 $ProgressForm.Height = 144 $ProgressForm.MaximizeBox = $False $ProgressForm.MinimizeBox = $False $ProgressForm.ControlBox = $False $ProgressForm.ShowIcon = $False $ProgressForm.StartPosition = 1 $ProgressForm.Visible = $False $ProgressForm.FormBorderStyle = "FixedDialog" #$ProgressForm.WindowState = "Normal" $InText = New-Object System.Windows.Forms.Label $InText.Text = 'Die Anfrage wird bearbeitet...' $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 = 40 $ProgressForm.Controls.Add($InText) $ProgressForm.Controls.Add($ProgressBar1) $sharedData.Form = $ProgressForm # Speichern der Form in die Hashtable [System.Windows.Forms.Application]::EnableVisualStyles() [System.Windows.Forms.Application]::Run($ProgressForm) #[System.Windows.Forms.Application]::DoEvents() } # Hashtable für Datenaustausch zwischen den Threads $sharedData = [HashTable]::Synchronized(@{}) $sharedData.Form = $Null # Thread eigener Runspace mit der Hashtable $newRunspace = [RunSpaceFactory]::CreateRunspace() $newRunspace.ApartmentState = "STA" $newRunspace.ThreadOptions = "ReuseThread" $newRunspace.Open() $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData) # Thread eigene asynchrone Powershell $PS = [PowerShell]::Create() $PS.Runspace = $newRunspace $PS.AddScript($Function:Form) $AsyncResult = $PS.BeginInvoke() # -------------------------------------------------------- # Eigentliches Script For ($i = 0 ; $i -lt 5; $I++) { "Runde Nr.: $I" Start-Sleep 2 } # -------------------------------------------------------- # Form schließen If($sharedData.Form) { $sharedData.Form.close() } # neue Powershell beenden und Objekt entfernen $PS.Endinvoke($AsyncResult) $PS.dispose()
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!
- Bearbeitet Peter Kriegel Montag, 9. September 2013 13:00 sadasdas
- Als Antwort markiert Scriptex Dienstag, 10. September 2013 09:14
Alle Antworten
-
Zitat aus:
Visual Basic 2008 Das umfassende Handbuch von Andreas Kuehnel, Stephan Leibbrandt, Galileo Press Verlag http://openbook.galileocomputing.de/visualbasic_2008/vb2008_11_winform_003.htm#mje21e5e988883411f07425b8267d123f4Ein grundlegender Unterschied zwischen einer Konsolenanwendung und einer Windows-Anwendung ist die sogenannte Nachrichtenschleife (message pump). Unter dem Begriff Nachricht ist ein Ereignis zu verstehen. Windows-Programme reagieren auf Ereignisse, und eine Nachrichtenschleife wartet ununterbrochen auf eingehende Ereignisse....
.... Die statische Methode Run der Klasse Application startet die Nachrichtenschleife. Als Argument wird Run eine neue Instanz des Formulars oder ein Applikationskontext übergeben......
.... Eine Nachrichtenschleife ist immer an einen bestimmten Thread gebunden!
# Ersetze Zeile : #$ProgressForm.ShowDialog() | Out-Null # mit dieser Zeile ;-)
[System.Windows.Forms.Application]::Run($ProgressForm)
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!
- Bearbeitet Peter Kriegel Montag, 9. September 2013 11:42 sdasds
- Als Antwort vorgeschlagen M. Heitmann Montag, 9. September 2013 11:47
- Nicht als Antwort vorgeschlagen Scriptex Montag, 9. September 2013 12:42
-
Peter, vielen Dank für deine Antwort.
Leider hat das Ersetzen des Aufrufs
$ProgressForm.ShowDialog() | Out-Null
mit
[System.Windows.Forms.Application]::Run($ProgressForm)
nicht geholfen. Sowohl beim Testskript als auch beim 2-Thread-Skript funktioniert es wieder nur in der ISE. Ist evtl. die PS-Version das Problem?
Anbei noch der aktualisierte Skriptcode.
Function TestForm { [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null $ProgressForm = New-Object System.Windows.Forms.Form $ProgressForm.Text = 'Bitte warten...' $ProgressForm.Width = 350 $ProgressForm.Height = 144 $ProgressForm.MaximizeBox = $False $ProgressForm.MinimizeBox = $False $ProgressForm.ControlBox = $True $ProgressForm.ShowIcon = $False $ProgressForm.StartPosition = 1 $ProgressForm.Visible = $False $ProgressForm.FormBorderStyle = "FixedDialog" #$ProgressForm.WindowState = "Normal" $InText = New-Object System.Windows.Forms.Label $InText.Text = 'Die Anfrage wird bearbeitet...' $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 = 40 $ProgressForm.Controls.Add($InText) $ProgressForm.Controls.Add($ProgressBar1) [System.Windows.Forms.Application]::Run($ProgressForm) } $AptSt = [threading.thread]::CurrentThread.GetApartmentState() Write-Host "ApartmentState: $($AptSt)" Write-Host "Test der LoadingBar: <Enter>" Read-Host TestForm
Gruß Scriptex
-
Sorry ich hatte so viel ausprobiert....
Das sollte nun funzen (Getestet!):
1. Der PowerShell Prozess MUSS schon im STA mode laufen
2. die Visual Styles müssen geladen und eingeschaltet werden
3. message loop starten# startet das Skript im STA-Mode neu, wenn dies erforderlich sein sollte. If ($host.Runspace.ApartmentState -ne 'STA') { write-host "Script is not running in STA mode. Switching " $Script = $MyInvocation.MyCommand.Definition Start-Process powershell.exe -ArgumentList "-sta $Script" Exit } # Grundform Function Form { [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.VisualStyles") | Out-Null $ProgressForm = New-Object System.Windows.Forms.Form $ProgressForm.Text = 'Bitte warten...' $ProgressForm.Width = 350 $ProgressForm.Height = 144 $ProgressForm.MaximizeBox = $False $ProgressForm.MinimizeBox = $False $ProgressForm.ControlBox = $False $ProgressForm.ShowIcon = $False $ProgressForm.StartPosition = 1 $ProgressForm.Visible = $False $ProgressForm.FormBorderStyle = "FixedDialog" #$ProgressForm.WindowState = "Normal" $InText = New-Object System.Windows.Forms.Label $InText.Text = 'Die Anfrage wird bearbeitet...' $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 = 40 $ProgressForm.Controls.Add($InText) $ProgressForm.Controls.Add($ProgressBar1) $sharedData.Form = $ProgressForm # Speichern der Form in die Hashtable [System.Windows.Forms.Application]::EnableVisualStyles() [System.Windows.Forms.Application]::Run($ProgressForm) #[System.Windows.Forms.Application]::DoEvents() } # Hashtable für Datenaustausch zwischen den Threads $sharedData = [HashTable]::Synchronized(@{}) $sharedData.Form = $Null # Thread eigener Runspace mit der Hashtable $newRunspace = [RunSpaceFactory]::CreateRunspace() $newRunspace.ApartmentState = "STA" $newRunspace.ThreadOptions = "ReuseThread" $newRunspace.Open() $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData) # Thread eigene asynchrone Powershell $PS = [PowerShell]::Create() $PS.Runspace = $newRunspace $PS.AddScript($Function:Form) $AsyncResult = $PS.BeginInvoke() # -------------------------------------------------------- # Eigentliches Script For ($i = 0 ; $i -lt 5; $I++) { "Runde Nr.: $I" Start-Sleep 2 } # -------------------------------------------------------- # Form schließen If($sharedData.Form) { $sharedData.Form.close() } # neue Powershell beenden und Objekt entfernen $PS.Endinvoke($AsyncResult) $PS.dispose()
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!
- Bearbeitet Peter Kriegel Montag, 9. September 2013 13:00 sadasdas
- Als Antwort markiert Scriptex Dienstag, 10. September 2013 09:14