wangwenx190 / framelesshelper

Project moved to: https://github.com/stdware/qwindowkit Cross-platform window customization framework for Qt Widgets and Qt Quick. Supports Windows, Linux and macOS.
MIT License
847 stars 202 forks source link

[Windows] Window resize border location is inside of the application border #3

Closed shujaatak closed 2 years ago

shujaatak commented 4 years ago

Fist thing first, illustration: screenshot2

In the above illustration, the yellow color shows the area where mouse cursor changes to show that the user now can resize the window. In framelesshelper application the mouse cursor changes when the mouse cursor is inside of the application window but in any other normal Windows application the cursor changes when the mouse is on the application border or outside of the application border(in the window shadow area). I think the resize border area is kept on border or outside of the border(in the window shadow area) to increase the client area so that the mouse could interact with the widgets inside of the application borders. So can this resize border be placed outside of the framelesshelper window border just like any other normal application window?

By the way, I noticed this when I got irritated by this unusual behavior as I missed clicking on the window border and mistakenly clicking on an other application running in the background.

Furthermore, I really like the way Mozilla does this(chrome way is not good, in my opinion). You can try to resize the Mozilla window by dragging the LEFT border and you won't see any flicker introduced in the minimize, maximize and close buttons but if you try the same with chrome then those buttons would flicker. While both Mozilla and Chrome provides custom window non-client area painting but retains the resizing border location.

I wish there was a stripped down just window part of the Mozilla browser so that the people like you ,me and others could benefit but that's another story. Mozilla is best in terms of GUI! But chrome is best in terms of other things.

shujaatak commented 4 years ago

I noticed that the Windows make that margins area(where mouse cursor changes to re-sizable one) as transparent around the window border, so I think in reality the outer region is part of the application but drawn transparent. You ask from where I got this? I was searching for making a dwm/customframe and I found this repository. I ran the "Composition Interface" example, keeping the default options and checked the fifth option from Theme group: screenshot3

and I suddenly got amazing following results:

screenshot2

I have made a background picture which helps to see the transparent part of the window:

screenshot1

I am not sure what I think is correct but what I see is a transparent window part outside of the application window borders.

shujaatak commented 4 years ago

Some links, projects and codes that may help to make dwm/customframe.

qdwmtest https://www.qtcentre.org/threads/55495-Non-client-area-painting-with-Qt https://gist.github.com/d1manson/51aec9e2395478e775d8

Qutty based on the above qdwmtest https://code.google.com/archive/p/qutty/

Qt implementation of custom DWM window: https://github.com/citorva/CustomWindow

Custom WM_NCPAINT: https://forum.qt.io/topic/3258/how-to-draw-to-the-windows-title-bar-with-qt-is-this-possible

https://forum.qt.io/topic/3258/how-to-draw-to-the-windows-title-bar-with-qt-is-this-possible/10

https://stackoverflow.com/questions/26826660/qt-wm-nccalcsize-black-showing

https://stackoverflow.com/questions/453069/qt-erase-background-windows-aero-glass

Changing the title bar color: https://stackoverflow.com/questions/1487919/how-does-windows-change-aero-glass-color?rq=1

shujaatak commented 4 years ago

Outstanding Delphi project(I downloaded and run the source code and It 100% works!!!) It has no borders issue too!

Although it is written in Delphi but the native Windows APIs are same for both C++ and Delphi.

You can find the project articles here: https://delphihaven.wordpress.com/2010/04/19/setting-up-a-custom-titlebar/ https://delphihaven.wordpress.com/2010/04/22/setting-up-a-custom-title-bar-reprise/ https://delphihaven.wordpress.com/2010/04/15/ever-felt-you-are-being-lied-too/

Project source code here(but it needs a free account): http://cc.embarcadero.com/Item/27688

I have uploaded the source code to GitHub so that you may not have to go through any inconvenience: https://github.com/shujaatak/Delphi_CustomFrame_DWM

I have tested the code with Delphi-10 using Embarcadero RAD Studio 2010 Delphi Architect

