You can’t modify the Windows Chrome from WPF. You just can’t.
Too Long – Didn’t Read
Copy the sample code into your MainWindow, then modify it to suit your project. If you’re comfortable with WPF, you won’t have any problem with it.
The Long Answer
I recently had to change the title bar color in a WPF application for Windows 10 on a project at work, and although I found all the information I needed on the web, it was scattered across a dozen different sites and it took a while to get it all figured out. I thought it would be nice to have it all packaged up in one article.
The basic concept with the
WindowChrome object is that all the default Windows Chrome goes away, and
WindowChrome overlays your app with mouse event handlers to replace the functionality that was lost.
What you lose:
- The Windows title bar, along with its buttons, system menu and window drag behavior
- The Windows sizing border and its associated behaviors
What you get back:
- An invisible title bar overlay with system menu, window drag, and title bar button behaviors
- An invisible window border overlay that handles window sizing events
- An interesting quirk that occurs when the app is maximized (see Borders and Sizing, below)
The bottom line is that the Chrome is now completely customizable.
What you get for free with
WindowChrome is a sizeable title bar that provides:
- Right-mouse handler to open the system menu
- Left-mouse handler for the minimize button
- Left-mouse handler for the maximize/restore button
- Left-mouse handler for the close button
WindowChrome doesn’t provide any graphics – you get to do that yourself. The sample app shows how to duplicate the images of the Windows title bar buttons, but obviously you can make them look however you want.
WindowChrome eats all mouse messages in the title bar area. If you want to do anything fancy like tooltips and hover state, you must override the
WindowChrome mouse handling. Simply add a button in the desired location and give it the property:
That turns off
WindowChrome mouse handling above your button and lets you take over. The trade off is that you must handle the system calls yourself and handle the logic for maximize vs. restore. But as you see in the sample code, it’s not a big deal.
Borders and Sizing
The default window sizing provided by
WindowChrome works well. It handles changing the mouse pointer to the sizing arrow icons, and the default resizable border width seems fine. You can adjust the resize border by setting
ResizeBorderThickness. To disable window sizing, simply add:
WindowChrome declaration in XAML.
The quirk I mentioned in the opening section is that when maximized,
WindowChrome causes the app to overflow the screen by 8 pixels (DIPs) in each direction. The size of the maximized app is 16 pixels larger than the viewable screen area, and the app origin is set to (-8, -8) in screen coordinates. To correct for that, you must do something to shrink your app by 8 pixels in each direction when it is maximized.
The white diamond shown in the sample app is to illustrate exactly where the edges of the application are. You can see that the points of the diamond are always touching the edge of the app, whether it’s maximized or not. I accomplish this by adding a border with a thickness of 8 when maximized, and a thickness of 0 otherwise. Obviously, there are other ways you could handle this as well.
The extra 8 pixels are never viewable. On a multi-monitor system, the extra pixels do not overflow onto the adjacent monitor. They’re just phantom pixels that will hide the edges of your app if you don’t account for them. I recommend playing with the border logic in the sample app to get a feel for what’s going on.
Putting It All Together
WindowChrome, the sample app would look like this:
The specified window height of 200 pixels (DIPs) consists of the default non-client area that Windows controls, and the client area that WPF controls. This client area just happens to contain of a fully functional “fake” title bar sitting at the top of the client area.
WindowChrome added, the sample app looks like this:
The non-client area is gone. The 200-pixel window is filled with the fake title bar and the remaining “client area.” Note that if you’re dropping
WindowChrome into an existing application, this will change any sizing arithmetic that includes the non-client area height.
Maximized, the sample app looks like this:
The outer 8 pixels of the client area spill off the edge of the screen. As mentioned above, I account for this by adding a border that only shows up when the app is maximized. Specifically, I handle the
StateChanged event and switch the border
Thickness property between
8, depending on the value of
WindowState. There are other ways you could accomplish the same thing, of course.
Note that the 8 pixels coming and going may affect sizing arithmetic within your app. Be sure to test your UI in both maximized and normal states if you’re using
One Final Note
The default Windows title bar handles left-click on the application icon to open the system menu.
WindowChrome does not do that. It handles right-clicks across the entire title bar to open the system menu, but it doesn’t handle left click on the app icon. If you want that behavior, you can easily add it by following the same pattern as the other title bar buttons.
Thanks for reading!
Special thanks to the nice folks at icon-icons.com for the cool watermelon icon.
- 30th December, 2019: Initial version