{"id":30,"date":"2010-12-09T17:31:54","date_gmt":"2010-12-09T06:31:54","guid":{"rendered":"http:\/\/blog.quppa.net\/?p=30"},"modified":"2010-12-09T17:31:54","modified_gmt":"2010-12-09T06:31:54","slug":"windows-7-style-notification-area-applications-in-wpf-part-3-taskbar-position","status":"publish","type":"post","link":"https:\/\/www.quppa.net\/blog\/2010\/12\/09\/windows-7-style-notification-area-applications-in-wpf-part-3-taskbar-position\/","title":{"rendered":"Windows 7-style Notification Area Applications in WPF: Part 3 (Taskbar Position)"},"content":{"rendered":"<blockquote><p><a href=\"https:\/\/github.com\/Quppa\/NotificationAreaIconSampleAppWPF\" title=\"GitHub: NotificationAreaIconSampleAppWPF\">View source on GitHub.<\/a><\/p><\/blockquote>\n<p>In the <a title=\"Windows 7-style Notification Area Applications in WPF: Part 2 (Notify Icon Position)\" href=\"https:\/\/www.quppa.net\/blog\/2010\/12\/08\/windows-7-style-notification-area-applications-in-wpf-part-2-notify-icon-position\/\">previous post in this series<\/a>, I showed how to find the location of a notify icon by implementing the new Windows 7 Shell32.dll function <a title=\"MSDN: Shell_NotifyIconGetRect\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378426(v=VS.85).aspx\">Shell_NotifyIconGetRect<\/a> in managed code for use with the <a title=\"MSDN: System.Windows.Forms.NotifyIcon Class\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.notifyicon.aspx\">System.Windows.Forms.NotifyIcon<\/a> class.<\/p>\n<p>In this post, I will look at how to accurately position a window above (or adjacent to) a notify icon, no matter the location of the taskbar (barring a certain issue that I note at the end of this post :)). As I noted in <a title=\"Windows 7-style Notification Area Applications in WPF: Part 2 (Notify Icon Position)\" href=\"https:\/\/www.quppa.net\/blog\/2010\/12\/08\/windows-7-style-notification-area-applications-in-wpf-part-2-notify-icon-position\/\">Part 2<\/a>, the current version of <a title=\"Keiki Usage Meter\" href=\"http:\/\/www.quppa.net\/keiki\">Keiki<\/a> is hardcoded to appear in the bottom right-hand corner of the user\u2019s screen, making it very out of place when the taskbar is moved to the top\/right\/left of the screen. We\u2019ll need to delve into the Win32 API once again, but as in Part 2 the amount of code required is not daunting, even for a Win32 beginner like me.<\/p>\n<p>    <!--more-->    <\/p>\n<h1>Possible positions<\/h1>\n<p>Basically, we are seeking to determine which edge the taskbar is placed along, and choose our window positioning logic accordingly:<\/p>\n<ol>\n<li>If the taskbar is docked to the bottom of the screen (default), place the window horizontally centred above the notify icon. <\/li>\n<li>If the taskbar is docked to the top of the screen, place the window horizontally centred below the notify icon. <\/li>\n<li>If the taskbar is docked to the left of the screen, place the window vertically centred to the right of the notify icon. <\/li>\n<li>If the taskbar is docked to the right of the screen, place the window vertically centred to the left of the notify icon. <\/li>\n<\/ol>\n<p>The fly-out containing hidden notify icons in Windows 7 must also be considered, since there is a special case when the taskbar is docked to the left or right of the screen: if the notify icon is contained in the fly-out box, the window should be placed above the icon, not beside it. (This seems like a bit of an arbitrary design decision to me, but it is how the system applications behave.)<\/p>\n<p><a href=\"https:\/\/www.quppa.net\/blog\/wp-content\/uploads\/Notification-Area-Right-Aligned.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top: 0px; border-right: 0px; padding-top: 0px\" title=\"Taking this screenshot took just about as long as did writing the article :(\" border=\"0\" alt=\"Notification Area Right Aligned\" src=\"https:\/\/www.quppa.net\/blog\/wp-content\/uploads\/Notification-Area-Right-Aligned_thumb.png\" width=\"357\" height=\"176\" \/><\/a><\/p>\n<p>It may be possible to determine the location of the taskbar with managed code alone: we could probably use the <a title=\"MSDN: System.Windows.Forms.Screen Class\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.screen.aspx\">System.Windows.Forms.Screen<\/a> class and find the difference between the <a title=\"MSDN: System.Windows.Forms.Screen.PrimaryScreen Property\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.screen.primaryscreen.aspx\">PrimaryScreen<\/a>\u2019s <a title=\"MSDN: System.Windows.Forms.Screen.Bounds Property\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.screen.bounds.aspx\">Bounds<\/a> and <a title=\"MSDN: System.Windows.Forms.Screen.WorkingArea\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.screen.workingarea.aspx\">WorkingArea<\/a> properties (both <a title=\"MSDN: System.Drawing.Rectangle Structure\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.drawing.rectangle.aspx\">Rectangles<\/a>). I suspect this is a valid approach, but I chose to use the Win32 API function <a title=\"MSDN: SHAppBarMessage Function\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb762108(VS.85).aspx\">SHAppBarMessage<\/a>, instead, as it provides all the information we need quite neatly, without any manual calculation required.<\/p>\n<h1>SHAppBarMessage<\/h1>\n<p>SHAppBarMessage is a function in <a title=\"MSDN: Shell Functions\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb776426(v=VS.85).aspx\">Shell32.dll<\/a> which \u2018sends an appbar message to the system\u2019. MSDN defines an <em><a title=\"MSDN: Using Application Desktop Toolbars\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc144177(v=VS.85).aspx\">appbar<\/a><\/em> (\u2018Application Desktop Toolbar\u2019) like this:<\/p>\n<blockquote>\n<p>An application desktop toolbar (also called an appbar) is a window that is similar to the Windows taskbar. It is anchored to an edge of the screen, and it typically contains buttons that give the user quick access to other applications and windows. The system prevents other applications from using the desktop area used by an appbar. Any number of appbars can exist on the desktop at any given time.<\/p>\n<\/blockquote>\n<p>The Windows taskbar turns out to be a special appbar, itself. (Incidentally, I\u2019m not sure I\u2019ve ever seen a program use this functionality.)<\/p>\n<p>The SHAppBarMessage syntax is:<\/p>\n<pre class=\"lang:c decode:true \">UINT_PTR SHAppBarMessage(\n  __in     DWORD dwMessage,\n  __inout  PAPPBARDATA pData\n);<\/pre>\n<p>The dwMessage parameter contains the appbar message to send. We\u2019re interested in using <a title=\"MSDN: ABM_GETTASKBARPOS Message\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb787949(v=VS.85).aspx\">ABM_GETTASKBARPOS<\/a> (0x00000005), which retrieves the bounding rectangle of the Windows taskbar. It also tells us which edge the taskbar is docked to.<\/p>\n<p>Happily, <a title=\"Crow&#39;s Programming Blog\" href=\"http:\/\/www.crowsprogramming.com\">Crow&#8217;s Programming Blog<\/a> has a <a title=\"Crow&#39;s Programming: Accessing the System Task Bar in C#\" href=\"http:\/\/www.crowsprogramming.com\/archives\/88\">great post<\/a> about using this function in managed code. The code presented is exactly what we need.<\/p>\n<p>Firstly, we need to define the structures and method signature required for SHAppBarMessage (remember that we used RECT in <a title=\"Windows 7-style Notification Area Applications in WPF: Part 2 (Notify Icon Position)\" href=\"https:\/\/www.quppa.net\/blog\/2010\/12\/08\/windows-7-style-notification-area-applications-in-wpf-part-2-notify-icon-position\/\">Part 2<\/a>, so there is no need to define it twice if you\u2019re already using that code):<\/p>\n<pre class=\"lang:c# decode:true \">[StructLayout(LayoutKind.Sequential)]\nprivate struct RECT\n{\n    public int left;\n    public int top;\n    public int right;\n    public int bottom;\n}\n[StructLayout(LayoutKind.Sequential)]\nprivate struct APPBARDATA\n{\n    public Int32 cbSize;\n    public IntPtr hWnd;\n    public Int32 uCallbackMessage;\n    public ABEdge uEdge;\n    public RECT rc;\n    public IntPtr lParam;\n}\nprivate enum ABMsg\n{\n    ABM_NEW = 0,\n    ABM_REMOVE = 1,\n    ABM_QUERYPOS = 2,\n    ABM_SETPOS = 3,\n    ABM_GETSTATE = 4,\n    ABM_GETTASKBARPOS = 5,\n    ABM_ACTIVATE = 6,\n    ABM_GETAUTOHIDEBAR = 7,\n    ABM_SETAUTOHIDEBAR = 8,\n    ABM_WINDOWPOSCHANGED = 9,\n    ABM_SETSTATE = 10\n}\nprivate enum ABEdge\n{\n    ABE_LEFT = 0,\n    ABE_TOP = 1,\n    ABE_RIGHT = 2,\n    ABE_BOTTOM = 3,\n}\nprivate enum ABState\n{\n    ABS_MANUAL = 0,\n    ABS_AUTOHIDE = 1,\n    ABS_ALWAYSONTOP = 2,\n    ABS_AUTOHIDEANDONTOP = 3\n}\n[DllImport(&quot;shell32.dll&quot;)]\nprivate static extern IntPtr SHAppBarMessage(ABMsg dwMessage, [In, Out] ref APPBARDATA pData);<\/pre>\n<p>We need to specify the handle of the taskbar in order to use the ABM_GETTASKBARPOS message. We\u2019ll need another PInvoke signature to find this:<\/p>\n<pre class=\"lang:c# decode:true \">[DllImport(&quot;user32.dll&quot;)]\nprivate static extern IntPtr FindWindow(string lpClassName, string lpWindowName);<\/pre>\n<p>The <a title=\"MSDN: FindWindow Function\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms633499(v=VS.85).aspx\">FindWindow function<\/a> lets us retrieve the handle of the top-level window whose class name and window name match the specified strings. The class name \u2018Shell_TrayWnd\u2019 refers to the taskbar, though (as Crow notes) this doesn\u2019t appear to be officially documented. <strong>Update:<\/strong> it seems that just passing in IntPtr.Zero as the handle works just as well, so we might not need FindWindow at all.<\/p>\n<p>Anyway, we can put all this together with a few lines of code:<\/p>\n<pre class=\"lang:c# decode:true \">IntPtr taskbarhandle = FindWindow(&quot;Shell_TrayWnd&quot;, &quot;&quot;);\nAPPBARDATA abdata = new APPBARDATA() { hWnd = taskbarhandle };\nabdata.cbSize = Marshal.SizeOf(abdata);\nIntPtr result = SHAppBarMessage(ABMsg.ABM_GETTASKBARPOS, ref abdata);\nswitch (abdata.uEdge)\n{\n    case ABEdge.ABE_BOTTOM:\n        \/\/ taskbar is at the bottom of the screen\n        break;\n    case ABEdge.ABE_TOP:\n        \/\/ taskbar is at the top of the screen\n        break;\n    case ABEdge.ABE_LEFT:\n        \/\/ taskbar is at the left of the screen\n        break;\n    case ABEdge.ABE_RIGHT:\n        \/\/ taskbar is at the right of the screen\n        break;\n}<\/pre>\n<p>After calling the function you can get the bounds of the taskbar by looking at the rc property of abdata.<\/p>\n<h1>Windows 7 fly-out box<\/h1>\n<p>As mentioned at the start of this post, when the task bar is docked to the right or left of the screen and the notify icon is displayed in the Windows 7 fly-out notification area box, the window should be displayed above the icon. We can easily determine whether the notify icon is in the fly-out or not using the result from <a title=\"Windows 7-style Notification Area Applications in WPF: Part 2 (Notify Icon Position)\" href=\"https:\/\/www.quppa.net\/blog\/2010\/12\/08\/windows-7-style-notification-area-applications-in-wpf-part-2-notify-icon-position\/\">Shell_NotifyIconGetRect<\/a>:<\/p>\n<pre class=\"lang:c# decode:true \">RECT nirect = GetNotifyIconRect();\nbool inFlyOut = (nirect.left &gt; abdata.rc.right || nirect.right &lt; abdata.rc.left\n|| nirect.bottom &lt; abdata.rc.top || nirect.top &gt; abdata.rc.bottom);<\/pre>\n<p>inFlyOut will be true if there is no overlap between the rectangles (that is, the notify icon is not within the bounds of the taskbar), and false if there is overlap (the notify icon is within the taskbar\u2019s bounds).<\/p>\n<h1>Putting it all together<\/h1>\n<p>We now have enough information to accurately position our window next to our notify icon.<\/p>\n<h2>Theming &amp; offsets<\/h2>\n<p>Though it is not something that most users would notice (or care about if they did :)), the positioning of notification area windows changed slightly from Vista to 7. In Vista (and pre-release Windows 7, actually), the window appeared 1 pixel away from the taskbar\/window edge, while in 7, there is an offset of 8 pixels (at 96 DPI; this increases by the DPI factor: it&#8217;s 10 pixels at 120 DPI, 12 pixels at 144 DPI, 16 pixels at 192 DPI) from the taskbar\/window edge.<\/p>\n<p><a href=\"https:\/\/www.quppa.net\/blog\/wp-content\/uploads\/DWM-Differences.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top: 0px; border-right: 0px; padding-top: 0px\" title=\"DWM Differences\" border=\"0\" alt=\"DWM Differences\" src=\"https:\/\/www.quppa.net\/blog\/wp-content\/uploads\/DWM-Differences_thumb.png\" width=\"342\" height=\"102\" \/><\/a><\/p>\n<p>To complicate matters, there is no offset under Aero Basic (that is, when the <a title=\"MSDN: Desktop Window Manager\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa969540(VS.85).aspx\">DWM<\/a> is disabled). Also, the normal window border is disabled if the DWM is disabled: it\u2019s replaced with a single pixel border whose colour is equal to the <a title=\"MSDN: SystemColors.WindowFrameColor Property\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.systemcolors.windowframecolor.aspx\">SystemColors.WindowFrameColor<\/a> colour (#646464 in Aero Basic and #000000 in Classic, if you\u2019re curious). Replacing the border is straightforward, so I won\u2019t deal it here. I also won\u2019t specify how to determine the current theme; I\u2019ll just assume that information is accessible.<\/p>\n<h2><\/h2>\n<h2>The code<\/h2>\n<p>I will aim to publish a full project later demonstrating this functionality with an empty application. Until then, I\u2019m afraid you\u2019ll have to link all these scattered code fragments together somehow and figure out the missing bits. I hope to release <a title=\"Keiki Usage Meter\" href=\"http:\/\/www.quppa.net\/keiki\/\">Keiki<\/a>\u2019s source at some point, too.<\/p>\n<pre class=\"lang:c# decode:true \">\/\/\/ &lt;summary&gt;\n\/\/\/ Returns the optimum window position in relation to the specified notify icon.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;param name=&quot;notifyicon&quot;&gt;The notify icon that the window should be aligned to.&lt;\/param&gt;\n\/\/\/ &lt;param name=&quot;windowwidth&quot;&gt;The width of the window.&lt;\/param&gt;\n\/\/\/ &lt;param name=&quot;windowheight&quot;&gt;The height of the window.&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;A Rect specifying the suggested location of the window.&lt;\/returns&gt;\npublic static Rect GetWindowPosition(NotifyIcon notifyicon, double windowwidth, double windowheight)\n{\n    \/\/ retrieve taskbar location\n    TaskBarInfo taskbarinfo = GetTaskBarInfo();\n    \/\/ retrieve notify icon location\n    Rect niposition = GetNotifyIconRectangle(notifyicon);\n    \/\/ check if notify icon is in the fly-out\n    bool inflyout = IsNotifyIconInFlyOut(notifyicon, taskbarinfo);\n    \/\/ determine centre of notify icon\n    Point nicentre = new Point(niposition.Left + (niposition.Width \/ 2), niposition.Top + (niposition.Height \/ 2));\n    \/\/ get window offset from edge\n    double edgeoffset = Compatibility.GetWindowEdgeOffset();\n    \/\/ get working area bounds\n    Rect workarea = Compatibility.GetWorkingArea();\n    \/\/ calculate window position\n    double windowleft = 0, windowtop = 0;\n    switch (taskbarinfo.Alignment)\n    {\n        case TaskBarAlignment.Bottom:\n            \/\/ horizontally centre above icon\n            windowleft = nicentre.X - (windowwidth \/ 2);\n            if (inflyout)\n                windowtop = niposition.Top - windowheight - edgeoffset;\n            else\n                windowtop = taskbarinfo.Position.Top - windowheight - edgeoffset;\n            break;\n        case TaskBarAlignment.Top:\n            \/\/ horizontally centre below icon\n            windowleft = nicentre.X - (windowwidth \/ 2);\n            if (inflyout)\n                windowtop = niposition.Bottom + edgeoffset;\n            else\n                windowtop = taskbarinfo.Position.Bottom + edgeoffset;\n            break;\n        case TaskBarAlignment.Left:\n            \/\/ vertically centre to the right of icon (or above icon if in flyout)\n            if (inflyout)\n            {\n                windowleft = nicentre.X - (windowwidth \/ 2);\n                windowtop = niposition.Top - windowheight - edgeoffset;\n            }\n            else\n            {\n                windowleft = taskbarinfo.Position.Right + edgeoffset;\n                windowtop = nicentre.Y - (windowheight \/ 2);\n            }\n            break;\n        case TaskBarAlignment.Right:\n            \/\/ vertically centre to the left of icon (or above icon if in flyout)\n            if (inflyout)\n            {\n                windowleft = nicentre.X - (windowwidth \/ 2);\n                windowtop = niposition.Top - windowheight - edgeoffset;\n            }\n            else\n            {\n                windowleft = taskbarinfo.Position.Left - windowwidth - edgeoffset;\n                windowtop = nicentre.Y - (windowheight \/ 2);\n            }\n            break;\n        default:\n            goto case TaskBarAlignment.Bottom; \/\/ should be unreachable\n    }\n    \/\/ check that the window is within the working area\n    \/\/ if not, put it next to the closest edge\n    if (windowleft + windowwidth + edgeoffset &gt; workarea.Right) \/\/ too far right\n        windowleft = workarea.Right - windowwidth - edgeoffset;\n    else if (windowleft &lt; workarea.Left) \/\/ too far left\n        windowleft = workarea.Left + edgeoffset;\n    if (windowtop + windowheight + edgeoffset &gt; workarea.Bottom) \/\/ too far down\n        windowtop = workarea.Bottom - edgeoffset;\n    \/\/ the window should never be too far up, so we can skip checking for that\n    return new Rect(windowleft, windowtop, windowwidth, windowheight);\n}<\/pre>\n<h2>A challenger appears<\/h2>\n<p>In my testing of the above code, I\u2019ve noticed one problem: the <a title=\"MSDN: System.Windows.SystemParameters.WorkArea Property\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.systemparameters.workarea.aspx\">System.Windows.SystemParameters.WorkArea property<\/a> (which is what my \u2018GetWorkingArea()\u2019 function calls on line 27 above) returns the working area of the primary monitor, and there is no guarantee that the taskbar is located on that monitor. If the taskbar is on a different monitor, the above code will behave strangely and end up placing the window on the primary monitor, despite the taskbar being somewhere else.<\/p>\n<p>You may see this as a relatively minor issue, but we have been very pedantic about getting everything exactly right up to here, so it would be poor form to give up at the last hurdle \ud83d\ude42<\/p>\n<p>I believe that either some more unmanaged Win32 or System.Windows.Forms code will be necessary to work this one out (please correct me if I\u2019m wrong). With WinForms we could probably use the System.Windows.Forms.Screens property to loop over each screen to see which contains the taskbar\u2019s bounding rectangle. Win32 has the <a title=\"MSDN: MonitorFromRect Function\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd145063(VS.85).aspx\">MonitorFromRect function<\/a> which can give us the handle of the monitor that has the largest area of intersection with a given rectangle (the taskbar rectangle, in our case). We can then pass that handle to <a title=\"MSDN: GetMonitorInfo Function\" href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd144901(VS.85).aspx\">GetMonitorInfo<\/a>, which will give us the work area that we should be using to position the window. I will try to cover the Win32 approach in a later post.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>View source on GitHub. In the previous post in this series, I showed how to find the location of a notify icon by implementing the new Windows 7 Shell32.dll function Shell_NotifyIconGetRect in managed code for use with the System.Windows.Forms.NotifyIcon class. In this post, I will look at how to accurately position a window above (or &hellip; <a href=\"https:\/\/www.quppa.net\/blog\/2010\/12\/09\/windows-7-style-notification-area-applications-in-wpf-part-3-taskbar-position\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Windows 7-style Notification Area Applications in WPF: Part 3 (Taskbar Position)&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,6],"tags":[17,28,47,72,74,87,88,96,128,146,170,179,185],"class_list":["post-30","post","type-post","status-publish","format-standard","hentry","category-keiki","category-programming","tag-appbar","tag-c","tag-dllimport","tag-interop","tag-keiki-2","tag-notification-area","tag-notifyicon","tag-pinvoke","tag-shell32","tag-taskbar","tag-win32","tag-winforms","tag-wpf"],"_links":{"self":[{"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/posts\/30","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/comments?post=30"}],"version-history":[{"count":0,"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/posts\/30\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/media?parent=30"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/categories?post=30"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.quppa.net\/blog\/wp-json\/wp\/v2\/tags?post=30"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}