Building counter GUI using PowerShell

huangapple go评论63阅读模式
英文:

Building counter GUI using powershell

问题

Here's the translated code portion:

#剩余时间(秒)
$delay = 10

#添加Windows窗体程序集
Add-Type -AssemblyName System.Windows.Forms

$Counter_Form = New-Object System.Windows.Forms.Form
$Counter_Form.Text = "倒计时定时器!"
$Counter_Form.Width = 450
$Counter_Form.Height = 200

$Counter_Label = New-Object System.Windows.Forms.Label
$Counter_Label.Width = 300
$Counter_Label.Height = 30
$Counter_Label.ForeColor = "绿色"

$normalfont = New-Object System.Drawing.Font("Times New Roman", 14)
$Counter_Label.Font = $normalfont
$Counter_Label.Left = 20
$Counter_Label.Top = 20

$Counter_Form.Controls.Add($Counter_Label)

#添加按钮以便在计时完成前退出
$OK_Button = New-Object System.Windows.Forms.Button
$OK_Button.Text = "确定"
$OK_Button.Width = 100
$OK_Button.Height = 30
$OK_Button.Left = 20
$OK_Button.Top = 70
$OK_Button.Add_Click({
    #在这里关闭窗体,也可以关闭系统
    $Counter_Form.Close()
})
$Counter_Form.Controls.Add($OK_Button)

