Invoke Notifyicon's Context Menu

Invoke NotifyIcon's Context Menu

You would normally handle the MouseClick event to detect the click and call the ContextMenuStrip.Show() method:

    private void notifyIcon1_MouseClick(object sender, MouseEventArgs e) {
contextMenuStrip1.Show(Control.MousePosition);
}

But that doesn't actually work properly, the CMS won't close when you click outside of it. Underlying issue is a Windows quirk (aka "bug") that is described in this KB article.

Invoking this workaround in your own code is pretty painful, the pinvoke is unpleasant. The NotifyIcon class has this workaround in its ShowContextMenu() method, they just made it difficult to get to since it is a private method. Reflection can bypass that restriction. I discovered this hack 5 years ago and nobody reported a problem with it yet. Set the NFI's ContextMenuStrip property and implement the MouseUp event like this:

using System.Reflection;
...
private void notifyIcon1_MouseUp(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
MethodInfo mi = typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(notifyIcon1, null);
}
}

PowerShell NotifyIcon Context Menu

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.

How can I use a modern Windows 10 context menu with a Win32 NotifyIcon?

Apart from the great answer of Nico Zhu, I think that the Windows 10 taskbar context menu is just the normal one only styled differently.

This opinion is made even stronger by the fact that MSFT created the “dark” theme for the File Explorer simply by restyling it.

It is all only a different style, not new controls.

Show NotifyIcon Context Menu and Control Its Position?

Well, I just discovered that there are existing programs that exhibit this same behavior. I just went through all the icons in my system tray and about half of them do it. If you left-click the icon and then move the mouse during the delay before the menu appears, the menu will appear at the last mouse location, wherever that is on the screen. Snagit is one application that does this. Outlook is the only program in my tray that always shows the menu where I clicked the icon. But Snagit looks like it's using a .NET ContextMenuStrip, while Outlook is probably using a native menu.

So either this is standard behavior, or it's a problem that no one else has been able to solve either. And as a user, I've never noticed this behavior until yesterday when I was testing my own application. So I guess it's not that big of a deal and I won't worry about it.

Add a function to contextMenu item at notifyIcon

There is a second parameter to Add that lets you assign an eventhandler:

contextMenu1.MenuItems.Add("Exit", ExitApplication);
// or using an anonymous method:
contextMenu1.MenuItems.Add("Exit", (s,e) => Application.Exit());

In the first example, ExitApplication is your event handler:

private void ExitApplication(object sender, EventArgs e) 
{
// exit..
}

You can also construct a MenuItem first and assign the eventhandler in the constructor, if you prefer.

Right clicking notifyicon in system tray does not show contextmenu

The line contextMenuStrip1.SuspendLayout(); stopped the contextmenustrip from showing. I had copied the code from a generated windows form, so I don't know why that line of code was in there.



Related Topics



Leave a reply



Submit