4
\$\begingroup\$

I have a small console application that runs a very long task and I would like to hide the console window when I click on the minimize button but keep a notify icon in the system tray so that I would know when the task is completed or something went wrong.

Well, I did a lot of research mostly here on SE and I came up with the following code:

using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace iTray
{
    class Program
    {

        #region pInvoke

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetConsoleWindow();

        [DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        private struct WINDOWPLACEMENT
        {
            public int length;
            public int flags;
            public int showCmd;
            public Point ptMinPosition;
            public Point ptMaxPosition;
            public Rectangle rcNormalPosition;
        }

        private enum ShowWindowCommands
        {
            Hide = 0,
            Normal = 1,
            ShowMinimized = 2,
            Maximize = 3,
            ShowMaximized = 3,
            ShowNoActivate = 4,
            Show = 5,
            Minimize = 6,
            ShowMinNoActive = 7,
            ShowNA = 8,
            Restore = 9,
            ShowDefault = 10,
            ForceMinimize = 11
        }

        [DllImport("user32.dll")]
        private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

        [DllImport("user32.dll")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

        private const uint SC_CLOSE = 0xF060;
        private const uint MF_ENABLED = 0x00000000;
        private const uint MF_DISABLED = 0x00000002;

        #endregion

        private static NotifyIcon Tray = default(NotifyIcon);
        private static IntPtr Me = default(IntPtr);

        static void Main(string[] args)
        {
            Console.Title = "Minimize Console To Tray";

            // Get The Console Window Handle
            Me = GetConsoleWindow();

            // Disable Close Button (X)
            EnableMenuItem(GetSystemMenu(Me, false), SC_CLOSE, (uint)(MF_ENABLED | MF_DISABLED));

            MenuItem mExit = new MenuItem("Exit", new EventHandler(Exit));
            ContextMenu Menu = new ContextMenu(new MenuItem[] { mExit });

            Tray = new NotifyIcon()
            {
                Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("iTray.Cheshire.ico")),
                Visible = true,
                Text = Console.Title,
                ContextMenu = Menu
            };
            Tray.DoubleClick += new EventHandler(DoubleClick);

            // Detect When The Console Window is Minimized and Hide it
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    WINDOWPLACEMENT wPlacement = new WINDOWPLACEMENT();
                    GetWindowPlacement(Me, ref wPlacement);
                    if (wPlacement.showCmd == (int)ShowWindowCommands.ShowMinimized)
                        ShowWindow(Me, (int)ShowWindowCommands.Hide);
                    // 1 ms Delay to Avoid High CPU Usage
                    Wait(1);
                }
            }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

            Application.Run();
        }

        private static void DoubleClick(object sender, EventArgs e)
        {
            ShowWindow(Me, (int)ShowWindowCommands.Restore);
        }

        private static void Exit(object sender, EventArgs e)
        {
            Tray.Dispose();
            Application.Exit();
        }

        private static void Wait(int timeout)
        {
            using (AutoResetEvent AREv = new AutoResetEvent(false))
                AREv.WaitOne(timeout, true);
        }
    }
}

The code is working fine, but I need to know your opinions whether the are some improvements to be made especially on the infinite loop part.

\$\endgroup\$
3
\$\begingroup\$

Instead of using an infinite loop, use a Timer and an event handler. You only get a 15ms resolution (actually, Wait and Sleep have the same resolution), but that should be plenty quick enough for your purposes. It'll use less CPU and free up the thread to do other work.

It would also be best to separate your DllImports out into a separate class. You'll see a lot of folks make it static, but I like to unit test my code, so I tend to use a pattern something like this.

public class MyWrapper : IMyWrapper
{
    public int Foo(...)
    {
        NativeFoo(...);
    }

    [DllImport("Library.dll", EntryPoiny = "Foo")]
    private static extern int NativeFoo(...);

}

Where the interface matches the public API of MyWrapper. This allows you to test your client code in isolation from the native library, which likely interacts with hardware/the OS.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I guess the Timer would be the optimal choice and as for the DllImport part, I used to put them all in an internal class and call them in the other classes, however, your way seems better so thank you for the hint. \$\endgroup\$ – PavilionVI May 6 '16 at 22:50
  • \$\begingroup\$ You're welcome! Good luck with your endeavors! \$\endgroup\$ – RubberDuck May 6 '16 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.