我有这个代码来创建一个非常简单的隐藏进程与图标托盘的上下文菜单。当通过竞赛菜单激活该功能时,脚本开始每5秒发送字母“q”。
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Windows\System32\mmc.exe")
# Part - Add the systray menu
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "WPF Systray tool"
$Main_Tool_Icon.Icon = $icon
$Main_Tool_Icon.Visible = $true
$Menu_Exit = New-Object System.Windows.Forms.MenuItem
$Menu_Exit.Text = "Exit"
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
#by Franco
$Menu_Key = New-Object System.Windows.Forms.MenuItem
$Menu_Key.Text = "Invia pressione tasto ogni 5 secondi"
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Exit)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Key)
# When Exit is clicked, close everything and kill the PowerShell process
$Menu_Exit.add_Click({
$Main_Tool_Icon.Visible = $false
Stop-Process $pid
})
#send Key "q" every 5 seconds
$Menu_key.add_Click({
$a = new-object -com "wscript.shell"
while ($true){
sleep 5
$a.sendkeys("q")
}
})
# Make PowerShell Disappear
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Force garbage collection just to start slightly lower RAM usage.
[System.GC]::Collect()
# Create an application context for it to all run within.
# This helps with responsiveness, especially when clicking Exit.
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
当我想用退出菜单上下文按钮退出脚本时,这个问题就会发生,直到我用鼠标左键再次单击图标托盘时,它才真正退出。只要我用鼠标左键点击图标,图标就消失了,它停止发送字母“q”。当我在没有首先通过上下文菜单激活sendkeys方法的情况下退出脚本wia图标上下文菜单时,这个问题不会发生。但是如果我这样修改代码:#send Key "q" every 5 seconds
$Menu_key.add_Click({
$a = new-object -com "wscript.shell"
$c = 0
while ($c -lt 5){
sleep 5
$a.sendkeys("q")
$c++
}
})
它退出,点击相同的退出按钮后,只有在while循环结束后,有没有办法在它结束前中断循环?
我也不明白脚本的下面部分是做什么的:$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
和#Force garbage collection just to start slightly lower RAM usage.
[System.GC]::Collect()
我在gitHub上下载了这个模型:Build-PS1-Systray-Tool
谢谢你的帮助
佛朗哥
1条答案
按热度按时间jdzmm42g1#
我也不明白脚本的下面部分是做什么的:
[System.GC]::Collect()
它强制 * 同步 * garbage collection,即。即回收由不再被任何应用程序引用的对象所占用的存储器。
这样做与您的脚本无关**,因为没有明显的理由这样做:没有积累不再被引用的对象,这些对象可能会导致需要缓解的内存压力。
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
为了让您的通知区域(系统托盘)图标处理 (WinForms)GUI事件**,此代码是必需的,以便与相关上下文菜单的交互按预期工作。
具体来说,它通过
[System.Windows.Forms.Application]::Run()
启动一个阻塞的windows消息循环,该循环一直运行到$appContext
中存储的应用程序上下文对象([System.Windows.Forms.ApplicationContext]
通过调用.ExitThread()
发出退出的信号)。这类似于在更典型的WinForms场景中对
.ShowDialog()
的调用,其中 form 将以模式显示;在这种情况下,阻塞调用在表单 * 关闭 * 时结束。除非显式创建其他线程,否则脚本将在单个线程中运行,包括作为事件委托服务器的脚本块。
您提到最终使用
Start-ThreadJob
来执行周期性任务,这实际上是*更好的 * 解决方案**,因为它使您能够并行执行长时间运行的任务,而不会干扰UI的响应能力。实际上,正是原始脚本的单线程特性导致了这个问题:您在
$Menu_key.add_Click()
事件处理程序中的sleep 5
(Start-Sleep -Seconds 5
)调用 * 阻塞 * UI事件处理,导致您看到的症状。有一些方法可以避免这种情况,但是创建一个单独的线程会给你带来更大的灵活性。
也就是说,在您的特定情况下-每N秒运行一个快速操作-使用WinForms计时器对象可能是最简单的解决方案。
以下自包含的示例代码演示了在WinForms应用程序上下文中定期运行操作的三种方法:
Start-ThreadJob
的线程作业在单独的线程中运行操作。System.Windows.Forms.Timer
)每N秒自动调用一个脚本块。System.Windows.Forms.Application.DoEvents
使用嵌套的手动事件处理循环,该循环在必要的短时间间隔内调用处理UI事件,同时在其间执行其他操作。注意:这在单表单、单通知图标的场景中效果很好,但在其他情况下应该避免。
DoEvents()
循环的一种方法是完全放弃使用应用程序上下文和[Application]::Run()
,而是在主线程中实现单个DoEvents()
循环(参见this answer)。注意事项:
System.Windows.Forms.ContextMenuStrip
和System.Windows.Forms.ToolStripMenuItem
来替换过时的System.Windows.Forms.ContextMenu
和System.Windows.Forms.MenuItem
类,这两个类都在中不再可用。NET(Core)3.1+。Write-Verbose
模拟到控制台的输出的。ApplicationContext.ExitThread()
用于退出消息循环,以便有序关闭,这是通过Stop-Process
杀死当前进程的更好替代方案