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
How to Save Picturebox.Image to File
Sqldatasourceenumerator.Instance.Getdatasources() Does Not Locate Local SQL Server 2008 Instance
Implementing Hoey Shamos Algorithm with C#
Ef Code First - How to Set Identity Seed
How to Make a Background Worker Thread Set to Single Thread Apartment
How to Use Httpwebrequest to Pull Image from Website to Local File
Generate N Random and Unique Numbers Within a Range
Does Garbage Collection Run During Debug
How Math.Pow (And So On) Actually Works
End of Central Directory Record Could Not Be Found
How to Target Attributes for a Record Class
Button Inside a Winforms Textbox
Why Is It Impossible to Override a Getter-Only Property and Add a Setter
Namespace and Class with the Same Name
How Do Closures Work Behind the Scenes? (C#)