【问题标题】:Adding MenuItems to Contextmenu for a TrayIcon in a Console app将菜单项添加到控制台应用程序中托盘图标的上下文菜单
【发布时间】:2020-11-28 09:19:44
【问题描述】:

我制作了一个小控制台应用程序,可将鼠标锁定到第一个屏幕。
现在我想创建一个带有 ContextMenu 的 TrayIcon 来关闭应用程序。

在调试模式下,我可以看到 ContextMenu 有两个项目,就像它应该的那样,但它不显示 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
}

【问题讨论】:

    标签: c# .net winforms console-application notifyicon


    【解决方案1】:

    要使NotifyIcon 工作,您必须启动一个消息循环,通常调用Application.Run()。调用方法通常也标记为单线程([STAThread])。

    差不多就是这样。
    ▶ 当然,您需要处理您创建的对象。在这种情况下,NotifyIcon 对象和ContextMenu。您还可以在 Icon 对象上调用 Dispose(),以防它在内部 NativeWindow 中设置为 null

    在此处的示例中,ConsoleNotifyIcon 类对象用于运行消息循环并接收 ContextMenu 项鼠标事件。
    在这种情况下,退出单击处理程序向主线程发出信号,退出请求已排队。它还会从通知区域中删除 NotifyIcon。

    然后主线程可以确认请求并终止。
    它还确保在退出之前 NotifyIcon 已被释放。

    ▶ 您可以在CloseRequest 事件处理程序中使用Environment.Exit()
    在这里,AppDomain.ProcessExit 事件被处理以响应Environment.Exit()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 类(NotifyIcon Handler):

    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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-30
      相关资源
      最近更新 更多