while ($delay -ge 0)
{
    $Counter_Form.Visible = $true
    $Counter_Label.Text = "剩余秒数: $($delay)"
    
    if ($delay -lt 5)
    { 
        $Counter_Label.ForeColor = "红色"
        $fontsize = 20 - $delay
        
        $warningfont = New-Object System.Drawing.Font("Times New Roman", $fontsize, ([System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Underline))
        $Counter_Label.Font = $warningfont
    } 

    Start-Sleep 1
    $delay -= 1
}

$Counter_Form.Visible = $false

$Counter_Form.ShowDialog()

This code displays a countdown timer with an OK button in a PowerShell GUI. The timer counts down from 10 seconds (you can adjust the $delay variable), and the OK button allows the user to close the timer before the countdown is complete.

英文:

I have remaining Time of 20 sec. How can I create a PowerShell GUI that shows a countdown timer with an OK button? The timer should show the remaining time in decreasing order (e.g., 20, 19, 18) and should close automatically when the remaining time is complete. If the user clicks the OK button before the remaining time is complete, it should close the timer.

#Remaing time in seconds
$delay = 10

#Add assembly for windows forms
Add-Type -AssemblyName System.Windows.Forms

$Counter_Form = New-Object System.Windows.Forms.Form
$Counter_Form.Text = "Countdown Timer!"
$Counter_Form.Width = 450
$Counter_Form.Height = 200

$Counter_Label = New-Object System.Windows.Forms.Label
$Counter_Label.Width = 300
$Counter_Label.Height = 30
$Counter_Label.ForeColor = "Green"

$normalfont = New-Object System.Drawing.Font("Times New Roman", 14)
$Counter_Label.Font = $normalfont
$Counter_Label.Left = 20
$Counter_Label.Top = 20

$Counter_Form.Controls.Add($Counter_Label)


#Adding button so that that exist without waiting for counter to complete
$OK_Button = New-Object System.Windows.Forms.Button
$OK_Button.Text = "OK"
$OK_Button.Width = 100
$OK_Button.Height = 30
$OK_Button.Left = 20
$OK_Button.Top = 70
$OK_Button.Add_Click({
    #here closing the form but we can shut down the system also
    $Counter_Form.Close()
})
$Counter_Form.Controls.Add($OK_Button)



while ($delay -ge 0)
{
     
    $Counter_Form.Visible = $true
    $Counter_Label.Text = "Seconds Remaining: $($delay)"
    
    if ($delay -lt 5)
    { 
        $Counter_Label.ForeColor = "Red"
        $fontsize = 20 - $delay
        
        $warningfont = New-Object System.Drawing.Font("Times New Roman", $fontsize, ([System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Underline))
        $Counter_Label.Font = $warningfont
    } 

    Start-Sleep 1
    $delay -= 1
}

$Counter_Form.Visible = $false

$Counter_Form.ShowDialog()

In above code I have added OK button but it's not working .

答案1

得分: 4

你可以使用Timer实例,正如Jimi在评论中建议的那样,在其Tick事件中,你可以定义每个滴答的逻辑,这几乎与你在while循环中拥有的逻辑相同。

Add-Type -AssemblyName System.Windows.Forms

# 我们需要在这里使用引用类型(在本例中是哈希表),以便Timer事件可以从该范围更新它
$delay = @{ Counter = 10 }

$Counter_Form = [System.Windows.Forms.Form]@{
    Text   = 'Countdown Timer!'
    Width  = 450
    Height = 200
}

$Counter_Label = [System.Windows.Forms.Label]@{
    Width     = 300
    Height    = 30
    ForeColor = 'Green'
    Font      = New-Object System.Drawing.Font('Times New Roman', 14)
    Left      = 20
    Top       = 20
    Text      = 'Hello world!'
}
$Counter_Form.Controls.Add($Counter_Label)

$OK_Button = [System.Windows.Forms.Button]@{
    Text   = 'OK'
    Width  = 100
    Height = 30
    Left   = 20
    Top    = 70
}

$OK_Button.Add_Click({
    # 如果点击OK,停止计时器!
    $timer.Stop()
    # 并更新标签
    $Counter_Label.Text = 'Everything good!'
    $Counter_Label.ForeColor = 'Green'
    $Counter_Label.Font = [System.Drawing.Font]::new('Times New Roman', 14)
})
$Counter_Form.Controls.Add($OK_Button)

# 创建一个Timer实例
$timer = [System.Windows.Forms.Timer]::new()
# 每1秒触发一次Tick事件
$timer.Interval = [timespan]::FromSeconds(1).TotalMilliseconds
$timer.Add_Tick({
    $text = '剩余秒数: {0}' -f $delay.Counter--
    # 你可以随时使用`Out-Host`来进行故障排除,完成后请将其删除
    $text | Out-Host
    $Counter_Label.Text = $text

    if ($delay.Counter -eq -1) {
        # 发送取消对话框,这将触发所有资源的处理
        $Counter_Form.DialogResult = 'Cancel'
    }

    if ($delay.Counter -lt 5) {
        $Counter_Label.ForeColor = 'Red'
        $Counter_Label.Font = [System.Drawing.Font]::new(
            'Times New Roman',
            20 - $delay.Counter,
            [System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Underline)
    }
})

$Counter_Form.Add_Shown({
    $Counter_Form.Activate()
    $timer.Start()
})

# 当退出窗体时
if ($Counter_Form.ShowDialog()) {
    $timer.Dispose()

    $Counter_Form.Controls | ForEach-Object {
        $_.Dispose()
    }

    $Counter_Form.Dispose()
}
英文:

You can use a Timer instance as Jimi recommended in comments and in its Tick event you can define the logic for each tick which is pretty much the same that what you have in your while loop.

Add-Type -AssemblyName System.Windows.Forms

# We need to use a reference type here (a hashtable in this case)
# so that the Timer event can update it from that scope
$delay = @{ Counter = 10 }

$Counter_Form = [System.Windows.Forms.Form]@{
    Text   = 'Countdown Timer!'
    Width  = 450
    Height = 200
}

$Counter_Label = [System.Windows.Forms.Label]@{
    Width     = 300
    Height    = 30
    ForeColor = 'Green'
    Font      = New-Object System.Drawing.Font('Times New Roman', 14)
    Left      = 20
    Top       = 20
    Text      = 'Hello world!'
}
$Counter_Form.Controls.Add($Counter_Label)

$OK_Button = [System.Windows.Forms.Button]@{
    Text   = 'OK'
    Width  = 100
    Height = 30
    Left   = 20
    Top    = 70
}

$OK_Button.Add_Click({
    # If we click OK, stop the timer!
    $timer.Stop()
    # And update the label
    $Counter_Label.Text = 'Everything good!'
    $Counter_Label.ForeColor = 'Green'
    $Counter_Label.Font = [System.Drawing.Font]::new('Times New Roman', 14)
})
$Counter_Form.Controls.Add($OK_Button)

# Create a Timer instance
$timer = [System.Windows.Forms.Timer]::new()
# With 1 second interval ticks
$timer.Interval = [timespan]::FromSeconds(1).TotalMilliseconds
$timer.Add_Tick({
    $text = 'Seconds remaining: {0}' -f $delay.Counter--
    # You can always troubleshoot using `Out-Host` here, remove it when done
    $text | Out-Host
    $Counter_Label.Text = $text

    if ($delay.Counter -eq -1) {
        # Send a Cancel Dialog, this will trigger the disposal of all resources
        $Counter_Form.DialogResult = 'Cancel'
    }

    if ($delay.Counter -lt 5) {
        $Counter_Label.ForeColor = 'Red'
        $Counter_Label.Font = [System.Drawing.Font]::new(
            'Times New Roman',
            20 - $delay.Counter,
            [System.Drawing.FontStyle]::Bold -bor [System.Drawing.FontStyle]::Underline)
    }
})

$Counter_Form.Add_Shown({
    $Counter_Form.Activate()
    $timer.Start()
})

# when exiting the form
if ($Counter_Form.ShowDialog()) {
    $timer.Dispose()

    $Counter_Form.Controls | ForEach-Object {
        $_.Dispose()
    }

    $Counter_Form.Dispose()
}

答案2

得分: 1

以下是翻译好的内容:

为了保持一个 WinForms 窗体对用户输入的响应,你需要采取以下两种方法之一:

要么:调用 `.ShowDialog()` 来模态显示一个窗体,即阻止执行,直到窗体关闭。

除非使用作业,否则唯一能在显示窗体时执行的 PowerShell 代码是作为窗体控件的事件处理程序的脚本块。

[Santiago Squarzon 的有用回答](https://stackoverflow.com/a/76630419/45375) 包含一个使用定时器控件定期更新倒计时显示的 `.ShowDialog()` 解决方案。

或者:如果你希望 PowerShell 代码保持对前台线程的控制,就像你尝试的那样,最简单的方法是调用 `.Show()`(或设置 `.Visible = $true`),它以非模态方式显示窗体并立即返回控制给调用者,然后在循环中使用 `[System.Windows.Forms.Application]::DoEvents()` 调用,其中插入短暂的休眠时期。

如果你休眠时间太长 - 或者在循环中执行的任务需要太长时间才能执行 - 你的窗体将变得无响应,或者只能延迟响应。

虽然一般不推荐使用 `[System.Windows.Forms.Application]::DoEvents()`,但在简单的单窗体情况下,比如这种情况下,它可以正常工作,并避免了基于作业的并行处理的复杂性,允许在显示窗体时执行(运行时间较短的)操作。

因此,要修复你自己的方法,你需要:

在倒计时循环中调用 `[System.Windows.Forms.Application]::DoEvents()`。

在循环迭代之间休眠少于 1 秒,以便按下“确定”按钮或移动窗口响应更快;100 毫秒效果很好。

以下是简化的、修正后的代码版本:

由于现在循环迭代每隔 100 毫秒调用一次,所以使用基于时间戳的方法来计算剩余的秒数。

`using namespace` 语句和基于哈希表的对象初始化简化了代码。
英文:

<!-- language-all: sh -->

In order to keep a WinForms form responsive to user input, you need one of two approaches:

  • Either: Call .ShowDialog() to display a form modally, i.e. to block execution until the form is closed.

    • Short of using jobs, the only PowerShell code that can execute while the form is being displayed is in script blocks serving as event handlers for the form's controls, if any.

    • Santiago Squarzon's helpful answer contains a .ShowDialog() solution that uses a timer control for periodically updating the countdown display.

  • Or: If you want PowerShell code to remain in control of the foreground thread, as in your attempt, the simplest approach is to call .Show() (or set .Visible = $true), which displays the form non-modally and returns control the caller right away, followed by [System.Windows.Forms.Application]::DoEvents() calls in a loop, interspersed with short periods of sleep.

    • If you sleep too long - or perform tasks in the loop that take too long to execute - your form will become unresponsive or respond only with a delay.

    • While [System.Windows.Forms.Application]::DoEvents() is generally discouraged, it works as intended in simple, one-form scenarios such as this one and avoids the complexities of job-based parallelism, by allowing for performing (short-running) operations while the form is being shown.


Therefore, to fix your own approach you need to:

  • Call [System.Windows.Forms.Application]::DoEvents() in your countdown loop.

  • Sleep for less than 1 second between loop iterations, so that pressing the OK button or moving the window responds more quickly; 100 milliseconds works well.

The following is a streamlined, corrected version of your code:

  • Given that the loop iterations are now invoked every 100+ milliseconds, a timestamp-based approach is used to calculate the remaining number of seconds.

  • using namespace statements and hashtable-based object initializations simplify the code.

using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms
$Counter_Form = [Form] @{
Text = &#39;Countdown Timer&#39;
Width = 350; Height = 160
FormBorderStyle = &#39;FixedDialog&#39; # Prevent resizing
ControlBox = $false  # Hide buttons in title bar.
StartPosition = &#39;CenterScreen&#39;
TopMost = $true
}
$Counter_Form.Controls.AddRange(@(
($Counter_Label = [Label] @{
Left = 20; Top = 20; Width = 300; Height = 30
ForeColor = &#39;Green&#39;
Font = [Font]::new(&#39;Times New Roman&#39;, 14)
})
($OK_Button = [Button] @{
Text = &#39;OK&#39;
Left = 20; Top = 70; Width = 100; Height = 30
})  
))
# Add handler for the button-click event: close the form.
$OK_Button.add_Click({
$Counter_Form.Close()
})
# Countdown period in seconds
$delay = 10
# Calculate the point in time when the delay has elapsed.
$closeAt = [datetime]::UtcNow.AddSeconds($delay)
$Counter_Form.Visible = $true
while ($Counter_Form.Visible -and ($secsRemaining = [int] ($closeAt - [datetime]::UtcNow).TotalSeconds) -ge 0) {     
$newLabelText = &quot;Seconds Remaining: $secsRemaining&quot;
if ($newLabelText -ne $Counter_Label.Text) {    
if ($secsRemaining -lt 5) { 
$Counter_Label.ForeColor = &#39;Red&#39;
$fontsize = 20 - $secsRemaining        
$warningfont = [Font]::new(&#39;Times New Roman&#39;, $fontSize, [FontStyle] &#39;Bold, Underline&#39;)
$Counter_Label.Font = $warningfont
} 
$Counter_Label.Text = $newLabelText
}
# Process form events.
[System.Windows.Forms.Application]::DoEvents()
# Sleep only *a little*, so as to keep the form responsive.
[Threading.Thread]::Sleep(100)
}
$Counter_Form.Visible = $false

huangapple
  • 本文由 发表于 2023年7月6日 22:04:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76629669.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定