As I haven't in-depth knowledge of making custom frameless window or helper so I am giving you pointers which may help you to improve the framelesshelper!

wangwenx190 commented 4 years ago

I wish there was a stripped down just window part of the Mozilla browser so that the people like you ,me and others could benefit but that's another story. Mozilla is best in terms of GUI! But chrome is best in terms of other things.

It's not another story, it's what I'm trying to do these days, what I'm trying to achieve using this repository, however things don't go well because Firefox's source code is very complicated (at least for me) and the frameless window part is mixed with other parts.

I am not sure what I think is correct but what I see is a transparent window part outside of the application window borders.

You are correct. I learned this from a blog some days (maybe weeks) ago. The author of that blog is a professional Windows developer, so it's convincing to me. The resizable area is outside the window, except the top edge. I'm trying to simulate this behavior but I don't really know the solution. But thanks for your articles and repository, I believe it can be solved in the near future.

Thanks again, for your effort. You really helped me a lot.

shujaatak commented 4 years ago

See this one: https://github.com/Ochrazy/Qt-FramelessNativeWindow

It handles the border sizing and has support for mac, linux, and windows. You can get ideas from it for the Windows part.

I like your implementation so I wish framelesshelper may get mature soon.

The example is so impressive that you would like it! The only that it's not as smooth as yours framelesshelper! Please do run their example as it's not like what's shown in screenshots.

wangwenx190 commented 4 years ago

Thanks. I'll have a look at it.

wangwenx190 commented 4 years ago

The resize area issue is now solved. I'm sorry that the solution isn't come from your articles but from Windows Terminal, which is a console application developed by Microsoft. But I really appreciate what you have done these days. I learned a lot from these articles and repositories. I'll upload the code as soon as possible, but I'm quite busy these days so there may be some delay. The new code looks perfect on Windows 10, but I'm wondering it's appearance on Windows 7. That's the only thing I'm worried about.

shujaatak commented 4 years ago

Congratulations! So happy that you found the solution! As far I think the problem lied in WM_NCCALCSIZE and WM_ACTIVATE, but I am curious to see how you solved it. Waiting for your commit now!

shujaatak commented 4 years ago

Microsoft Terminal's minimize, maximize and close buttons flicker when the user resize the window using the LEFT resizing border, it's not like Mozilla Firefox, or Microsoft Edge. I hope you may have found a better solution.

wangwenx190 commented 4 years ago

Microsoft Terminal's minimize, maximize and close buttons flicker when the user resize the window using the LEFT resizing border, it's not like Mozilla Firefox, or Microsoft Edge.

Yes, I can see the flicker, but it doesn't matter. The flicker is caused by other parts of the code. It won't affect the frameless window part. If you use WinNativeEventFilter, the three system buttons will be hidden and it's the user's responsibility to draw them. The trick to avoid the flicker is don't re-position and re-draw them when the window is resizing.

shujaatak commented 4 years ago

Now I am restless to see the framelesshelper in action!

wangwenx190 commented 4 years ago

There's a small problem preventing the code from becoming perfect. The new code needs to draw something in WM_PAINT, but if I use *result = 0; return true; in the message, the application will crash. If I just use break;, all the changes we made in that message will get lost (I guess it's covered by Qt's own painting). But if I move all the code to the widget's nativeEvent function, the application no longer crashes, however it results in a blank window if there exists BeginPaint. Now I'm struggling to find it's solution. I use a hack I learned from Windows Terminal to bring the top frame border back because we get rid of it in WM_NCCALCSIZE. If you don't care about the lost of the top border, the WM_PAINT part will be no longer needed. However, a window without a top border doesn't look very good, so I want to bring it back if possible. Of course you can draw the border yourself through QPainter or anything you like, but this can't be done in NativeEventFilters. Now I'm thinking of using SetWindowRgn or something like that to limit Qt's paint area to avoid our own painting be covered by Qt.

wangwenx190 commented 4 years ago

I tested my code in OBS, despite of the top border issue, nothing flickers.

wangwenx190 commented 4 years ago

