代码之家  ›  专栏  ›  技术社区  ›  Wakan Tanka

根据事件更改托盘图标

  •  0
  • Wakan Tanka  · 技术社区  · 5 年前

    我有代码,将开始在托盘(任务栏)进程。右击托盘图标后,将显示菜单。单击第一个菜单项后,winform窗口开始。此winform显示记事本进程的状态。我的目标是根据记事本的状态更改托盘图标(如果记事本正在运行,则显示 online.ico 否则显示 offline.ico ). 如果我理解正确,那么我的代码正在启动/停止 System.Windows.Forms.Timer 每次打开/关闭winform窗口时,我不确定这是否是最好的方法。我想我需要在“外面”启动计时器 OnMenuItem1ClickEventFn 所以它可以重新加载 *.ico 文件夹。以下剧本的灵感来源于 this 地点:

    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)
    

    编辑:基于@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
    }
    
    0 回复  |  直到 5 年前
        1
  •  1
  •   Lance U. Matthews    5 年前

    您已经有代码来设置 NotifyIcon ...

    $Main_Tool_Icon.Icon = $icon
    

    …并定义要使用的图标。。。

    # 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")
    

    …并定期测试 Notepad 正在运行并正确响应。。。

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

    你只需要把它们和一些额外的调整结合起来。。。

    • 用描述性名称将每个图标存储在自己的变量中,以便在它们之间轻松切换。
    • $Main_Tool_Icon 需要在 create_taskbar_menu 这样就可以在 OnMenuItem1ClickEventFn . 我将创建和初始化移到调用 创建任务栏菜单 ,但也可以在 创建任务栏菜单 或者其他功能。

    最后看起来像这样。。。

    # 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
    

    …还有这个。。。

    $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
            }
        }
    })
    

    您将看到一个图标是基于 Test-Notepad 都是什么时候 $Main\u工具\u图标 Tick 事件已引发。

    至于处理 $timer 什么时候 $Form 正在关闭。。。

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

    ……就是这样 几乎 一个合适的地方,但是。。。

    • 这个 Closing event 基本上是在窗体 请求 关闭;它提供了取消该请求的机会。这个 Closed event 会更合适,因为它是在窗体实际关闭时引发的。
    • 文件表明 结束 关闭 事件已过时。使用 FormClosed event 相反。

    另外,在 OnMenuItem4ClickEventFn 你在打电话吗 $window.Close() 尽管 $window 我想你的意思是 $Form.Close() . 另一个我认为稍微干净一点的方法是点击 Exit 菜单项 exit the Windows Forms application loop ...

    function OnMenuItem4ClickEventFn () {
        $Main_Tool_Icon.Visible = $false
    
        [System.Windows.Forms.Application]::Exit()
    }
    

    …然后您可以将清理/拆卸代码放入 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
    }
    

    以下是完整的代码,其中包括上述更改,并为我工作。。。

    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
    }