wpf icon indicating copy to clipboard operation
wpf copied to clipboard

Add support for Windows 11 "Snap Layout" to the custom title bar (WindowChrome)

Open d2phap opened this issue 4 years ago • 66 comments

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

image

Default Title bar

image

d2phap avatar Jul 09 '21 11:07 d2phap

Ref https://github.com/dotnet/wpf/issues/4749

lindexi avatar Jul 09 '21 12:07 lindexi

Does anyone know if there is an API to trigger the new snap layout "menu"?

batzen avatar Jul 10 '21 04:07 batzen

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

miloush avatar Jul 11 '21 13:07 miloush

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

This would be helpful for Windows Terminal, as well as other WinUI 3 apps.

mdtauk avatar Jul 11 '21 13:07 mdtauk

@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 HT causes the system button to be rendered for a short time

WPF_HT_Button

batzen avatar Jul 11 '21 14:07 batzen

@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 avatar Jul 11 '21 15:07 miloush

@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.

Splitwirez avatar Jul 12 '21 12:07 Splitwirez

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.

billhenn avatar Sep 21 '21 20:09 billhenn

FYI: The WindowChromeBehavior from ControlzEx will provide a solution for this problem during Hacktoberfest.

batzen avatar Sep 22 '21 01:09 batzen

@batzen Are you able to share with us a copy of the test app so we can get to the bottom of this?

mevey avatar Sep 23 '21 20:09 mevey

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.

batzen avatar Sep 25 '21 09:09 batzen

@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.

batzen avatar Sep 26 '21 17:09 batzen

Thank you @batzen, this is extremely helpful as we try to understand root cause.

mevey avatar Sep 29 '21 18:09 mevey

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:

  1. 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.

  2. 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.

  3. 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 avatar Sep 29 '21 18:09 billhenn

@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.

batzen avatar Sep 29 '21 19:09 batzen

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.

billhenn avatar Sep 29 '21 19:09 billhenn

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 avatar Oct 12 '21 00:10 mevey

@mevey any updates on this?

kalin-todorov avatar Oct 29 '21 14:10 kalin-todorov

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.

batzen avatar Oct 29 '21 14:10 batzen

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 00

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);

ghost1372 avatar Nov 01 '21 13:11 ghost1372

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 .

sachinkumarrajak08 avatar Nov 02 '21 07:11 sachinkumarrajak08

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 .

you should write this code in wndProc see here or here

ghost1372 avatar Nov 02 '21 07:11 ghost1372

Hi, @ghost1372 , i have checked your given link in my apps, my apps does not return IntPtr(HTMAXBUTTON) results.

sachinkumarrajak08 avatar Nov 03 '21 11:11 sachinkumarrajak08

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

ghost1372 avatar Nov 03 '21 11:11 ghost1372

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 avatar Nov 13 '21 09:11 sachinkumarrajak08

@sachinkumarrajak08 yes i have this issue too, also you mouse hover not working for max button

ghost1372 avatar Nov 13 '21 09:11 ghost1372

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;

pomianowski avatar Nov 26 '21 10:11 pomianowski

Hi @Pomianowski Thank you your code worked fine, But there is one problem One click on titlebar maximizes Window 01

and Do you have any solution for button hover? 03

ghost1372 avatar Nov 29 '21 16:11 ghost1372

Hi @ghost1372, initially I did something like that: https://github.com/lepoco/wpfui/blob/main/WPFUI/Common/SnapLayout.cs

pomianowski avatar Dec 01 '21 12:12 pomianowski

@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

ghost1372 avatar Dec 04 '21 08:12 ghost1372