Winforms 根据事件更改托盘图标

Winforms 根据事件更改托盘图标,winforms,powershell,Winforms,Powershell,我有代码,将启动托盘(任务栏)中的进程。右键单击托盘图标后,将显示菜单。单击第一个菜单项后,winform窗口启动。此winform显示记事本进程的状态。我的目标是根据记事本的状态更改托盘图标(如果记事本正在运行,则显示online.ico,否则显示offline.ico)。如果我理解正确,那么每次打开/关闭winform窗口时,我的代码都会启动/停止System.Windows.Forms.Timer,我不确定这是否是最好的方法。我猜我需要在OnMenuItem1ClickEventFn之外启

我有代码,将启动托盘(任务栏)中的进程。右键单击托盘图标后,将显示菜单。单击第一个菜单项后,winform窗口启动。此winform显示记事本进程的状态。我的目标是根据记事本的状态更改托盘图标(如果记事本正在运行,则显示
online.ico
,否则显示
offline.ico
)。如果我理解正确,那么每次打开/关闭winform窗口时,我的代码都会启动/停止
System.Windows.Forms.Timer
,我不确定这是否是最好的方法。我猜我需要在
OnMenuItem1ClickEventFn
之外启动计时器,以便它能够以某种方式重新加载
*.ico
文件。以下脚本深受该网站的启发:

编辑:基于@BACON-answer的工作解决方案

# Toggle following two lines
Set-StrictMode -Version Latest
# Set-StrictMode -Off

Add-Type -AssemblyName System.Windows.Forms    
Add-Type -AssemblyName System.Drawing

function Test-Notepad {
    [bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}


function OnMenuItem1ClickEventFn () {
    # Build Form object
    $Form = New-Object System.Windows.Forms.Form
        $Form.Text = "My Form"
        $Form.Size = New-Object System.Drawing.Size(200,200)
        $Form.StartPosition = "CenterScreen"
        $Form.Topmost = $True
        $Form.Controls.Add($Label)               # Add label to form
        $form.ShowDialog()| Out-Null             # Show the Form
}


function OnMenuItem4ClickEventFn () {
    $Main_Tool_Icon.Visible = $false

    [System.Windows.Forms.Application]::Exit()
}


function create_taskbar_menu{
    # Create menu items
    $MenuItem1 = New-Object System.Windows.Forms.MenuItem
    $MenuItem1.Text = "Menu Item 1"

    $MenuItem2 = New-Object System.Windows.Forms.MenuItem
    $MenuItem2.Text = "Menu Item 2"

    $MenuItem3 = New-Object System.Windows.Forms.MenuItem
    $MenuItem3.Text = "Menu Item 3"

    $MenuItem4 = New-Object System.Windows.Forms.MenuItem
    $MenuItem4.Text = "Exit"


    # Add menu items to context menu
    $contextmenu = New-Object System.Windows.Forms.ContextMenu
    $Main_Tool_Icon.ContextMenu = $contextmenu
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)


    $MenuItem4.add_Click({OnMenuItem4ClickEventFn})
    $MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}


$Current_Folder = split-path $MyInvocation.MyCommand.Path

# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')    | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework')   | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing')          | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll")  | out-null

# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")

$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true

# Build Label object
$Label = New-Object System.Windows.Forms.Label
    $Label.Name = "labelName"
    $Label.AutoSize = $True

# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
    if ($Label){
        $Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
            "Notepad is running", $onlineIcon
        } else {
            "Notepad is NOT running", $offlineIcon
        }
    }
})
$timer.Start()

create_taskbar_menu

# Make PowerShell Disappear - Thanks Chrissy
$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)

# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://docs.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()

# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
    [System.Windows.Forms.Application]::Run($appContext)    
}
finally
{
    foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
    {
        # The following test returns $false if $component is
        # $null, which is really what we're concerned about
        if ($component -is [System.IDisposable])
        {
            $component.Dispose()
        }
    }

    Stop-Process -Id $PID
}

您已经有了设置
NotifyIcon
使用的图标的代码

$Main\u Tool\u Icon.Icon=$Icon
…并定义要使用的图标

#选择要在systray中显示的图标
$icon=[System.Drawing.icon]::提取关联图标($Current\u Folder/icons/online.ico)
#记事本未运行时使用此图标
#$icon=[System.Drawing.icon]::提取关联图标($Current\u Folder/icons/offline.ico)
…并定期测试记事本是否正在运行并做出适当响应

$timer.Add\u勾选({
如果($标签){
$Label.Text=如果(测试记事本){“记事本正在运行”}否则{“记事本没有运行”}
}
})
你只需要把它们和一些额外的调整结合起来

  • 用描述性名称将每个图标存储在自己的变量中,以便在它们之间轻松切换
  • $Main\u Tool\u图标
    需要在
    创建任务栏菜单
    的范围之外定义,以便可以在
    OnMenuItem1ClickEventFn的内部访问它。我将创建和初始化移动到调用
    创建任务栏菜单
    之前,但您也可以在
    创建任务栏菜单
    或其他一些功能中对其进行初始化
最后看起来像这样

#选择要在systray中显示的图标
$onlineIcon=[System.Drawing.Icon]::ExtractAssociatedIcon($Current\u Folder/icons/online.ico”)
#记事本未运行时使用此图标
$offlineIcon=[System.Drawing.Icon]::ExtractAssociatedIcon(“$Current\u Folder/icons/offline.ico”)
$Main\u Tool\u Icon=新对象System.Windows.Forms.NotifyIcon
$Main\u Tool\u Icon.Text=“Icon Text”
$Main_Tool_Icon.Icon=if(测试记事本){$onlineIcon}else{$offlineIcon}
$Main\u Tool\u Icon.Visible=$true
创建任务栏菜单
…还有这个

