Windows 7-style Notification Area Applications in WPF: Part 1 (Removing Resize)

View source on GitHub.

Keiki, my OptusNet Usage Meter, is designed to sit in the notification area (or system tray, if you prefer) and behave similarly to the default system ‘applets’ (Volume/Network/Action Centre/Power). That is, the application becomes visible with a single left click on the notify (tray) icon, and is hidden again when focus is lost.Notification Area

I have recently started to refactor Keiki’s code, as I have learnt a lot about WPF since I first wrote the application. While refactoring I’ve also tried to polish a few rough edges: one of these is the main window’s resize behaviour. The Windows 7 (and Vista before it) tray applications (excuse the ever-changing terminology) have no title bar and are not resizable. It turns out that this window style isn’t trivial to implement with WPF.

Updated: improved code to focus application when border is clicked.

Intuitively, we would simply set WindowStyle to ‘None’ and ResizeMode to ‘NoResize’:

However, this results in a window without a border at all. Using ‘CanResize’ as the ResizeMode gives us the correct appearance, but now we’re stuck with a resizable window.

My earlier solution (as found in the current version of Keiki) was to simply set the window’s MinWidth/MinHeight and MaxWidth/MaxHeight to the same value when the window is loaded:

This has a few drawbacks:

  1. The resize cursors still appear when the mouse is over the window border.
  2. In Windows 7, Aero Snap still kind-of works, resulting in some strange behaviour (the window moves to the top right corner of the screen).

Fortunately, Dave Mullaney has a better solution, which I will reproduce here with some changes.

Firstly, set the window’s WindowStyle to ‘None’ and ResizeMode to ‘CanResize’.

We now need to add some code-behind that handles the messages sent to our window. I will try not to embarrass myself by displaying my level of ignorance regarding the Win32 API and leave it at that.

Simply copy the following into your window’s code-behind:

With that, your window should now be non-resizable despite still having a border.

Note: since we’re ignoring any window messages when the mouse pointer isn’t over the client area, the application will no longer gain focus when its borders are clicked (it won’t lose focus, either). I’m interested to hear of any solutions to this problem. Update: Code improved to give focus when the border is clicked.

In a future post I will explore positioning windows in relation to notification icons with managed code.

5 thoughts on “Windows 7-style Notification Area Applications in WPF: Part 1 (Removing Resize)”

    1. This tutorial focuses on WPF, not Windows Forms, but it should be easy to adapt. Just override the Control.WndProc method and use the code in the article (or better yet from the complete sample). You don’t need to worry about the OnSourceInitialized bit, just the WndProc and IsOverClientArea methods.

      1. I was doing the same, just stuck at “handled = true” part.


        #region Disable Window Resize
        // system constants
        private const int WM_NCHITTEST = 0x0084;
        private const int WM_SETCURSOR = 0x0020;
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_RBUTTONDOWN = 0x0204;
        private const int WM_MBUTTONDOWN = 0x0207;
        private const int WM_XBUTTONDOWN = 0x020B; // back/forward buttons
        private const int HTCLIENT = 0x1;

        [DllImport("user32.dll")]
        private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

        protected override void WndProc(ref Message m)
        {
        switch (m.Msg)
        {
        case WM_NCHITTEST:
        // if the mouse pointer is not over the client area of the tab
        // ignore it - this disables resize on the glass chrome
        if (!IsOverClientArea(m.HWnd, m.WParam, m.LParam))
        {
        //handled = true;
        }
        break;
        case WM_SETCURSOR:
        if (!IsOverClientArea(m.HWnd, m.WParam, m.LParam))
        {
        // the high word of lParam specifies the mouse message identifier
        // we only want to handle mouse down messages on the border
        int hiword = (int)m.LParam >> 16;
        if (hiword == WM_LBUTTONDOWN
        || hiword == WM_RBUTTONDOWN
        || hiword == WM_MBUTTONDOWN
        || hiword == WM_XBUTTONDOWN)
        {
        //handled = true;
        this.Focus(); // focus the window
        }
        }
        break;
        default:
        base.WndProc(ref m);
        break;
        }
        }
        private bool IsOverClientArea(IntPtr hwnd, IntPtr wParam, IntPtr LParam)
        {
        IntPtr uHitTest = DefWindowProc(hwnd, WM_NCHITTEST, wParam, LParam);
        if (uHitTest.ToInt32() == HTCLIENT) // check if we're over the client area
        return true;
        return false;
        }
        #endregion
        }

        1. This seems to work:

          protected override void WndProc(ref System.Windows.Forms.Message m)
          {
          bool handled = false;
          switch (m.Msg)
          {
          case WM_NCHITTEST:
          // if the mouse pointer is not over the client area of the tab
          // ignore it - this disables resize on the glass chrome
          if (!IsOverClientArea(m.HWnd, m.WParam, m.LParam))
          handled = true;
          break;
          case WM_SETCURSOR:
          if (!IsOverClientArea(m.HWnd, m.WParam, m.LParam))
          {
          // the high word of lParam specifies the mouse message identifier
          // we only want to handle mouse down messages on the border
          int hiword = (int)m.LParam >> 16;
          if (hiword == WM_LBUTTONDOWN
          || hiword == WM_RBUTTONDOWN
          || hiword == WM_MBUTTONDOWN
          || hiword == WM_XBUTTONDOWN)
          {
          handled = true;
          this.Focus(); // focus the window
          }
          }
          break;
          }
          if (!handled)
          base.WndProc(ref m);
          }

Leave a Reply

Your email address will not be published. Required fields are marked *