PowerShell NotifyIcon 上下文菜单
PowerShell NotifyIcon Context Menu
我有以下使用 PowerShell 的 NotifyIcon 运行:
这是右击图标打开的上下文菜单,目前只显示退出:
我想知道如何添加两个事件处理程序:
- 运行图标有左键点击事件时的函数
- 在右键菜单中添加另一个选项,该选项也运行一个功能
我已经在网上搜索了一个多小时,并且使用来自 5 或 6 个不同网站的旧代码尝试了大约 20 种不同的变体(都显示了截然不同的示例)。除了头疼,我一无所获。谁能提供任何 guidance/direction?
$ProgramDataPath = "$ENV:ProgramData\test"
$ProgramFilesPath = "${env:ProgramFiles(x86)}\test"
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$STForm = New-Object System.Windows.Forms.form
$NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$ContextMenu = New-Object System.Windows.Forms.ContextMenu
$MenuItem = New-Object System.Windows.Forms.MenuItem
$Timer = New-Object System.Windows.Forms.Timer
$HealthyIcon = New-Object System.Drawing.Icon("$ProgramFilesPath\icons\healthy.ico")
$UnhealthyIcon = New-Object System.Drawing.Icon("$ProgramFilesPath\icons\unhealthy.ico")
$STForm.ShowInTaskbar = $false
$STForm.WindowState = "minimized"
$NotifyIcon.Icon = $HealthyIcon
$NotifyIcon.ContextMenu = $ContextMenu
$NotifyIcon.ContextMenu.MenuItems.AddRange($MenuItem)
$NotifyIcon.Visible = $True
# We need to avoid using Start-Sleep as this freezes the GUI. Instead, we'll utilitse the .NET forms timer, it repeats a function at a set interval.
$Timer.Interval = 300000 # (5 min)
$Timer.add_Tick({ Load-Config })
$Timer.start()
# This will appear as a right click option on the system tray icon
$MenuItem.Index = 0
$MenuItem.Text = "Exit"
$MenuItem.add_Click({
$Timer.Stop()
$NotifyIcon.Visible = $False
$STForm.close()
})
function Load-Config
{
#Get-Content some Data from a file here
if ($warn)
{
$NotifyIcon.Icon = $UnhealthyIcon
$NotifyIcon.ShowBalloonTip(30000, "Attention!", "Some data from a file here...", [system.windows.forms.ToolTipIcon]"Warning")
Remove-Variable warn
}
else
{
$NotifyIcon.Icon = $HealthyIcon
}
}
Load-Config
[void][System.Windows.Forms.Application]::Run($STForm)
让我们谈谈您真正需要什么。看起来你有很多不需要的部件,比如计时器等等。您只需要一个 运行 空间。一个开放的形式将保持 运行 空间开放而不需要那个计时器。确保 $Form.ShowDialog()
是最后一个 运行.
那么让我们继续 NotifyIcon 弹出窗口。使弹出窗口发生的方法是私有的,这意味着我们需要通过反射来访问它。我们还需要在 MouseDown
上将通知图标的事件设置为 运行,并单击按钮 $_.button
确保将 $NotifyIcon.Icon
设置为图标,否则通知图标不会显示。
工作脚本
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.ShowInTaskbar = $true
$form.WindowState = [System.Windows.WindowState]::Normal
$MenuItemLeft = New-Object System.Windows.Forms.MenuItem
$MenuItemLeft.Text = "Left Exit"
$MenuItemLeft.add_Click({
$NotifyIcon.Visible = $False
$form.Close()
$NotifyIcon.Dispose()
})
$ContextMenuLeft = New-Object System.Windows.Forms.ContextMenu
$ContextMenuLeft.MenuItems.Add($MenuItemLeft)
$MenuItemRight = New-Object System.Windows.Forms.MenuItem
$MenuItemRight.Text = "Right Exit"
$MenuItemRight.add_Click({
$NotifyIcon.Visible = $False
$form.Close()
$NotifyIcon.Dispose()
})
$ContextMenuRight = New-Object System.Windows.Forms.ContextMenu
$ContextMenuRight.MenuItems.Add($MenuItemRight)
$NotifyIcon= New-Object System.Windows.Forms.NotifyIcon
$NotifyIcon.Icon = "C:\Test\Test.ico"
$NotifyIcon.ContextMenu = $ContextMenuRight
$NotifyIcon.add_MouseDown({
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::left ) {
$NotifyIcon.contextMenu = $ContextMenuLeft
}else{
$NotifyIcon.contextMenu = $ContextMenuRight
}
$NotifyIcon.GetType().GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic).Invoke($NotifyIcon,$null)
})
$NotifyIcon.Visible = $True
$form.ShowDialog()
$NotifyIcon.Dispose()
我重读了你的 post 所以我会为你提供最重要的部分
要让 Powershell 运行 您的命令,运行空格必须处于活动状态。 Runspace 接受 powershell 命令并将它们转化为实际操作。
由于您为此使用了 powershell,因此通知图标操作依赖于 运行空间来解释这些操作。
NotifyIcon 基本上只是角落里的一个图标,可以弹出气球通知或上下文菜单。
因此,当您查看时,您会看到 $NotifyIcon.ContextMenu
是一个包含 ContextMenu 对象 的 属性。上下文菜单对象包含 菜单项。
因此只需将 MenuItems 添加到 ContextMenu 对象,然后将该 ContextMenu 对象添加到 $NotifyIcon.ContextMenu。现在您可以更改和添加您喜欢的所有项目。
由于 powershell 在继续下一行代码之前等待表单关闭,$Form.ShowDialog()
将保持 运行space 活动,直到表单退出。
让我们看看这个令人讨厌的烂摊子:
$NotifyIcon.GetType().GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic).Invoke($NotifyIcon,$null)
这叫做反射。它允许您与 class 进行交互。用更简单的话来说。 ShowContextMenu 方法是私有的,不能 运行 通常来自 class 内部工作的外部。使用反射你可以调用它。
所以让我们再分解一下,因为这确实是您问的问题。
GetType() 会告诉你对象是什么。如果我做了 "HEY".gettype()
它会告诉我这个对象是一个字符串。在这种情况下 $NotifyIcon.GetType()
告诉我这是一个 NotifyIcon。发生了什么事它给我带回了一个 Type Class.
在这里我们看到 GetMethod("ShowContextMenu")
但让我在这里更深入地挖掘......我们怎么知道有一个名为 ShowContextMenu 的方法。那么我们可以做的是使用 GetMembers()
查看此 NotifyIcon class 的所有成员。现在 GetMembers()
实际上只是一个搜索...默认情况下只搜索 public 成员,因此我们需要搜索所有成员。要搜索的参数在枚举 [System.Reflection.BindingFlags]
和一些 Bitwise math.
中
$BitWise
[System.Reflection.BindingFlags].GetEnumNames() | %{
$BitWise = $BitWise -bor [System.Reflection.BindingFlags]$_
} | out-null
$NotifyIcon.GetType().GetMembers($BitWise) | ?{$_.Name -like "*Context*"} | select Name, MemberType
这表示查找名称中包含单词 Context 的所有项目,并显示其全名及其类型。作为回应,我们得到
Name MemberType
---- ----------
set_ContextMenu Method
get_ContextMenu Method
get_ContextMenuStrip Method
set_ContextMenuStrip Method
ShowContextMenu Method
ContextMenu Property
ContextMenuStrip Property
contextMenu Field
contextMenuStrip Field
我们可以看到 ShowContextMenu 也可以看到它的一个方法
所以现在我们需要直接获取那个方法。 getMethod()
来了,这只是另一种搜索,它返回 1 个项目而不是所有项目。所以
GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)
Get Method ShowContextMenu 它将是 Private 所以 NonPublic 并且必须先创建 class 的实例才能 运行 所以 Instance.
.Invoke($NotifyIcon,$null)
然后我们通过告诉它哪个控件具有我们想要的方法来调用该方法 运行 并传递任何参数 none 所以 $null.
你就是这样做的。
我有以下使用 PowerShell 的 NotifyIcon 运行:
这是右击图标打开的上下文菜单,目前只显示退出:
我想知道如何添加两个事件处理程序:
- 运行图标有左键点击事件时的函数
- 在右键菜单中添加另一个选项,该选项也运行一个功能
我已经在网上搜索了一个多小时,并且使用来自 5 或 6 个不同网站的旧代码尝试了大约 20 种不同的变体(都显示了截然不同的示例)。除了头疼,我一无所获。谁能提供任何 guidance/direction?
$ProgramDataPath = "$ENV:ProgramData\test"
$ProgramFilesPath = "${env:ProgramFiles(x86)}\test"
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$STForm = New-Object System.Windows.Forms.form
$NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$ContextMenu = New-Object System.Windows.Forms.ContextMenu
$MenuItem = New-Object System.Windows.Forms.MenuItem
$Timer = New-Object System.Windows.Forms.Timer
$HealthyIcon = New-Object System.Drawing.Icon("$ProgramFilesPath\icons\healthy.ico")
$UnhealthyIcon = New-Object System.Drawing.Icon("$ProgramFilesPath\icons\unhealthy.ico")
$STForm.ShowInTaskbar = $false
$STForm.WindowState = "minimized"
$NotifyIcon.Icon = $HealthyIcon
$NotifyIcon.ContextMenu = $ContextMenu
$NotifyIcon.ContextMenu.MenuItems.AddRange($MenuItem)
$NotifyIcon.Visible = $True
# We need to avoid using Start-Sleep as this freezes the GUI. Instead, we'll utilitse the .NET forms timer, it repeats a function at a set interval.
$Timer.Interval = 300000 # (5 min)
$Timer.add_Tick({ Load-Config })
$Timer.start()
# This will appear as a right click option on the system tray icon
$MenuItem.Index = 0
$MenuItem.Text = "Exit"
$MenuItem.add_Click({
$Timer.Stop()
$NotifyIcon.Visible = $False
$STForm.close()
})
function Load-Config
{
#Get-Content some Data from a file here
if ($warn)
{
$NotifyIcon.Icon = $UnhealthyIcon
$NotifyIcon.ShowBalloonTip(30000, "Attention!", "Some data from a file here...", [system.windows.forms.ToolTipIcon]"Warning")
Remove-Variable warn
}
else
{
$NotifyIcon.Icon = $HealthyIcon
}
}
Load-Config
[void][System.Windows.Forms.Application]::Run($STForm)
让我们谈谈您真正需要什么。看起来你有很多不需要的部件,比如计时器等等。您只需要一个 运行 空间。一个开放的形式将保持 运行 空间开放而不需要那个计时器。确保 $Form.ShowDialog()
是最后一个 运行.
那么让我们继续 NotifyIcon 弹出窗口。使弹出窗口发生的方法是私有的,这意味着我们需要通过反射来访问它。我们还需要在 MouseDown
上将通知图标的事件设置为 运行,并单击按钮 $_.button
确保将 $NotifyIcon.Icon
设置为图标,否则通知图标不会显示。
工作脚本
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.ShowInTaskbar = $true
$form.WindowState = [System.Windows.WindowState]::Normal
$MenuItemLeft = New-Object System.Windows.Forms.MenuItem
$MenuItemLeft.Text = "Left Exit"
$MenuItemLeft.add_Click({
$NotifyIcon.Visible = $False
$form.Close()
$NotifyIcon.Dispose()
})
$ContextMenuLeft = New-Object System.Windows.Forms.ContextMenu
$ContextMenuLeft.MenuItems.Add($MenuItemLeft)
$MenuItemRight = New-Object System.Windows.Forms.MenuItem
$MenuItemRight.Text = "Right Exit"
$MenuItemRight.add_Click({
$NotifyIcon.Visible = $False
$form.Close()
$NotifyIcon.Dispose()
})
$ContextMenuRight = New-Object System.Windows.Forms.ContextMenu
$ContextMenuRight.MenuItems.Add($MenuItemRight)
$NotifyIcon= New-Object System.Windows.Forms.NotifyIcon
$NotifyIcon.Icon = "C:\Test\Test.ico"
$NotifyIcon.ContextMenu = $ContextMenuRight
$NotifyIcon.add_MouseDown({
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::left ) {
$NotifyIcon.contextMenu = $ContextMenuLeft
}else{
$NotifyIcon.contextMenu = $ContextMenuRight
}
$NotifyIcon.GetType().GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic).Invoke($NotifyIcon,$null)
})
$NotifyIcon.Visible = $True
$form.ShowDialog()
$NotifyIcon.Dispose()
我重读了你的 post 所以我会为你提供最重要的部分
要让 Powershell 运行 您的命令,运行空格必须处于活动状态。 Runspace 接受 powershell 命令并将它们转化为实际操作。
由于您为此使用了 powershell,因此通知图标操作依赖于 运行空间来解释这些操作。
NotifyIcon 基本上只是角落里的一个图标,可以弹出气球通知或上下文菜单。
因此,当您查看时,您会看到 $NotifyIcon.ContextMenu
是一个包含 ContextMenu 对象 的 属性。上下文菜单对象包含 菜单项。
因此只需将 MenuItems 添加到 ContextMenu 对象,然后将该 ContextMenu 对象添加到 $NotifyIcon.ContextMenu。现在您可以更改和添加您喜欢的所有项目。
由于 powershell 在继续下一行代码之前等待表单关闭,$Form.ShowDialog()
将保持 运行space 活动,直到表单退出。
让我们看看这个令人讨厌的烂摊子:
$NotifyIcon.GetType().GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic).Invoke($NotifyIcon,$null)
这叫做反射。它允许您与 class 进行交互。用更简单的话来说。 ShowContextMenu 方法是私有的,不能 运行 通常来自 class 内部工作的外部。使用反射你可以调用它。
所以让我们再分解一下,因为这确实是您问的问题。
GetType() 会告诉你对象是什么。如果我做了 "HEY".gettype()
它会告诉我这个对象是一个字符串。在这种情况下 $NotifyIcon.GetType()
告诉我这是一个 NotifyIcon。发生了什么事它给我带回了一个 Type Class.
在这里我们看到 GetMethod("ShowContextMenu")
但让我在这里更深入地挖掘......我们怎么知道有一个名为 ShowContextMenu 的方法。那么我们可以做的是使用 GetMembers()
查看此 NotifyIcon class 的所有成员。现在 GetMembers()
实际上只是一个搜索...默认情况下只搜索 public 成员,因此我们需要搜索所有成员。要搜索的参数在枚举 [System.Reflection.BindingFlags]
和一些 Bitwise math.
$BitWise
[System.Reflection.BindingFlags].GetEnumNames() | %{
$BitWise = $BitWise -bor [System.Reflection.BindingFlags]$_
} | out-null
$NotifyIcon.GetType().GetMembers($BitWise) | ?{$_.Name -like "*Context*"} | select Name, MemberType
这表示查找名称中包含单词 Context 的所有项目,并显示其全名及其类型。作为回应,我们得到
Name MemberType
---- ----------
set_ContextMenu Method
get_ContextMenu Method
get_ContextMenuStrip Method
set_ContextMenuStrip Method
ShowContextMenu Method
ContextMenu Property
ContextMenuStrip Property
contextMenu Field
contextMenuStrip Field
我们可以看到 ShowContextMenu 也可以看到它的一个方法
所以现在我们需要直接获取那个方法。 getMethod()
来了,这只是另一种搜索,它返回 1 个项目而不是所有项目。所以
GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)
Get Method ShowContextMenu 它将是 Private 所以 NonPublic 并且必须先创建 class 的实例才能 运行 所以 Instance.
.Invoke($NotifyIcon,$null)
然后我们通过告诉它哪个控件具有我们想要的方法来调用该方法 运行 并传递任何参数 none 所以 $null.
你就是这样做的。