问题
I made a little console app which locks the Mouse to the first screen.
Now I want to create a TrayIcon with a ContextMenu to close the application.
In debug-mode, I can see that the ContextMenu has two Items, just like it should, but it doesn't display the ContextMenu.
An EventHandler shouldn't be needed from what I've read so far.
My code:
static void GenerateTrayIcon()
{
ContextMenu trayiconmenu = new ContextMenu();
trayiconmenu.MenuItems.Add(0, new MenuItem("Show", new EventHandler(Show_Click)));
trayiconmenu.MenuItems.Add(1, new MenuItem("Exit", new EventHandler(Exit_Click)));
NotifyIcon TrayIcon = new NotifyIcon();
TrayIcon.Icon = new Icon("Path to .ico");
TrayIcon.Text = "Cursor is locked to primary screen";
TrayIcon.Visible = true;
TrayIcon.ContextMenu = trayiconmenu;
}
static void Exit_Click(object sender, EventArgs e)
{
Environment.Exit(0);
}
static void Show_Click(object sender, EventArgs e)
{
// Do something
}
回答1:
To make the NotifyIcon work, you have to start a Message Loop, usually calling Application.Run(). The calling method is also usually marked as single-threaded ([STAThread]
).
That's more or less just it.
► Of course you need to dispose of the objects you created. In this case, the NotifyIcon object and the ContextMenu. You can also call Dispose() on the Icon object, in case it's just set to null
in the internal NativeWindow.
In the example here, the ConsoleNotifyIcon
class object is used to run the Message Loop and receive the ContextMenu items mouse events.
In this case, the Exit click handler just signals the main Thread that an exit request has been queued. It also removes the NotifyIcon from the Notification Area.
The Main Thread can then acknowledge the request and terminate.
It also makes sure, before exiting, that the NotifyIcon has been disposed.
► You can decide to actually use Environment.Exit()
in the Exit event, but it's a quite brutal exit procedure. Evaluate whether it won't disrupt the Main Thread operations.
[STAThread]
static void Main(string[] args)
{
// Start the NotifyIcon Thread
ConsoleNotifyIcon.GenerateTrayIcon();
// Do stuff. Check whether an Exit request has been queued
while (!ConsoleNotifyIcon.IsAppCloseRequest) {
string input = Console.ReadLine();
// Other operations...
}
if (!ConsoleNotifyIcon.IsAppCloseRequest) {
ConsoleNotifyIcon.Dispose();
}
}
using System.Threading.Tasks;
using System.Windows.Forms;
public class ConsoleNotifyIcon
{
// Store these objects as private Fields
private static NotifyIcon trayIcon;
private static ContextMenu trayContextMenu;
// The main public method starts a new Task. It uses a ThreadPool Thread.
[STAThread]
public static void GenerateTrayIcon() => Task.Run(() => StartTrayIcon());
private static void StartTrayIcon() {
trayContextMenu = new ContextMenu();
trayContextMenu.MenuItems.Add(0, new MenuItem("Show", Show_Click));
trayContextMenu.MenuItems.Add(1, new MenuItem("Exit", Exit_Click));
trayIcon = new NotifyIcon() {
ContextMenu = trayContextMenu
Icon = [Some Icon], // Possibly, use an Icon Resource
Text = "Cursor is locked to primary screen",
Visible = true,
};
// Setup completed. Starts the Message Loop
Application.Run();
}
public static bool IsAppCloseRequest = false;
static void Exit_Click(object sender, EventArgs e) {
// Dispose of the objects created.
// Also removes the NotifyIcon from the Tray Notification Area
Dispose();
// Signals the App Exit request. You could also use an Event.
IsAppCloseRequest = true;
}
static void Show_Click(object sender, EventArgs e) {
// Do something
}
public static void Dispose() {
trayIcon.Icon?.Dispose();
trayIcon.Dispose();
trayContextMenu.Dispose();
}
}
回答2:
With Jimi's answer i got it sorted. I ran the method which creates the TrayIcon and the ContextMenu in another thread, just like this:
static void Main(string[] args)
{
Thread thread = new Thread(GenerateTrayIcon);
thread.Start();
while (1 > 0)
{
int currentX = Cursor.Position.X;
int currentY = Cursor.Position.Y;
int screenX = Screen.PrimaryScreen.Bounds.Width;
int screenY = Screen.PrimaryScreen.Bounds.Height;
if (currentX > screenX)
{
Cursor.Position = new Point(screenX, currentY);
}
if (currentY > screenY)
{
Cursor.Position = new Point(screenY, currentX);
}
}
}
static void GenerateTrayIcon()
{
trayiconmenu.MenuItems.Add(0, new MenuItem("Exit", new EventHandler(Exit_Click)));
TrayIcon.Icon = new Icon("C:\\Users\\lukas\\Desktop\\auge.ico");
TrayIcon.Text = "Cursor is locked to primary screen";
TrayIcon.Visible = true;
TrayIcon.ContextMenu = trayiconmenu;
Application.Run();
}
static void Exit_Click(object sender, EventArgs e)
{
Environment.Exit(0);
TrayIcon.Dispose();
}
}
}
来源:https://stackoverflow.com/questions/65048083/adding-menuitems-to-contextmenu-for-a-trayicon-in-a-console-app