The three invisible resize areas are in the window area, but they are outside the client area, and they will become visible because we turned the whole window area into client area in WM_NCCALCSIZE and I don't know how to make them be transparent again without breaking the frame shadow if the window frame is totally removed. So I just keep the three sides of window frame, only get rid of the title bar and top border. I need to do something in WM_PAINT to bring the top border back, but as I said before, some problems occurred. According to the comments in Windows Terminal's source code, DWM will always draw the whole title bar if you modify the top edge of the client area in WM_NCCALCSIZE, unless you don't touch the top edge and let the whole title bar area get lost together with the top border. In other words, there is no way to only get rid of the title bar itself and keep the top border untouched. After many times of testing, I'm convinced it's true.

shujaatak commented 4 years ago

I just tested the updated framwlesshelper and it looks almost perfect! By almost I mean that the the three side borders appear as white lines if the window is focused else gray lines, the rest is PERFECT! (The below screenshot shows the above behaviour). I tested with both QWidget and QMainWindow, there is no flicker not like in many other frameless helpers including Microsoft Terminal which flickers when a user resize window using LEFT or TOP resizing borders! screenshot2

shujaatak commented 4 years ago

I have a very high respect for you for doing this great job! You have almost found the secret!

wangwenx190 commented 4 years ago

Thanks! I'm glad this repo can help you! The window border's color should be the theme color or black when the window is active, it shouldn't be white. I'll have a look at it. But it's normal if it's color become gray when the window loses focus, it's not a bug. As for the WM_PAINT problem, the Qt engineer tell me to try message hook. I'll give it a try.

shujaatak commented 4 years ago

When the window loses focus, the left, bottom and right borders becomes gray but the top remain same as when the window is in focus.

As for the WM_PAINT problem, the Qt engineer tell me to try message hook. I'll give it a try.

I am hopeful that you will find the solution!

wangwenx190 commented 4 years ago

After some discussion with the Qt engineer, I learned that developers shouldn't do anything in WM_PAINT because it will break Qt's backing store mechanism. However, the top border can't be seen if we don't do the hack in WM_PAINT. I managed to solve this yesterday, but the repository was completely re-written. I'll open a new branch for the new solution. The master branch will be reverted back to the original solution.

wangwenx190 commented 4 years ago

The original solution supports Win7~Win10, but it has the resize area issue. The new solution only supports Windows 10 but it looks perfect. The only problem of the new solution is that it's more invasive (because of not using NativeEventFilter) so you will have to write more code if you want to integrate it into your own project.

wangwenx190 commented 4 years ago

the top remain same as when the window is in focus.

That's because there is no top border actually. We have get rid of it in WM_NCCALCSIZE. So I need to use a hack to bring it back in WM_PAINT.

shujaatak commented 4 years ago

People have started transitioning towards Windows 10 after the Windows 7 End of Support. The Windows 10 market share is increasing very fast. screenshot2 Ref: https://gs.statcounter.com/os-version-market-share/windows/desktop/worldwide

shujaatak commented 4 years ago

Yet another repository to get ideas from: https://github.com/oberth/custom-chrome

shujaatak commented 4 years ago

Would WM_NCPAINT help too?

wangwenx190 commented 4 years ago

People have started transitioning towards Windows 10 after the Windows 7 End of Support. The Windows 10 market share is increasing very fast. screenshot2 Ref: https://gs.statcounter.com/os-version-market-share/windows/desktop/worldwide

Thanks! I have read it. It seems that Win10 has the most users in the world, however, things are quite different in my homeland, China. In China there are many people still use Win7 (some people even use XP! They don't care about safety or new features) in their office or at home, so it not a good idea to completely drop Win7 support, at least for Chinese. That's also the reason why I didn't delete the original solution. It's not perfect but it works well from Win7 to Win 10 at least.

Would WM_NCPAINT help too?

I tried it already but all the changes I made in WM_NCPAINT will get lost once Qt repaint the window. What's worse, it causes severe flickering when the window is being resized.

Yet another repository to get ideas from: https://github.com/oberth/custom-chrome

Thanks! I'll have a look at it.!

shujaatak commented 4 years ago

I managed to solve this yesterday, but the repository was completely re-written. I'll open a new branch for the new solution.

Can't wait to check it out!

shujaatak commented 4 years ago

win10_only branch behaves same as master branch i.e. the resizing borders are inside the application's window borders.

wangwenx190 commented 4 years ago

win10_only branch behaves same as master branch i.e. the resizing borders are inside the application's window borders.

You can try the new branch now. I pushed new code just now. Maybe the branch was created a bit earlier :)

