【问题标题】:Progress bar exists but it is not visible进度条存在但不可见
【发布时间】:2021-06-24 10:54:21
【问题描述】:

我在脚本中包含了一个进度条。当我运行脚本时,该栏存在(因为当我使用 Alt+Tab 浏览打开的窗口时会列出相关窗口)但我无法选择并查看它。

这是我的代码块...

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()

【问题讨论】:

  • 这可能是因为它与脚本的其余部分在一个进程中运行。这意味着你必须让你的脚本事件驱动甚至多线程参见:How to handle progress bar using PowerShell?
  • $CurrentTime = $Time.Elapsed: 错误的计时器。使用 System.Windows.Forms.Timer。或者将 System.Timers.Timer 的 SynchronizingObject 属性设置为 Form 实例。 -- 不需要Refresh() 表单,ProgressBar 已经自己完成了。

标签: powershell winforms progress-bar


【解决方案1】:

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

  • 基于事件显示表单模态,使用其.ShowDialog() 方法

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

    • 因此,您要执行的任何操作都必须在附加到表单或其控件的事件处理程序中执行,可能包括计时器 em> 控制其事件定期运行

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

  • 基于循环显示表单非模态,使用其.Show() 方法:

    • 继续执行您的 PowerShell 脚本。

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

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

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

示例代码

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

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

  • Start-Job用于模拟长时间运行的后台操作;但是,在 PowerShell (Core) 7+ 中,最好使用更快、更高效的 Start-ThreadJob cmdlet;如果您按需安装,您也可以在 Windows PowerShell 中使用它;见this answer

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

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


基于事件的示例

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-10
    • 2018-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多