将 MenuItem 添加到控制台应用程序中 TrayIcon 的上下文菜单
Adding MenuItems to Contextmenu for a TrayIcon in a Console app
我制作了一个小的控制台应用程序,它将鼠标锁定在第一个屏幕上。
现在我想创建一个带有 ContextMenu 的 TrayIcon 来关闭应用程序。
在调试模式下,我可以看到 ContextMenu 有两个 Item,就像它应该的那样,但它不显示 ContextMenu。
我的代码:
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
}
制作NotifyIcon work, you have to start a Message Loop, usually calling Application.Run()。调用方法通常也标记为单线程([STAThread]
).
大致就是这样。
▶ 当然你需要处理你创建的对象。在本例中,NotifyIcon 对象和 ContextMenu。您还可以在 Icon 对象上调用 Dispose(),以防它在内部 NativeWindow 中设置为 null
。
在此处的示例中,ConsoleNotifyIcon
class 对象用于 运行 消息循环并接收 ContextMenu 项目鼠标事件。
在这种情况下,退出点击处理程序 向主线程发出信号 退出请求已排队。它还从通知区域中删除了 NotifyIcon。
然后主线程可以确认请求并终止。
它还确保在退出之前,NotifyIcon 已被释放。
▶ 您可以在 CloseRequest
事件处理程序中使用 Environment.Exit()
。
在这里,AppDomain.ProcessExit event is handled to respond to Environment.Exit()
and SetConsoleCtrlHandler 处理其他退出原因(请参阅代码中的注释)。
在任何情况下,都会调用 CleanUp()
方法,以删除剩余的事件处理程序并处理 NotifyIcon 对象分配的资源。
private static readonly object closeLocker = new object();
private static ConsoleEventDelegate closeHandler;
private delegate bool ConsoleEventDelegate(ExitReason closeReason);
private enum ExitReason
{
ControlC = 0,
ControlBreak = 1,
UserClose = 2,
UserLogoff = 5,
SystemShutdown = 6
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate HandlerRoutine, bool Add);
[STAThread]
static void Main(string[] args)
{
// Handles Close Button, CTRL-C, CTRL-Break, Logoff and ShutDown
closeHandler = new ConsoleEventDelegate(ConsoleEventHandler);
SetConsoleCtrlHandler(closeHandler, true);
// Handles Environment.Exit()
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
// Add a handler to the NotifyIcon CloseRequest event
ConsoleNotifyIcon.CloseRequest += NotifyIconCloseRequest;
// Create the NotifyIcon Icon in the Tray Notification Area
ConsoleNotifyIcon.GenerateTrayIcon();
// [...]
// Other processes
Console.ReadLine();
// Raises the ProcessExit event
Environment.Exit(0);
}
// Event raised by the NotifyIcon's Exit routine.
// Causes OnProcessExit to fire
private static void NotifyIconCloseRequest(object sender, EventArgs e) => Environment.Exit(0);
// Fires when Environment.Exit(0) is called
private static void OnProcessExit(object sender, EventArgs e) => CleanUp();
// Handles - Console Close Button, Control-C, Control-Break
// - System Log-off event, System ShutDown event
static bool ConsoleEventHandler(ExitReason reason)
{
SetConsoleCtrlHandler(closeHandler, false);
CleanUp();
return true;
}
// All Console Exit reasons end up here
private static void CleanUp()
{
// This is called from a different Thread
lock (closeLocker) {
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
ConsoleNotifyIcon.CloseRequest -= NotifyIconCloseRequest;
if (!ConsoleNotifyIcon.IsDisposed) {
ConsoleNotifyIcon.Dispose();
}
}
}
ConsoleNotifyIcon class(NotifyIcon 处理程序):
using System.Threading.Tasks;
using System.Windows.Forms;
public class ConsoleNotifyIcon
{
public static event EventHandler<EventArgs> CloseRequest;
// Store these objects as private Fields
private static NotifyIcon trayIcon;
private static ContextMenu trayContextMenu;
// The main public method starts a new Thread, in case a STA Thread is needed
// If not, you can just Task.Run() it
public static void GenerateTrayIcon()
{
var thread = new Thread(StartTrayIcon);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
[STAThread] // Reminder
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 { get; private set; }
public static bool IsDisposed { get; private set; }
static void Exit_Click(object sender, EventArgs e) {
// Sets the public property, it can be used to check the status
IsAppCloseRequest = true;
// Signals the Exit request, raising the CloseRequest event.
// The application may decide to delay the exit process, so calling Dispose()
// is handled by the subscribers of the event, as shown in the Console code
CloseRequest?.Invoke(null, EventArgs.Empty);
}
static void Show_Click(object sender, EventArgs e) {
// Do something
}
public static void Dispose() {
if (IsDisposed) return;
Application.ExitThread();
trayIcon?.Icon?.Dispose();
trayIcon?.Dispose();
trayContextMenu?.Dispose();
IsDisposed = true;
}
}
我制作了一个小的控制台应用程序,它将鼠标锁定在第一个屏幕上。
现在我想创建一个带有 ContextMenu 的 TrayIcon 来关闭应用程序。
在调试模式下,我可以看到 ContextMenu 有两个 Item,就像它应该的那样,但它不显示 ContextMenu。
我的代码:
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
}
制作NotifyIcon work, you have to start a Message Loop, usually calling Application.Run()。调用方法通常也标记为单线程([STAThread]
).
大致就是这样。
▶ 当然你需要处理你创建的对象。在本例中,NotifyIcon 对象和 ContextMenu。您还可以在 Icon 对象上调用 Dispose(),以防它在内部 NativeWindow 中设置为 null
。
在此处的示例中,ConsoleNotifyIcon
class 对象用于 运行 消息循环并接收 ContextMenu 项目鼠标事件。
在这种情况下,退出点击处理程序 向主线程发出信号 退出请求已排队。它还从通知区域中删除了 NotifyIcon。
然后主线程可以确认请求并终止。
它还确保在退出之前,NotifyIcon 已被释放。
▶ 您可以在 CloseRequest
事件处理程序中使用 Environment.Exit()
。
在这里,AppDomain.ProcessExit event is handled to respond to Environment.Exit()
and SetConsoleCtrlHandler 处理其他退出原因(请参阅代码中的注释)。
在任何情况下,都会调用 CleanUp()
方法,以删除剩余的事件处理程序并处理 NotifyIcon 对象分配的资源。
private static readonly object closeLocker = new object();
private static ConsoleEventDelegate closeHandler;
private delegate bool ConsoleEventDelegate(ExitReason closeReason);
private enum ExitReason
{
ControlC = 0,
ControlBreak = 1,
UserClose = 2,
UserLogoff = 5,
SystemShutdown = 6
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate HandlerRoutine, bool Add);
[STAThread]
static void Main(string[] args)
{
// Handles Close Button, CTRL-C, CTRL-Break, Logoff and ShutDown
closeHandler = new ConsoleEventDelegate(ConsoleEventHandler);
SetConsoleCtrlHandler(closeHandler, true);
// Handles Environment.Exit()
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
// Add a handler to the NotifyIcon CloseRequest event
ConsoleNotifyIcon.CloseRequest += NotifyIconCloseRequest;
// Create the NotifyIcon Icon in the Tray Notification Area
ConsoleNotifyIcon.GenerateTrayIcon();
// [...]
// Other processes
Console.ReadLine();
// Raises the ProcessExit event
Environment.Exit(0);
}
// Event raised by the NotifyIcon's Exit routine.
// Causes OnProcessExit to fire
private static void NotifyIconCloseRequest(object sender, EventArgs e) => Environment.Exit(0);
// Fires when Environment.Exit(0) is called
private static void OnProcessExit(object sender, EventArgs e) => CleanUp();
// Handles - Console Close Button, Control-C, Control-Break
// - System Log-off event, System ShutDown event
static bool ConsoleEventHandler(ExitReason reason)
{
SetConsoleCtrlHandler(closeHandler, false);
CleanUp();
return true;
}
// All Console Exit reasons end up here
private static void CleanUp()
{
// This is called from a different Thread
lock (closeLocker) {
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
ConsoleNotifyIcon.CloseRequest -= NotifyIconCloseRequest;
if (!ConsoleNotifyIcon.IsDisposed) {
ConsoleNotifyIcon.Dispose();
}
}
}
ConsoleNotifyIcon class(NotifyIcon 处理程序):
using System.Threading.Tasks;
using System.Windows.Forms;
public class ConsoleNotifyIcon
{
public static event EventHandler<EventArgs> CloseRequest;
// Store these objects as private Fields
private static NotifyIcon trayIcon;
private static ContextMenu trayContextMenu;
// The main public method starts a new Thread, in case a STA Thread is needed
// If not, you can just Task.Run() it
public static void GenerateTrayIcon()
{
var thread = new Thread(StartTrayIcon);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
[STAThread] // Reminder
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 { get; private set; }
public static bool IsDisposed { get; private set; }
static void Exit_Click(object sender, EventArgs e) {
// Sets the public property, it can be used to check the status
IsAppCloseRequest = true;
// Signals the Exit request, raising the CloseRequest event.
// The application may decide to delay the exit process, so calling Dispose()
// is handled by the subscribers of the event, as shown in the Console code
CloseRequest?.Invoke(null, EventArgs.Empty);
}
static void Show_Click(object sender, EventArgs e) {
// Do something
}
public static void Dispose() {
if (IsDisposed) return;
Application.ExitThread();
trayIcon?.Icon?.Dispose();
trayIcon?.Dispose();
trayContextMenu?.Dispose();
IsDisposed = true;
}
}