The new code is not finished yet but is usable at least. There are some more detailed work to do but you can start testing it. I haven't look into the border color issue, but now the top border is back at least.

I guess the border color issue is cause by DwmExtendFrameIntoClientArea. I'll check it when I'm free, but I'm quite busy these days, so it may take a while.

wangwenx190 commented 4 years ago

The new solution uses qtwinmigrate from Qt Solutions, which is a commercial module in the Qt 4 times. It become open-source but unmaintained at some time.

The new solution uses a custom QWidget as a bridge, embed itself into a native Win32 window, so we can bring our top border back without breaking the backing store mechanism of Qt. However, this structure makes things much more complicated. There are much more work to do such as implementing basic window functions.

shujaatak commented 4 years ago

win10_only branch results with this stylesheet:

widget->setStyleSheet("border: 1px solid red;background-color:green");

(please click on the following screenshot to see it in it's original size)

screenshot2

What I was expecting? I was expecting a clean red border enclosing the window but as you can see the bottom side is missing the red border, also there is white border covering all four sides of the window. No matter if I apply the stylesheet to widget or winWidget the results are same.

wangwenx190 commented 4 years ago

Thanks for your testing!

I can see your snapshot. The white border around the four edges is the standard window frame border provided by Windows actually, so you can't paint anything on it. It's on the non-client area ,which is handled by DWM. Qt can only paint on client area. The color of it is controlled by system, you can change your theme color and the border color will also change.

But the border color should be black, not white, if you don't set a custom theme color. I'm still investigating it.

If you really want to draw your own frame border, I'm sorry that you will have to use the original solution because this can't be done in this solution. The original solution removed all the frame borders so you can draw anything you want on the window, including custom title bar and frame border. But this solution keeps the borders untouched, so their thickness and color are handled by DWM and thus can't be controlled by the developer. I don't know whether there are official Win32 APIs to modify them.

About the missing bottom border, I may know the reason, I'll try to fix it today.

wangwenx190 commented 4 years ago

Commit 4aec1f147d9768dbdce49eb8cb08e5bb02aec0bc fixed the bottom border issue. The root cause is I miscalculated the content widget's size. Easy to fix. Border color issue still investigating.

wangwenx190 commented 4 years ago

The reason why the frame border's color is white is now cleared. After calling DwmExtendFrameIntoClientArea, the frame borders become transparent (not white). It's a DWM bug and can be fixed in WM_PAINT. Still working on this.

shujaatak commented 4 years ago

But this solution keeps the borders untouched, so their thickness and color are handled by DWM and thus can't be controlled by the developer.

You are right, the stylesheet just draw inside the client area as it can be seen in the following fullscreen window(with the bottom border issue resolved):

screenshot2

For me it seems that the border color is white not transparent, as you can see in the following screenshot:

screenshot2

If the border was transparent then in the above screenshot we could only see the red border.

wangwenx190 commented 4 years ago

After some testing, I found that just calling DwmExtendFrameIntoClientArea won't change the border color, using it to extend the window frame will cause the border color issue. This can be reproduced in normal Win32 projects, so it's clear that the border color issue is not caused by the frameless code. I'll continue working on this.

For me it seems that the border color is white not transparent

Yeah, you are right. I found this as well. If this is not the transparency bug mentioned in MSDN, it will be more difficult to solve.

screenshot2

Sorry for the late reply, but this picture is not accurate enough. For the original solution, the resize area is inside the window indeed, this is true, however, the frameless window's size = the client area's size of the original window + the three transparent resize areas' size. As what I mentioned before, we have turned the whole window area into the client area, including the invisible resize area. So the window size will expand, actually.

shujaatak commented 4 years ago

win10_only branch: I think I found a hack/solution. First of all I noticed that Microsoft itself has no solution for the black top border. I noticed that all Windows Frameless Applications like Edge, Windows Settings, etc show a black top border when the application is active/in-focus otherwise show a semi-transparent border. So I thought why not we should do the same after all if Microsoft itself can't draw all borders with the same color then how can we. I noticed that Mozilla Firefox also has the same black top border(try to look closely) and Google Chrome has blue top border(I wonder how it does this).

Enough bragging, now here is what I did. I declared a bool variable isActivated before the switchstatement inside QWinNativeWindow::WndProc and initialized it to false. Then I made the following changes:

    case WM_ACTIVATE:
    {
        if (wParam == NULL)
            isActivated = false;
        else
            isActivated = true;
    }
    case WM_DWMCOMPOSITIONCHANGED:
        UpdateFrameMarginsForWindow(hWnd, isActivated);
        break;

I added a bool isActivated parameter to UpdateFrameMarginsForWindow like this: VOID UpdateFrameMarginsForWindow(HWND handle, bool isActivated)

And made only following changes inside UpdateFrameMarginsForWindow :

//margins.cyTopHeight = GetFrameSizeForWindow(handle, TRUE).top;
 if(!isActivated) margins.cyTopHeight = -1;

That's it! Now the window behaves same as Edge, Firefox etc.

screenshot2

shujaatak commented 4 years ago

As far as I think, now win10_onlybranch works perfectly but can I ask why this branch would not work in windows-7 or what kind of problems it will show?

shujaatak commented 4 years ago

My bad, although Mozilla Firefox's top border remains solid black with both Windows Light and Dark themes but Edge and other Windows applications top border becomes semi-transparent with Light theme! My hack behaves same like Firefox, the top border remains solid black no matter if I use Light or Dark theme although with Dark theme framelesshelper based window with above hack looks good!

shujaatak commented 4 years ago

On the other side Google Chrome shows only solid blue top border(I wonder how) no matter if I use Light or Dark Windows theme:

screenshot2

shujaatak commented 4 years ago

Another observation, Edge sometimes show solid black top border and sometime show it as semi-transparent this means the Engineers behind it use some kind of hacks!

wangwenx190 commented 4 years ago

Thanks for all your work! I really appreciate it!

The behavior of Google Chrome is not standard. The top border's color should not be blue unless the user change his/her theme color to blue. But from your snapshot I know it's not. So the conclusion is the top border is drawn by Chrome manually and it's color is wrong.

You can try notepad.exe or explorer.exe. In dark mode, it's four borders are all solid black when it has focus, they become gray when it loses focus. It means, in dark mode, all the window borders should be solid black, not only the top border. However, in light mode, all the four borders are gone (or become white? can't see clearly) when the window has focus, they become gray when it loses focus. It's very strange that UWP applications have a solid black border on top and I think this looks a bit strange.

