A little while back I was tasked with creating a custom window in WPF that had its own look and feel. In order to do this you must first set the WindowStyle to None. This is great because it opens the door for you to customize your new window to look however you want. Unfortunately, when you set your WindowStyle to None you lose all of the base functionality of the window. You now have to implement native window actions such as sizing, dragging, the minimize, maximize/restore, and close buttons, etc. After looking around the web for a while I found a really good starting point for creating my custom window. Here is a link to a sample project that has an Office Style custom window already created.
http://blog.opennetcf.com/ayakhnin/content/binary/OfficeStyleWindow.zip
In this project you will find a nicely styled window whose look should be easily customizable. Now we just have to add some code to make our new window accommodate for the taskbar. One thing we must be especially aware of is accommodating for the taskbar when it is set to auto-hide. When the taskbar is auto-hidden, you have to leave 2 pixels available for the bar on the docked edge so the user can mouse over that area to restore the hidden taskbar.
After much testing and digging I ended up writing a public static WindowSizing class that should handle the bar correctly. Using the sample linked above, we must first insert these two lines inside of the Window1 constructor after InitializeComponent(); in the Window1.xaml.cs file.
this.Loaded += new RoutedEventHandler(Window1_Loaded); this.SourceInitialized += new EventHandler(Window1_SourceInitialized);
Here is the code for the event handlers.
void Window1_Loaded(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Maximized; } void Window1_SourceInitialized(object sender, EventArgs e) { WindowSizing.WindowInitialized(this); }
And here is the public static WindowSizing class code which should be self explanatory.
using System; using System.Runtime.InteropServices; using System.Windows; namespace OfficeStyleWindowProject { public static class WindowSizing { const int MONITOR_DEFAULTTONEAREST = 0x00000002; #region DLLImports [DllImport("shell32", CallingConvention = CallingConvention.StdCall)] public static extern int SHAppBarMessage(int dwMessage, ref APPBARDATA pData); [DllImport("user32", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32")] internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi); [DllImport("user32")] internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags); #endregion private static MINMAXINFO AdjustWorkingAreaForAutoHide(IntPtr monitorContainingApplication, MINMAXINFO mmi) { IntPtr hwnd = FindWindow("Shell_TrayWnd", null); if (hwnd == null) return mmi; IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (!monitorContainingApplication.Equals(monitorWithTaskbarOnIt)) return mmi; APPBARDATA abd = new APPBARDATA(); abd.cbSize = Marshal.SizeOf(abd); abd.hWnd = hwnd; SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref abd); int uEdge = GetEdge(abd.rc); bool autoHide = System.Convert.ToBoolean(SHAppBarMessage((int)ABMsg.ABM_GETSTATE, ref abd)); if (!autoHide) return mmi; switch (uEdge) { case (int)ABEdge.ABE_LEFT: mmi.ptMaxPosition.x += 2; mmi.ptMaxTrackSize.x -= 2; mmi.ptMaxSize.x -= 2; break; case (int)ABEdge.ABE_RIGHT: mmi.ptMaxSize.x -= 2; mmi.ptMaxTrackSize.x -= 2; break; case (int)ABEdge.ABE_TOP: mmi.ptMaxPosition.y += 2; mmi.ptMaxTrackSize.y -= 2; mmi.ptMaxSize.y -= 2; break; case (int)ABEdge.ABE_BOTTOM: mmi.ptMaxSize.y -= 2; mmi.ptMaxTrackSize.y -= 2; break; default: return mmi; } return mmi; } private static int GetEdge(RECT rc) { int uEdge = -1; if (rc.top == rc.left && rc.bottom > rc.right) uEdge = (int)ABEdge.ABE_LEFT; else if (rc.top == rc.left && rc.bottom < rc.right) uEdge = (int)ABEdge.ABE_TOP; else if (rc.top > rc.left) uEdge = (int)ABEdge.ABE_BOTTOM; else uEdge = (int)ABEdge.ABE_RIGHT; return uEdge; } public static void WindowInitialized(Window window) { IntPtr handle = (new System.Windows.Interop.WindowInteropHelper(window)).Handle; System.Windows.Interop.HwndSource.FromHwnd(handle).AddHook(new System.Windows.Interop.HwndSourceHook(WindowProc)); } private static IntPtr WindowProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) { switch (msg) { case 0x0024: WmGetMinMaxInfo(hwnd, lParam); handled = true; break; } return (IntPtr)0; } private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam) { MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO)); IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (monitorContainingApplication != System.IntPtr.Zero) { MONITORINFO monitorInfo = new MONITORINFO(); GetMonitorInfo(monitorContainingApplication, monitorInfo); RECT rcWorkArea = monitorInfo.rcWork; RECT rcMonitorArea = monitorInfo.rcMonitor; mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left); mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top); mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left); mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top); mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; //maximum drag X size for the window mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; //maximum drag Y size for the window mmi.ptMinTrackSize.x = 800; //minimum drag X size for the window mmi.ptMinTrackSize.y = 600; //minimum drag Y size for the window mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi); //need to adjust sizing if taskbar is set to autohide } Marshal.StructureToPtr(mmi, lParam, true); } public enum ABEdge { ABE_LEFT = 0, ABE_TOP = 1, ABE_RIGHT = 2, ABE_BOTTOM = 3 } public enum ABMsg { ABM_NEW = 0, ABM_REMOVE = 1, ABM_QUERYPOS = 2, ABM_SETPOS = 3, ABM_GETSTATE = 4, ABM_GETTASKBARPOS = 5, ABM_ACTIVATE = 6, ABM_GETAUTOHIDEBAR = 7, ABM_SETAUTOHIDEBAR = 8, ABM_WINDOWPOSCHANGED = 9, ABM_SETSTATE = 10 } [StructLayout(LayoutKind.Sequential)] public struct APPBARDATA { public int cbSize; public IntPtr hWnd; public int uCallbackMessage; public int uEdge; public RECT rc; public bool lParam; } [StructLayout(LayoutKind.Sequential)] public struct MINMAXINFO { public POINT ptReserved; public POINT ptMaxSize; public POINT ptMaxPosition; public POINT ptMinTrackSize; public POINT ptMaxTrackSize; }; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class MONITORINFO { public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); public RECT rcMonitor = new RECT(); public RECT rcWork = new RECT(); public int dwFlags = 0; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x; public int y; public POINT(int x, int y) { this.x = x; this.y = y; } } [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct RECT { public int left; public int top; public int right; public int bottom; } } }
If you have any questions or problems please feel free to leave a comment.
For a downloadable sample please go here.
sweet
awesome blog, do you have twitter or facebook? i will bookmark this page thanks. jasmin holzbauer
No Facebook account but Twitter is on the way, I’ll keep you posted.
Hey. Pretty neat code, this was really needed. Anyway I implemented this and have two questions:
1. Why do we need to maximize the window in the load event? I guess it works without that as well right?
2. The case when the autosize feature just being enabled and the window is in maximized state.. it would leave a gap exactly as the size of the taskbar.. unless you restore and maximize it again to cover the screen. Is there a way to handle that bug?
Hey Sushant,
As per question 1, you are right, we don’t need to maximize on the load event, that is just how I preferred this application to start (full screen). If you don’t maximize, it will just load in normal state.
As far as your second question I will have to test this tomorrow when I have a chance and get back to you. (We are on holiday in USA today).
Thanks for the comments.
Sushant,
I have tested your question two with my application and it works fine. If the window is maximized and the user changes the bar to auto-hide, the window will automatically scale larger to cover the space previously occupied by the taskbar. Hope this helps! PS – I tested this on Windows 7.
That’s impressive as hell, worked for me right out of the box. I needed this because of the bug in the ribbon window not allowing taskbar with “auto-hide == true” to be be displayed.
This is my first time visit at here and i am truly impressed to read all at alone place.
I am trying out your code and its great! One problem though, I need a window without the thin boarder around it. I just want all window and no boarder at all. Any ideas?
I’m not sure what you mean. There is a 1 pixel border around the entire window that needs to be there so you get the cursor change to resize the window. Do you mean you want to get rid of the title bar and status bar?
There is a bug, since line 38 will return true if either auto hide or always on top are enabled. This might be the reason more people have not seen this problem:
http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/e3e6486a-08c4-4b05-bf39-287d0d884de6
I ran it on Windows XP SP3.
A correct implementation can be found here:
http://stackoverflow.com/questions/2032461/how-can-i-determine-programmatically-whether-the-windows-taskbar-is-hidden-or-no
Great work bro..Just what i needed .Keep it up!!!!
You are so awesome! I don’t suppose I’ve read anything like this before.
So great to discover somebody with some original thoughts on this subject.
Really.. many thanks for starting this up.
This website is something that is required on the web, someone with a little originality!
[…] try: this.WindowState = WindowState.Maximized; this.ShowInTaskbar = false; This solution and many others similar, all not […]
There is a bug if you start the application on lower resolution and then change it to higher while the application is running you wont be able to maximize it on higher resolution.It will stuck in a constant size.
[…] attempt: this.WindowState = WindowState.Maximized; this.ShowInTaskbar = false; This answer and many more related, all not […]
This works great when the window is first initialized. But, I’d also like it to readjust when the display settings change (such as on a tablet when it is rotated between portrait and landscape). I can add a handler for the window’s DisplaySettingsChanged event, but I can’t figure out what to call from there. The only public method is WindowInitialized, but calling that again doesn’t work. Any suggestions?
Great solution. However, one bug I have found is that you cannot expand the window beyond the size of one screen – i.e. if you have 2 screen, the window is limited to the size of one of them – even if you have the window straddling the two screens. I presume there is a max size set somewhere that needs to be turned off in restore mode and on for max mode. Any idea where in the code to make that change?