简体   繁体   中英

PowerShell NotifyIcon Context Menu

I have the following NotifyIcon running using PowerShell:

NotifyIcon的

This is the context menu opened by right clicking the icon, it's just showing exit at the moment:

右键单击NotifyIcon

I would like to know how I can add two event handlers:

  1. Run a function when there is a left click event on the icon
  2. Add another option to the right click menu that also runs a function

I've been searching the web for over an hour and I have tried about 20 different variations using old code from 5 or 6 different websites (all showing wildly different examples). I have gained nothing but a headache. Can anyone offer any 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)

Lets talk about what you really need. It looks like you have alot of unneeded parts like a timer and so on. All you need is a runspace. A Open Form will keep the runspace open without the need for that timer. Make sure the $Form.ShowDialog() is the last thing run.

So lets move on to the NotifyIcon popup. The method that makes that popup happen is private, which means we will need to reach it through reflection. We will also need to set the event for the Notify Icon to run on MouseDown as well as get the button clicked $_.button

Make sure you set the $NotifyIcon.Icon to a Icon or else the Notify Icon wont show up.

Working Script

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()

I Reread your post so ill provide you with the most important parts

For Powershell to run your commands a runspace must be active. A Runspace takes powershell commands and turns them into real actions.

Since you went with powershell for this then the notifyicons actions are dependent on a runspace to interpret those actions.

A NotifyIcon is basically just a icon in the corner that can popup a balloon notification or Context Menu.

So when you look you will see $NotifyIcon.ContextMenu that is a property that holds a ContextMenu Object . A Context Menu Object contains Menu Items .

So just add MenuItems to a ContextMenu Object and then add that ContextMenu Object to $NotifyIcon.ContextMenu. Now you can change and add all the items you like.

Since powershell waits for the form to close before moving on to the next line of code the $Form.ShowDialog() will keep the runspace alive until the form is exited.

Lets look at this nasty mess : $NotifyIcon.GetType().GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic).Invoke($NotifyIcon,$null)

This is called reflection. It allows you to interact with a class. In easier terms. ShowContextMenu method is private and cant be run normally from outside the internal workings of the class. Using reflection you can call it anyways.

So lets break it down just a little more since this is really what you asked about.

GetType() will get you what the object is. If i did "HEY".gettype() it would tell me that this object was a string. In this case $NotifyIcon.GetType() is telling me that this is a NotifyIcon. Whats happening its its bring me back a Type Class.

In this we see GetMethod("ShowContextMenu") but let me dig a little deeper here... How did we know there was a method called ShowContextMenu . Well what we can do is view all the members of this NotifyIcon class by using GetMembers() . Now GetMembers() is really just a search... by default only searches for public Members so we needs to search all Members. The parameters of what to search for is in an enum [System.Reflection.BindingFlags] and some 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

This says find all items that contain the word Context in its name and display its Full name and what Type it is. In response we get

Name                 MemberType
----                 ----------
set_ContextMenu          Method
get_ContextMenu          Method
get_ContextMenuStrip     Method
set_ContextMenuStrip     Method
ShowContextMenu          Method
ContextMenu            Property
ContextMenuStrip       Property
contextMenu               Field
contextMenuStrip          Field

We can see ShowContextMenu and we can also see its a method

So now we need to get that method directly. Here comes getMethod() which is just another search that brings back 1 item instead of all the items. So

GetMethod("ShowContextMenu",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)

Get Method ShowContextMenu it will be Private so NonPublic and a instance of the class has to be created before it can run so Instance.

.Invoke($NotifyIcon,$null)

Then we invoke the method by telling it which control has the method we want to run and pass any parameters which are none so $null.

And thats how you do it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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