wpf
wpf copied to clipboard
Add support for Windows 11 "Snap Layout" to the custom title bar (WindowChrome)
As Windows 11 introduce Snap Layout when we hover on Restore/Maximize caption button, I want to have this feature on my custom title bar using WindowChrome.
The current SystemCommands class does not have it:
public static class SystemCommands
{
public static void CloseWindow(Window window);
public static void MaximizeWindow(Window window);
public static void MinimizeWindow(Window window);
public static void RestoreWindow(Window window);
public static void ShowSystemMenu(Window window, Point screenLocation);
}
Custom Title bar

Default Title bar

Ref https://github.com/dotnet/wpf/issues/4749
Does anyone know if there is an API to trigger the new snap layout "menu"?
It is not a system command.
If you have a custom maximize button, it is your job to tell that to the OS by responding HTMAXBUTTON to WM_NCIHITTEST. That will give you the snap options on hover.
I think there is an opportunity for API improvement to simplify this task through an attached WindowChrome property
It is not a system command.
If you have a custom maximize button, it is your job to tell that to the OS by responding
HTMAXBUTTONtoWM_NCIHITTEST. That will give you the snap options on hover.I think there is an opportunity for API improvement to simplify this task through an attached
WindowChromeproperty
This would be helpful for Windows Terminal, as well as other WinUI 3 apps.
@miloush While returning HT.MAXBUTTON works and shows the menu it causes various unwanted side effects.
- MouseOver does not work anymore (highlight)
- Clicking a button which returned some window button
HTcauses the system button to be rendered for a short time

@batzen I guess you can still handle the mouse over, however yeah I will give you that the system-rendered button on click is sad; feels like a bug in the shell.
@miloush The events aren't even raised when the caption button HT values are returned. The WPF stuff just acts like the cursor isn't even over the window.
We also have been digging into this issue of trying to show the Snap Layout Menu in a WPF app that uses our custom WindowChrome, where we render all custom title bar buttons. We've run into the same issues when returning HTMAXBUTTON from WM_NCHITTEST as described above, where WPF no longer receives any pointer input (due to HTCLIENT not being returned), and clicking our custom button renders a classic Win32 maximize button.
We really need some other API than responding to WM_NCHITTEST to get this working for those of us who have powerful custom window chromes and need the WPF elements in those chromes to remain responsive.
FYI: The WindowChromeBehavior from ControlzEx will provide a solution for this problem during Hacktoberfest.
@batzen Are you able to share with us a copy of the test app so we can get to the bottom of this?
I can share a link to the branch in ControlzEx as soon as i pushed the changes. Feel free to ping me if i didn't do so till monday.
@mevey You can grab the code from https://github.com/ControlzEx/ControlzEx/tree/features/Win11Support Please note that it's still a WIP and i haven't yet implemented support for rounded corners, but the snap menu on the maximize button works without all the negative side effects everyone observed.
Thank you @batzen, this is extremely helpful as we try to understand root cause.
We also have worked the past week on Windows 11 features in our own WindowChrome and have managed to get the snap layout menu showing without the two negative side effects we found in our first attempt. Our changes can be summarized as:
-
Handle WM_NCHITTEST and return HTMAXBUTTON when over our custom WPF Button that represents the Maximize button in our window chrome's title bar. This gets the system to show the snap layout menu when the mouse is over our Maximize button.
-
Mark the WM_NCLBUTTONDOWN message as handled when over HTMAXBUTTON per number 1 above. This prevents the classic 3D Win32 button from showing when clicking our WPF Maximize button.
-
Handling various WM_NC* mouse messages and using SendMessage to send related messages to the Window to mimic "client area" mouse messages with translated POINT values so that WPF can receive them. This ensures the pressed state can be reflected on the button. But it doesn't help with the hover state since Win32 still thinks we are in a non-client area so WM_MOUSELEAVE gets fired by the system immediately any time we send WM_MOUSEMOVE messages ourselves, thereby making WPF's IsMouseOver fail.
While the above sounds relatively simple, it really isn't and ended up being a lot more work than we'd hoped. We've had to hack things in to handle the edge cases like proper hover state display on the button when the menu is open. It would be nice if the system could notify the window somehow when the snap layout menu was opening/closing so we could alter our hover state on our Maximize button. Right now it seems no Windows Win32 messages are sent to the window once the snap layout menu opens. It's difficult to know if the mouse then moves over that menu or not.
@billhenn Point 3 is the reason why I created https://github.com/ControlzEx/ControlzEx/blob/features/Win11Support/src/ControlzEx/Behaviors/WindowChrome/NonClientControlManager.cs It's simply not possible, judging from all my experiments, to return the proper hittest result and get client area mouse messages at the same time as the area is treated as non client after the hittest result.
It's simply not possible, judging from all my experiments, to return the proper hittest result and get client area mouse messages at the same time as the area is treated as non client after the hittest result.
Yes, that is the one of the most difficult things we've had to attempt to work around. It would be nice if there was a way to manually request the snap layout menu show so we could avoid the entire hacky non-client piece of things. If our WPF Button could detect a hover scenario on itself, then we could simply call some Windows API (which probably doesn't currently exist) to request the snap layout menu at a certain screen coordinate and be done with it.
Hey folks, Just letting you know that we are still working on this issue internally and will have a response for you soon. We appreciate your patience.
@mevey any updates on this?
For anyone interested: https://github.com/ControlzEx/ControlzEx/pull/151 ControlzEx has it implemented and also offers window border colors, controlling rounded borders and the custom window chrome is flicker free. If you choose to use glow windows those move/resize without any visible gap.
Hi @batzen Your example was very complex (at least for me) I used another code. I was able to show SnapLayout But I see the problems you mentioned earlier

