ProgressBar在ISE上显示,但在控制台上不显示。 Runspace上的Powershell响应式GUI

时间:2018-10-05 17:58:45

标签: powershell user-interface progress-bar responsive runspace

我一直在研究脚本的概念/模板的简单证明,其中我具有默认的运行空间来运行繁重的任务,而另一个具有运行响应式GUI的能力。

我已经测试了各种方法来传达运行空间。最初,我尝试了Control.Invoke,但是在不同论坛上的一些意见以及测试中的怪异行为使我不得不使用基于同步HashTable的基于消息的互通。 ProgressBar可与control.Invoke一起使用,但是执行其他一些操作(如禁用表单上的多个按钮)的执行速度非常慢。

第一个问题:我想在任务执行时显示一个进度条(选取框),以更改其可见状态。但是,当脚本在ISE上运行时显示进度条,而在控制台上运行时则不显示。我认为这是因为主运行空间很忙,但我不知道它如何影响GUI运行空间...

2nd:在下面发布的脚本中,我正在通过堆栈变量使用脚本块在运行空间之间传递命令。然后,每个运行空间(主运行空间通过池进行操作,GUI通过计时器进行操作)检查堆栈中是否有待执行的任务,如果有,则执行该任务。但是,如果我想调用在另一个运行空间上声明的函数(在此示例中为Test-OutOfMainScopeUiFunction),则不能。我会在脚本块上收到运行时错误。我曾经想到过这样的解决方案: -在两个运行空间上导入函数 -使函数成为全局函数还是使用functon委托? -尽管使用了脚本块,但仍将执行命令的字符串传递给了->目前正在使用此脚本,但不太喜欢...容易出错

任何对进度条问题或脚本改进的解决方案将不胜感激。谢谢!

$global:x = [Hashtable]::Synchronized(@{})
$global:x.MainDispatcher = new-object system.collections.stack
$global:x.UiDispatcher = new-object system.collections.stack
$global:x.GuiExited = $false

$formSB = { 

  [reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
  [reflection.assembly]::LoadWithPartialName("System.Drawing")

  Function Test-OutOfMainScopeUiFunction 
  {
    $x.Host.Ui.WriteLine("Test-OutOfMainScopeUiFunction Executed")
  }
  Function Execute-OnMainRs
  {
    param(
      [Parameter(Mandatory=$true)]
      [ScriptBlock]$ScriptBlock
    )

    $x.Host.Ui.WriteLine("`r`nAdding task")

    $x.MainDispatcher.Push($ScriptBlock)

    $x.Host.Ui.WriteLine("Task added")
    $x.Host.Ui.WriteLine("Task: $($x.MainDispatcher)")
  }

  $form = New-Object System.Windows.Forms.Form

  $button = New-Object System.Windows.Forms.Button
  $button.Text = 'click'
  $button.Dock = [System.Windows.Forms.DockStyle]::Right

  $progressBar = (New-Object -TypeName System.Windows.Forms.ProgressBar)
  $ProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
  $ProgressBar.MarqueeAnimationSpeed = 50
  $ProgressBar.Dock = [System.Windows.Forms.DockStyle]::Bottom
  $ProgressBar.Visible = $false  

  $label = New-Object System.Windows.Forms.Label
  $label.Text = 'ready'
  $label.Dock = [System.Windows.Forms.DockStyle]::Top

  $timer=New-Object System.Windows.Forms.Timer
  $timer.Interval=100

  $timer.add_Tick({
      if($x.UiDispatcher.Count -gt 0)
      {
        & $($x.UiDispatcher.Pop())
      }
  })

  $form.Controls.add($label)
  $form.Controls.add($button)
  $form.Controls.add($progressBar)

  Add-Member -InputObject $form -Name Label -Value $label -MemberType NoteProperty
  Add-Member -InputObject $form -Name ProgressBar -Value $progressBar -MemberType NoteProperty

  $button.add_click({ 
      Execute-OnMainRs -ScriptBlock {
          write-host "MainRS: Long Task pushed from the UI started on: $(Get-Date)"
          start-sleep -s 2
          write-host "MainRS: Long Task pushed from the UI finished on: $(Get-Date)"
        }
  })

  $form.add_closed({ $x.GuiExited = $true })

  $x.Form = $form

  $timer.Start()

  $form.ShowDialog()

}

Function Execute-OnRs
{ 
  param(
    [Parameter(Mandatory=$true)]
    [ScriptBlock]$ScriptBlock
  )

  $x.Host = $Host

  $rs = [RunspaceFactory]::CreateRunspace()
  $rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
  $rs.Open()
  $rs.SessionStateProxy.SetVariable("x",$x)

  $ps = [PowerShell]::Create().AddScript($ScriptBlock)

  $ps.Runspace = $rs
  $handle = $ps.BeginInvoke()

  #Almacenar variables del RS
  $x.Handle = $handle
  $x.Ps = $ps
}
Function Execute-OnUiRs
{
  param(
    [Parameter(Mandatory=$true)]
    [ScriptBlock]$ScriptBlock
  )

  $x.UiDispatcher.Push($ScriptBlock)
}

Function Dispatch-PendingJobs
{ 
  while($global:x.GuiExited -eq $false) { 
    if($global:x.MainDispatcher.Count -gt 0)
    {
      Execute-OnUiRs -ScriptBlock {
        $msg = "UIRS: MainThread informs: Long Task started on $(Get-Date)."
        $global:x.Form.Label.Text = $msg
        $global:x.Form.ProgressBar.Visible = $true
        $x.host.ui.WriteLine($msg)    
        #Next line throws an error visible on UI runspace error stream
        Test-OutOfMainScopeUiFunction
      }

      & $($global:x.MainDispatcher.Pop())

      Execute-OnUiRs -ScriptBlock {
        $msg = "UIRS: MainThread informs: Long Task finished on $(Get-Date)."
        $global:x.Form.Label.Text = $msg
        $global:x.Form.ProgressBar.Visible = $false
        $x.host.ui.WriteLine($msg)
      }

      write-host "UI Streams: $($global:x.Ps.Streams |out-string)"
    }
    else
    {
      start-sleep -m 100
    }
  }
}

1 个答案:

答案 0 :(得分:0)

找到了解决方案... http://community.idera.com/powershell/powertips/b/tips/posts/enabling-visual-styles

必须先启用VisualStyles。问题与运行空间无关。这是一个简短而清晰的代码示例,该示例摘自Power Shell marquee progress bar not working及其修复程序。

Add-Type -AssemblyName System.Windows.Forms

[System.Windows.Forms.Application]::EnableVisualStyles()

$window = New-Object Windows.Forms.Form
$window.Size = New-Object Drawing.Size @(400,75)
$window.StartPosition = "CenterScreen"
$window.Font = New-Object System.Drawing.Font("Calibri",11,[System.Drawing.FontStyle]::Bold)
$window.Text = "STARTING UP"

$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Location = New-Object System.Drawing.Point(10, 10)
$ProgressBar1.Size = New-Object System.Drawing.Size(365, 20)
$ProgressBar1.Style = "Marquee"
$ProgressBar1.MarqueeAnimationSpeed = 20
$window.Controls.Add($ProgressBar1)

$window.ShowDialog()