I can judge whether the user is using light theme or dark theme through the registry, this is not difficult because I often do it. Maybe I can adjust the border color according to the user's settings.

Your solution works quite well, thanks for it! Maybe this issue can be solved within this week.

As far as I think, now win10_onlybranch works perfectly but can I ask why this branch would not work in windows-7 or what kind of problems it will show?

win10-only branch just remove the top part of the window, it leaves the other three borders untouched. This doesn't look strange on Windows 10 because the border width on Windows 10 is just one pixel. However, the border width on Windows 7 is eight pixels (when DPI is 96), if we do the same thing just like what we do on Windows 10, the window will look terrible. I have tried it once, the window looks terrible indeed.

Another observation, Edge sometimes show solid black top border and sometime show it as semi-transparent this means the Engineers behind it use some kind of hacks!

Maybe it's using DwmGetColorizationColor to get the theme color. This API has a bug according to the comments in Qt Win Extras's source code. It sometimes will return a wrong semi-transparent color. So Qt will read the color from the registry, not through this API, although it's the official way to do this. Or maybe I'm wrong and it's caused by the hacks. Chrome is full of hacks indeed. But who knows.

shujaatak commented 4 years ago

You can try notepad.exe or explorer.exe. In dark mode, it's four borders are all solid black when it has focus, they become gray when it loses focus. It means, in dark mode, all the window borders should be solid black, not only the top border.

