我想在Form上从网上下载文件,并在ProgressBar中显示下载进度。为此,我订阅加载事件并执行异步加载。一切正常。这是简化的代码(删除了所有不必要的):
Add-Type -assembly System.Windows.Forms
$isDownloaded = $False
$webMain = New-Object System.Net.WebClient
Register-ObjectEvent -InputObject $webMain -EventName 'DownloadFileCompleted' -SourceIdentifier WebMainDownloadFileCompleted -Action {
$Global:isDownloaded = $True
}
Register-ObjectEvent -InputObject $webMain -EventName 'DownloadProgressChanged' -SourceIdentifier WebMainDownloadProgressChanged -Action {
$Global:Data = $event
}
function DownloadFile($Link, $Path, $Name) {
write-host "begin"
$Global:webMain.DownloadFileAsync($Link, $Path)
While (!$isDownloaded) {
$percent = $Global:Data.SourceArgs.ProgressPercentage
If ($percent -ne $null) {
write-host $percent
}
Wait-Event -Timeout 1
}
write-host "end"
}
DownloadFile 'https://www.7-zip.org/a/7z2301-x64.exe' 'D:\7Zip.exe' '7Zip'
一切正常。现在我将它添加到代码中的任何位置
$Form1 = New-Object System.Windows.Forms.Form
或
$Button1 = New-Object System.Windows.Forms.Button
并且脚本不起作用。 DownloadProgressChanged 和 DownloadFileCompleted 事件根本不会发生。
问题:为什么仅创建表单或按钮就会干扰脚本?
如果没有 System.Windows.Forms.Form,代码可以工作,但我最终需要创建一个表单并在其上渲染加载。
DownloadFileAsync 有效 - 文件已下载,但在使用任何 New-Object System.Windows.Forms.* 时,Register-ObjectEvent 本身中的事件不会发生(但没有它们也能正常工作)。
如评论中所述,PowerShell 对于异步编程来说并不是一种很好的语言,问题在于
.ShowDialog()
会阻塞线程并且不允许您的事件正常执行。解决方案是在单独的运行空间中注册事件,下面是如何实现这一点的最小示例(尽可能最小)。我添加了一些指针注释来帮助您理解逻辑,尽管代码显然并不容易,如前所述,PowerShell 不是为此设计的语言,C# 会给您更轻松的时间。
演示:
代码:
Add-Type -Assembly System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form = [System.Windows.Forms.Form]@{
Size = '500, 150'
FormBorderStyle = 'Fixed3d'
}
$btn = [System.Windows.Forms.Button]@{
Name = 'MyButton'
Text = 'Click Me!'
Size = '90, 30'
Location = '370, 70'
Anchor = 'Bottom, Right'
}
$btn.Add_Click({
# disable the button here to allow a single download at a time
$this.Enabled = $false
# hardcoded link here for demo
$downloader.DownloadFileAsync('https://www.7-zip.org/a/7z2301-x64.exe', "$pwd\7Zip.exe")
})
$progress = [System.Windows.Forms.ProgressBar]@{
Name = 'MyProgressBar'
Size = '460, 40'
Location = '10, 10'
Step = 0
Value = 0
Maximum = 100
}
$form.Controls.AddRange(($btn, $progress))
# create a WebClient instance
$downloader = [System.Net.WebClient]::new()
# create a new runspace where the Download Events will execute
$rs = [runspacefactory]::CreateRunspace($Host)
$rs.Open()
# add the `$form` instance to the runspace Global scope
$rs.SessionStateProxy.PSVariable.Set(
[psvariable]::new(
'form',
$form,
[System.Management.Automation.ScopedItemOptions]::AllScope))
# the code that will initiate the events in the new runspace
$ps = [powershell]::Create().AddScript({
$registerObjectEventSplat = @{
InputObject = $args[0] # $args[0] = $form
EventName = 'DownloadProgressChanged'
SourceIdentifier = 'WebMainDownloadProgressChanged'
Action = {
$progress = $form.Controls.Find('MyProgressBar', $false)[0]
# for demo, in addition to increment the progress bar,
# show the percentage to the console
$eventArgs.ProgressPercentage | Out-Host
# increment the progress bar
$progress.Value = $eventArgs.ProgressPercentage
}
}
Register-ObjectEvent @registerObjectEventSplat
$registerObjectEventSplat['EventName'] = 'DownloadFileCompleted'
$registerObjectEventSplat['SourceIdentifier'] = 'WebMainDownloadFileCompleted'
$registerObjectEventSplat['Action'] = {
# when the download is completed, enable the button
$form.Controls.Find('MyButton', $false)[0].Enabled = $true
# and show this to the console
Write-Host 'Download Completed!'
}
Register-ObjectEvent @registerObjectEventSplat
}).AddArgument($downloader)
$ps.Runspace = $rs
$task = $ps.BeginInvoke()
$form.ShowDialog()