$timer.Add\u勾选({
如果($标签){
#通过一次测试更改文本和图标
$Label.Text、$Main\u Tool\u Icon.Icon=if(测试记事本){
“记事本正在运行”,$Online图标
}否则{
“记事本未运行”,$offlineIcon
}
}
})
在初始化
$Main\u Tool\u icon
和引发
勾选事件时,您将看到根据
测试记事本的结果选择了一个图标

$Form
关闭时处理
$timer

$Form.Add\u关闭({$timer.Dispose()})
…这几乎是一个合适的地方,但是

  • 当要求关闭表单时,基本上会引发该问题;它提供了取消该请求的机会。更合适的方法是在表单实际关闭时引发
  • 文档说明
    关闭
    关闭
    事件都已过时。改用新的
另外,在
OnMenuItem4ClickEventFn
中,您正在调用
$window.Close()
,即使未定义
$window
;我想你的意思是
$Form.Close()
。我认为稍微干净一点的另一种选择是,只需单击
退出
菜单项

函数OnMenuItem4ClickEventFn(){ $Main\u Tool\u Icon.Visible=$false [System.Windows.Forms.Application]::Exit() }
…然后您可以将清理/拆卸代码放入脚本末尾的
finally
块中

试试看
{
#调用[System.Windows.Forms.Application]::Exit()时返回此调用
[System.Windows.Forms.Application]::运行($appContext)
}
最后
{
#$timer还必须在脚本范围内定义
#(在OnMenuItem1ClickEventFn之外)以使其工作
$timer.Dispose()
#$Form、$Label、$Main\u Tool\u Icon、$onlineIcon等都将是处理的候选项。。。
#退出整个PowerShell进程
停止进程$pid
}

以下是包含上述更改的完整代码,适用于我

添加类型-AssemblyName System.Windows.Forms
添加类型-AssemblyName System.Drawing
功能测试记事本{
[bool](获取进程-名称“记事本”-ErrorAction SilentlyContinue)
}
函数OnMenuItem1ClickEventFn(){
$timer.Start()
#生成表单对象
$Form=新对象System.Windows.Forms.Form
$Form.Text=“我的表格”
$Form.Size=新对象系统.Drawing.Size(200200)
$Form.StartPosition=“中心屏幕”
$Form.Topmost=$True
$Form.Add_Closing({$timer.Dispose()})#Dispose()也会停止计时器。
$Form.Controls.Add($Label)#将标签添加到表单
$form.ShowDialog()| Out Null#显示表单
}
函数OnMenuItem4ClickEventFn(){
$Main\u Tool\u Icon.Visible=$false
[System.Windows.Forms.Application]::Exit()
}
功能创建\u任务栏\u菜单{
#创建菜单项
$MenuItem1=新对象System.Windows.Forms.MenuItem
$MenuItem1.Text=“菜单项1”
$MenuItem2=新对象System.Windows.Forms.MenuItem
# Toggle following two lines
Set-StrictMode -Version Latest
# Set-StrictMode -Off

Add-Type -AssemblyName System.Windows.Forms    
Add-Type -AssemblyName System.Drawing

function Test-Notepad {
    [bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}


function OnMenuItem1ClickEventFn () {
    # Build Form object
    $Form = New-Object System.Windows.Forms.Form
        $Form.Text = "My Form"
        $Form.Size = New-Object System.Drawing.Size(200,200)
        $Form.StartPosition = "CenterScreen"
        $Form.Topmost = $True
        $Form.Controls.Add($Label)               # Add label to form
        $form.ShowDialog()| Out-Null             # Show the Form
}


function OnMenuItem4ClickEventFn () {
    $Main_Tool_Icon.Visible = $false

    [System.Windows.Forms.Application]::Exit()
}


function create_taskbar_menu{
    # Create menu items
    $MenuItem1 = New-Object System.Windows.Forms.MenuItem
    $MenuItem1.Text = "Menu Item 1"

    $MenuItem2 = New-Object System.Windows.Forms.MenuItem
    $MenuItem2.Text = "Menu Item 2"

    $MenuItem3 = New-Object System.Windows.Forms.MenuItem
    $MenuItem3.Text = "Menu Item 3"

    $MenuItem4 = New-Object System.Windows.Forms.MenuItem
    $MenuItem4.Text = "Exit"


    # Add menu items to context menu
    $contextmenu = New-Object System.Windows.Forms.ContextMenu
    $Main_Tool_Icon.ContextMenu = $contextmenu
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
    $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)


    $MenuItem4.add_Click({OnMenuItem4ClickEventFn})
    $MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}


$Current_Folder = split-path $MyInvocation.MyCommand.Path

# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')    | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework')   | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing')          | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll")  | out-null

# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")

$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true

# Build Label object
$Label = New-Object System.Windows.Forms.Label
    $Label.Name = "labelName"
    $Label.AutoSize = $True

# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
    if ($Label){
        $Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
            "Notepad is running", $onlineIcon
        } else {
            "Notepad is NOT running", $offlineIcon
        }
    }
})
$timer.Start()

create_taskbar_menu

# Make PowerShell Disappear - Thanks Chrissy
$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)

# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://docs.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()

# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
    [System.Windows.Forms.Application]::Run($appContext)    
}
finally
{
    foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
    {
        # The following test returns $false if $component is
        # $null, which is really what we're concerned about
        if ($component -is [System.IDisposable])
        {
            $component.Dispose()
        }
    }

    Stop-Process -Id $PID
}