What I see is Notepad.exe has standard title bar and I have noticed that, in dark mode and when it is focused, it shows all four borders as dark-grayish color(not solid black) with transparency, you have to look very closely for seeing through the borders. But when notepad is out of focus, it show all four borders as light-grayish color with transparency. While in light mode and when it is focused, notepad.exe shows all four borders as white color with transparency but when notepad is out of focus it shows all borders with the same light-grayish color with transparency like in dark mode.

screenshot2

When in focus or out of focus, the Edge, Windows Settings Application etc. which have custom title bars show LEFT, BOTTOM and RIGHT borders same as Notepad.exe in both Dark and Light modes. In dark mode and when in focus, the top border is solid black and when out of focus it becomes light-grayish color with transparency like other three borders. While in light mode and when in focus, the top border is dark-grayish with transparency and when out of focus it becomes light-grayish color with transparency like other three borders.

screenshot3

In dark mode, I have noticed that the Edge top border is sometimes shown as solid black and sometimes dark-grayish with transparency same as the other three borders. I can see this behavior of the Edge top border by randomly clicking on title bar and sometimes on the widgets inside it like address bar etc.

My Windows 10 version is 1909.

wangwenx190 commented 4 years ago

The little border is making everything more and more complicated.

dark-grayish color(not solid black) with transparency

My English is not good, it's VERY hard for me to describe things like that. So maybe many of my words are lack of accuracy, I'm really sorry for that.

Here is the problem. We managed to bring the top border back by draw one manually, but it's color is not what we want. We can paint it using any color(brush) in WM_PAINT, but it can't be changed then. For example, once I draw a red line, I can't draw a blue line on top of it afterwards. However, we have to paint different colors for different states of the application, such as get focus, loses focus, user changed theme color, users switched to light or dark mode, etc.

No need to worry about the transparency problem. No matter what color you paint, the line will always become semi-transparent as long as it's painted on top of the border line.

wangwenx190 commented 4 years ago

I'm also using 1909 so I don't know whether the behavior of the four window borders vary between different Windows versions or not.

wangwenx190 commented 4 years ago

And thanks for your snapshots. They make things much more clear.

shujaatak commented 4 years ago

What I noticed today is that Google Chrome uses accent color as the top border color.

screenshot2

Apparently Edge and Visual Studio use a dirty trick(a transparent window in the background)

screenshot2

wangwenx190 commented 4 years ago

I tried QWizard just now. It also looks frameless on Windows 10 (it has a title bar on Win7), and it looks quite well. But it's four window borders are also white when it has focus. That's what this code initially does. What about just use this (I mean, forget about the borders, just leave it white, since it doesn't look bad) ?

Apparently Edge and Visual Studio use a dirty trick(a transparent window in the background)

Actually, this is what many software does. It will look perfect if you embed your window into a larger transparent window and draw a frame shadow yourself. But it's very dirty indeed. I don't like this kind of tricks. I'll never do it.

shujaatak commented 4 years ago

The white borders would look OK with a light theme but they would look horrible with a dark theme of an application.

shujaatak commented 4 years ago

By the way, highly successful commercial projects like Adobe products like Photoshop etc., AnyDesk, BlueStacks, Camtasia, and many more softwares which I have used but now can't remember the names have the resizing borders inside the application borders just like framelesshelper! I think your approach used in master branch is quite well and would work for most of the projects and hopefully users won't be annoyed as most of the users have already got used to it.

wangwenx190 commented 4 years ago

By the way, highly successful commercial projects like Adobe products like Photoshop etc., AnyDesk, BlueStacks, Camtasia, and many more softwares which I have used but now can't remember the names have the resizing borders inside the application borders just like framelesshelper!

Yes, I've noticed this as well. But I think the border issue can be solved. I'm still investigating it.