简体   繁体   English

根据事件更改托盘图标

[英]change tray icon based on event

I have code that will start process in tray (task bar).我有将在托盘(任务栏)中启动进程的代码。 After right click on tray icon it will show menu.右键单击托盘图标后,它将显示菜单。 After clicking on first menu item winform window starts.单击第一个菜单项 winform 窗口启动后。 This winform shows status about the notepad process.此 winform 显示有关记事本进程的状态。 My goal is to change the tray icon based on the status of notepad (if notepad is running then show online.ico otherwise show offline.ico ).我的目标是根据记事本的状态更改托盘图标(如果记事本正在运行,则显示online.ico否则显示offline.ico )。 If I understand correct then my code is starting/stopping System.Windows.Forms.Timer every time winform window is opened/closed and I'm not sure if this is the best possible approach.如果我理解正确,那么每次打开/关闭 winform 窗口时我的代码都会启动/停止System.Windows.Forms.Timer ,我不确定这是否是最好的方法。 My guess is that I need to somehow start timer "outside" of OnMenuItem1ClickEventFn so it can somehow reload *.ico files.我的猜测是我需要以某种方式在OnMenuItem1ClickEventFn “外部”启动计时器,以便它可以以某种方式重新加载*.ico文件。 Following script is heavily inspired by this site:以下脚本深受网站的启发:

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

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


function OnMenuItem1ClickEventFn () {
    # Build Label object
    $Label = New-Object System.Windows.Forms.Label
        $Label.Name = "labelName"
        $Label.AutoSize = $True

    # Set and start timer
    $timer = New-Object System.Windows.Forms.Timer
    $timer.Interval = 1000
    $timer.Add_Tick({
        if ($Label){
            $Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
        }
    })
    $timer.Start()


    # 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.Add_Closing({ $timer.Dispose() })  # Dispose() also stops the timer.
        $Form.Controls.Add($Label)               # Add label to form
        $form.ShowDialog()| Out-Null             # Show the Form
}


function OnMenuItem4ClickEventFn () {
    $Main_Tool_Icon.Visible = $false
    $window.Close()
    Stop-Process $pid
}


function create_taskbar_menu{
    # Create menu items
    $Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
    $Main_Tool_Icon.Text = "Icon Text"
    $Main_Tool_Icon.Icon = $icon
    $Main_Tool_Icon.Visible = $true

    $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
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
# $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")


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
[void][System.Windows.Forms.Application]::Run($appContext)

EDITED: working solution based on @BACON answer编辑:基于@BACON 答案的工作解决方案

# 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
}

You already have code to set the icon used by the NotifyIcon ...您已经有代码来设置NotifyIcon使用的图标...

$Main_Tool_Icon.Icon = $icon

...and to define the icons to use... ...并定义要使用的图标...

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

...and to periodically test if Notepad is running and respond appropriately... ...并定期测试Notepad是否正在运行并做出适当的响应...

$timer.Add_Tick({
    if ($Label){
        $Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
    }
})

You just need to combine them with a couple additional tweaks...你只需要将它们与一些额外的调整结合起来......

  • Store each icon in its own variable with a descriptive name so its easy to switch between them.使用描述性名称将每个图标存储在自己的变量中,以便在它们之间轻松切换。
  • $Main_Tool_Icon needs to be defined outside the scope of create_taskbar_menu so it can be accessed inside of OnMenuItem1ClickEventFn . $Main_Tool_Icon需要在create_taskbar_menu范围之外定义,以便它可以在OnMenuItem1ClickEventFn内部访问。 I moved the creation and initialization to just before the call to create_taskbar_menu , but you could also initialize it inside of create_taskbar_menu or some other function.我将创建和初始化移动到调用create_taskbar_menu之前,但您也可以在create_taskbar_menu或其他一些函数内部对其进行初始化。

That ends up looking like this...最终看起来像这样......

# 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

create_taskbar_menu

...and this... ...和这个...

$timer.Add_Tick({
    if ($Label){
        # Change the text and icon with one test
        $Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
            "Notepad is running", $onlineIcon
        } else {
            "Notepad is NOT running", $offlineIcon
        }
    }
})

You'll see that an icon is selected based on the result of Test-Notepad both when $Main_Tool_Icon is initialized and when the Tick event is raised.您将看到在初始化$Main_Tool_Icon和引发Tick事件时根据Test-Notepad的结果选择了一个图标。

As for disposing $timer when $Form is closing...至于在$Form关闭时处理$timer ......

$Form.Add_Closing({ $timer.Dispose() })

...that is almost an appropriate place to do that, however... ......这几乎是一个合适的地方,但是......

  • The Closing event is raised essentially when the form has been requested to close; Closing事件本质Closing是在表单被请求关闭时引发的; it provides an opportunity for that request to be canceled.它为取消该请求提供了机会。 The Closed event would be more appropriate because it is raised when the form has actually been closed. Closed事件更合适,因为它是在表单实际关闭时引发的。
  • The documentation states that both the Closing and Closed events are obsolete.文档指出ClosingClosed事件都已过时。 Use the FormClosed event instead.请改用FormClosed事件

Also, in OnMenuItem4ClickEventFn you are calling $window.Close() even though $window is not defined;此外,在OnMenuItem4ClickEventFn ,即使未定义$window ,您也正在调用$window.Close() I think you meant $Form.Close() .我想你的意思是$Form.Close() An alternative that I think is a bit cleaner would be to have clicking the Exit menu item merely exit the Windows Forms application loop ...我认为更简洁的另一种方法是单击“ Exit菜单项仅退出 Windows 窗体应用程序循环......

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

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

...and then you can put your cleanup/teardown code in a finally block at the end of the script... ...然后您可以将清理/拆卸代码放在脚本末尾的finally块中...

try
{
    # This call returns when [System.Windows.Forms.Application]::Exit() is called
    [System.Windows.Forms.Application]::Run($appContext)
}
finally
{
    # $timer would also have to be defined at the script scope
    # (outside of OnMenuItem1ClickEventFn) for this to work
    $timer.Dispose()

    # $Form, $Label, $Main_Tool_Icon, $onlineIcon, etc. would all be candidates for disposal...

    # Exit the entire PowerShell process
    Stop-Process $pid
}

The following is complete code that incorporates the changes mentioned above and works for me...以下是包含上述更改并适用于我的完整代码...

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

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


function OnMenuItem1ClickEventFn () {
    $timer.Start()

    # 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.Add_Closing({ $timer.Dispose() })  # Dispose() also stops the timer.
        $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
        }
    }
})

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, $form, $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
}

Hi I know its against the rules to ask more questions here, therefore i asked a follow up questions related to this.嗨,我知道在这里提出更多问题是违反规则的,因此我提出了与此相关的后续问题。 Maybe the knowledgeable mr Lance has an answer for me too.. thank you也许知识渊博的兰斯先生也为我解答了..谢谢

My Question link 我的问题链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM