进度条存在但不可见

Progress bar exists but it is not visible

我在脚本中包含了一个进度条。当我 运行 脚本栏存在时(因为当我使用 Alt+Tab 浏览打开的 windows 时列出了相关的 window)但是我无法 select 看到它。

这是我的代码块...

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName PresentationFramework
[...]
# progress bar
$form_bar = New-Object System.Windows.Forms.Form
$form_bar.Text = "TRANSFER RATE"
$form_bar.Size = New-Object System.Drawing.Size(600,200)
$form_bar.StartPosition = "manual"
$form_bar.Location = '1320,840'
$font = New-Object System.Drawing.Font("Arial", 12)
$form_bar.Font = $font
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(20,20)
$label.Size = New-Object System.Drawing.Size(550,30)
$form_bar.Controls.Add($label)
$bar = New-Object System.Windows.Forms.ProgressBar
$bar.Style="Continuous"
$bar.Location = New-Object System.Drawing.Point(20,70)
$bar.Maximum = 101
$bar.Size = New-Object System.Drawing.Size(550,30)
$form_bar.Controls.Add($bar)
$form_bar.Topmost = $true
$form_bar.Show() | out-null
$form_bar.Focus() | out-null
[...]
$percent = ($trasferred_bytes / $total_bytes)*100
$formatted = '{0:0.0}' -f $percent
[int32]$progress = $percent
$CurrentTime = $Time.Elapsed
$estimated = [int]((($CurrentTime.TotalSeconds/$percent) * (100 - $percent)) / 60)
$label.Text = "Progress: $formatted% - $estimated mins to end"
if ($progress -ge 100) {
    $bar.Value = 100
} else {
    $bar.Value = $progress
}
$form_bar.Refresh()

两种从 PowerShell 显示 WinForms 表单的基本方法

  • 事件-based: 显示形式modally, 使用其 .ShowDialog() 方法:

    • 阻止 执行您的 PowerShell 脚本,直到表单关闭。

    • 因此,您要执行的任何操作都必须在附在表单或其上的事件处理程序中执行控件,可能包括 timer 控件,其事件 运行 周期性地 .

    • 但是,您不能在事件处理程序中 运行 冗长的操作而不阻塞表单的事件处理,因此最好的解决方案是使用 PowerShell background job(见下文)。

  • 循环-based: 非模态显示表格 ,使用其.Show()方法:

    • 继续执行您的 PowerShell 脚本。

    • 由于 PowerShell 仍控制着前台线程,因此表单默认 无响应(这就是您遇到的情况)。

    • 因此,您必须在显示表单时输入一个循环,在该循环中您定期 调用 [System.Windows.Forms.Application]::DoEvents() 以保持表单响应,通常辅以 Start-Sleep 以避免紧密循环。

      • 注意:循环中的代码本身不能是长运行宁阻塞操作,因为那样会排除常规 [System.Windows.Forms.Application]::DoEvents() 电话;对于长时间 运行ning 阻塞操作,您还必须使用后台作业,您可以在循环中监控其进度(见下文)。

示例代码:

  • 以下简化、独立的 PSv5+ 示例说明了这两种方法。

  • 关于事件处理程序脚本块的警告是它们 运行 在调用者的 child 范围内;虽然您可以直接 get 调用者的变量,但 setting 它们需要使用 $script: 范围说明符,在最简单的情况下 - 请参阅 .

  • Start-Job is used for the simulated long-running background operation; however, in PowerShell (Core) 7+, it is preferable to use the faster and more efficient Start-ThreadJob cmdlet instead; you can also use it in Windows PowerShell, if you install it on demand; see .

  • 请注意,在基于循环的解决方案中,您可能并不总是需要使用后台 [线程] 作业;如果进度条 运行 的进度条更新之间的代码相当快,您可以 运行 直接在循环中。

如示例所示,基于循环的解决方案更简单,概念上也更直接。


基于事件的样本:

using namespace System.Windows.Forms
using namespace System.Drawing

Add-Type -AssemblyName System.Windows.Forms

$maxProgressSteps = 10

# Create the form.
$form = [Form] @{
  Text = "TRANSFER RATE"; Size = [Size]::new(600, 200); StartPosition = 'CenterScreen'; TopMost = $true; MinimizeBox = $false; MaximizeBox = $false; FormBorderStyle = 'FixedSingle'
}
# Add controls.
$form.Controls.AddRange(@(
  ($label = [Label] @{ Location = [Point]::new(20, 20); Size = [Size]::new(550, 30) })
  ($bar = [ProgressBar] @{ Location = [Point]::new(20, 70); Size = [Size]::new(550, 30); Style = 'Continuous'; Maximum = $maxProgressSteps })
))

# Create a timer and register an event-handler script block
# that periodically checks the background job for new output
# and updates the progress bar accordingly.
($timer = [Timer] @{ Interval = 200 }).add_Tick({ 
  # Note: This code runs in a *child scope* of the script.
  if ($output = Receive-Job $job) {
    $step = $output[-1] # Use the last object output.
    # Update the progress bar.
    $label.Text = '{0} / {1}' -f $step, $maxProgressSteps
    $bar.Value = $step
  }
  if ($job.State -in 'Completed', 'Failed') { $form.Close() }
})

# Enable the timer when the form loads.
$form.add_Load({
  $timer.Enabled = $true
})

# Start the long-running background job that
# emits objects as they become available.
$job = Start-Job {
  foreach ($i in 1..$using:maxProgressSteps) {
    $i
    Start-Sleep -Milliseconds 500
  }
}

# Show the form *modally*, i.e. as a blocking dialog.
$null = $form.ShowDialog()

# Getting here means that the form was closed.
# Clean up.
$timer.Dispose(); $form.Dispose()
Remove-Job $job -Force

基于循环的样本:

using namespace System.Windows.Forms
using namespace System.Drawing

Add-Type -AssemblyName System.Windows.Forms

$maxProgressSteps = 10

# Create the form.
$form = [Form] @{
  Text = "TRANSFER RATE"; Size = [Size]::new(600, 200); StartPosition = 'CenterScreen'; TopMost = $true; MinimizeBox = $false; MaximizeBox = $false; FormBorderStyle = 'FixedSingle'
}
# Add controls.
$form.Controls.AddRange(@(
  ($label = [Label] @{ Location = [Point]::new(20, 20); Size = [Size]::new(550, 30) })
  ($bar = [ProgressBar] @{ Location = [Point]::new(20, 70); Size = [Size]::new(550, 30); Style = 'Continuous'; Maximum = $maxProgressSteps })
))

# Start the long-running background job that
# emits objects as they become available.
$job = Start-Job {
  foreach ($i in 1..$using:maxProgressSteps) {
    $i
    Start-Sleep -Milliseconds 500
  }
}

# Show the form *non-modally*, i.e. execution
# of the script continues, and the form is only
# responsive if [System.Windows.Forms.Application]::DoEvents() is called periodically.
$null = $form.Show()

while ($job.State -notin 'Completed', 'Failed') {
  # Check for new output objects from the background job.
  if ($output = Receive-Job $job) {
    $step = $output[-1] # Use the last object output.
    # Update the progress bar.
    $label.Text = '{0} / {1}' -f $step, $maxProgressSteps
    $bar.Value = $step
  }  

  # Allow the form to process events.
  [System.Windows.Forms.Application]::DoEvents()

  # Sleep a little, to avoid a near-tight loop.
  Start-Sleep -Milliseconds 200
}

# Clean up.
$form.Dispose()
Remove-Job $job -Force