You seem to have solved these problems, Can you help me solve these problems?
int x = lparam.ToInt32() & 0xffff;
int y = lparam.ToInt32() >> 16;
var rect = new Rect(_ButtonMax.PointToScreen(new Point()), new Size(_ButtonMax.Width, _ButtonMax.Height));
if (rect.Contains(new Point(x, y)))
{
handled = true;
}
return new IntPtr(HTMAXBUTTON);
Hi, @ghost1372 . i have checked your code in my c# wpf application ,i am getting compile time error . can you tell me or provide any sample for how to use your code in c# wpf for button control .
Hi, @ghost1372 . i have checked your code in my c# wpf application ,i am getting compile time error . can you tell me or provide any sample for how to use your code in c# wpf for button control .
Hi, @ghost1372 , i have checked your given link in my apps, my apps does not return IntPtr(HTMAXBUTTON) results.
Hi, @ghost1372 , i have checked your given link in my apps, my apps does not return IntPtr(HTMAXBUTTON) results.
You have to pay attention to the HwndSourceHook function and how to register it in
protected override void OnSourceInitialized(EventArgs e)
Follow the tutorial in the second link
Hi, @ghost1372 , i have added this below code for solving extra max button showing issue. it has fixed that issue but i am unbale to resize my apps windows width and Hight size using my mouse. if (msg == WM_NCLBUTTONDOWN) { handled = true; ; }
@sachinkumarrajak08 yes i have this issue too, also you mouse hover not working for max button
To trigger a click in a non-client area we can do it this way
case Input.WM_NCLBUTTONDOWN:
IInvokeProvider invokeProv = new ButtonAutomationPeer(_myButtonControl)
.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
invokeProv?.Invoke();
handled = true;
break;
Hi @Pomianowski Thank you your code worked fine, But there is one problem
One click on titlebar maximizes Window

and Do you have any solution for button hover?

Hi @ghost1372, initially I did something like that: https://github.com/lepoco/wpfui/blob/main/WPFUI/Common/SnapLayout.cs
@Pomianowski thanks but I found another way and everything works fine😁 If anyone is interested, you can see the codes here https://github.com/ghost1372/HandyControls/commit/41fce1df04b45ab9a7a8ebad33f3810a89a1ad13