dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.05k stars 1.17k forks source link

Strange behavior of GlassFrameThickness in WindowChrome. #7769

Open naumenkoff opened 1 year ago

naumenkoff commented 1 year ago

Hello!

Why, when using WindowChrome, if you set GlassFrameThickness="0", the edges remain rounded when trying to snap the window to any part of the screen? If you set any other value for GlassFrameThickness, the edges will not be rounded when the window is snapped to any part of the screen. Additionally, when using any value for GlassFrameThickness other than 0, white stripes appear in the direction of size changes, but this is not the case with GlassFrameThickness="0". What causes this behavior and how can it be changed?

Additionally, if you use a non-zero value for GlassFrameThickness, resizing occurs smoothly, but if GlassFrameThickness is set to 0, it will appear jerky and look terrible.

My question is, how can I get rid of the white stripes when resizing the window (which are not present when GlassFrameThickness="0"), while still maintaining smooth resizing (which is present when GlassFrameThickness is not equal to 0) and correct window snapping to the screen borders or when using Snap Layouts (i.e., the window will become square, as when GlassFrameThickness is not equal to 0)?

https://user-images.githubusercontent.com/40211394/234741460-0d97ce2f-bd8a-4b22-a2eb-4edc28b1bb6c.mp4

naumenkoff commented 1 year ago

The default corner radius isn't 0 you need to specify that in the WindowChrome to get the behavior you want

  <WindowChrome.WindowChrome>
        <WindowChrome
            CaptionHeight="0"
            CornerRadius="0"
            GlassFrameThickness="0"
            UseAeroCaptionButtons="False" />
    </WindowChrome.WindowChrome>

This doesn't explain some of the behavior of your issue though and could hint that Corner Radius isn't being respected correctly.

Either I didn't express myself correctly, or you didn't fully understand my question.

Imagine that I set the value of GlassFrameThickness to 1. In this case, my window is clearly outlined (behaving similarly to what's described in this article: apply-rounded-corners), has rounded corners, and if I want to attach it to any part of the screen, either by doing it manually or by using Snap Layouts, the window will become square. In addition to this, when resizing the window, the contents of the window adjust smoothly to the new size, but there's a downside - a noticeable white strip appears in the direction of resizing the window.

However, if I set the value of GlassFrameThickness to 0 (i.e., disable it, as stated in the documentation), here's what will happen - the rounded corners remain (I don't care about that), and if I want to attach the window to the edge of the screen manually or using Snap Layouts, it remains round and does not become square. Because of this, if I place the window, for example, on the left half of the screen, glimmers of my desktop will be visible at the bottom left and top left corners. Additionally, if I fill the remaining part of the screen with any application, it will have four glimmers - in all corners, I will see my desktop. Besides not becoming square, there's another problem - the content of the window adapts abruptly and jerkily when its size changes. But in addition to these two downsides, there's one advantage - there are no white artifacts when the screen size changes abruptly.

naumenkoff commented 1 year ago

The default corner radius isn't 0 you need to specify that in the WindowChrome to get the behavior you want

  <WindowChrome.WindowChrome>
        <WindowChrome
            CaptionHeight="0"
            CornerRadius="0"
            GlassFrameThickness="0"
            UseAeroCaptionButtons="False" />
    </WindowChrome.WindowChrome>

This doesn't explain some of the behavior of your issue though and could hint that Corner Radius isn't being respected correctly.

Either I didn't express myself correctly, or you didn't fully understand my question. Imagine that I set the value of GlassFrameThickness to 1. In this case, my window is clearly outlined (behaving similarly to what's described in this article: apply-rounded-corners), has rounded corners, and if I want to attach it to any part of the screen, either by doing it manually or by using Snap Layouts, the window will become square. In addition to this, when resizing the window, the contents of the window adjust smoothly to the new size, but there's a downside - a noticeable white strip appears in the direction of resizing the window. However, if I set the value of GlassFrameThickness to 0 (i.e., disable it, as stated in the documentation), here's what will happen - the rounded corners remain (I don't care about that), and if I want to attach the window to the edge of the screen manually or using Snap Layouts, it remains round and does not become square. Because of this, if I place the window, for example, on the left half of the screen, glimmers of my desktop will be visible at the bottom left and top left corners. Additionally, if I fill the remaining part of the screen with any application, it will have four glimmers - in all corners, I will see my desktop. Besides not becoming square, there's another problem - the content of the window adapts abruptly and jerkily when its size changes. But in addition to these two downsides, there's one advantage - there are no white artifacts when the screen size changes abruptly.

I do understand and it does sound like a bug, I'm not trying to dismiss this an a non-issue, just offering a workaround.

My suggestion to set CornerRadius=0 will workaround some of the undesirable behavior you are seeing in the corners.

You could take this advice one step further and change CornerRadius to 0 when Window State is Maximized and change it back in any other Window State

The cause of the issue is probably still CornerRadius not being respected properly because it should be 0 when the window is in a Maximized state

The white stripes while resizing with the glass frame is a known issue, I just don't remember the issue number. (The issue might actually be in a different repo)

This is caused by the glass frame not having the same background color as the content of the window

You are right that removing the glass frame will mean your controls don't update their position as fast because it changes the hierarchy, which is likely what is causing the adapts abruptly and jerkily when its size changes

I am upset that I cannot interact properly with Snap Layouts (I searched for ways but couldn't find them), instead I have to come up with a workaround using SystemParameters.WorkArea and window position. Let me explain in more detail: if you press Win+Z, the Snap Layouts window opens. In this window, you can select the window layout, and if you do so, the WindowState will be Normal in any case, not Maximized. Therefore, without a workaround behavior class, I will not be able to implement the correct functioning of the application with Snap Layouts. I want the window to become square when a person EITHER selects the window layout through Snap Layouts OR attaches it to the edge of the screen independently. But unfortunately, as far as I understand, there is no such option. Therefore, the best solution would be to ENABLE GlassFrameThickness by setting some value, for example -1, because in this case the window will automatically become square or round, depending on whether it is attached to the edge of the screen. In this case, you will have to sacrifice the terrible visual aspect when resizing the window - white stripes, but in return, you will get smooth window resizing and automatic rounding of the window. It would be cool to have some property and enumeration that specifies to which part of the screen the window is attached (TopLeft, TopCenter, TopRight, Center, etc.)

aquinn39 commented 1 year ago

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

        [DllImport("dwmapi.dll", PreserveSig = true)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;
DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

naumenkoff commented 1 year ago

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

        [DllImport("dwmapi.dll", PreserveSig = true)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;
DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

        [DllImport("dwmapi.dll", PreserveSig = true)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;
DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

Yes, I have tried this option and it solves the problem with rounded edges, i.e. the rounded edges become square when the window is attached to the screen border. However, another problem arises - a flickering black bar appears above the window during application resizing. In addition, due to GlassFrameThickness="0", the window content adapts very abruptly and it looks terrible. Moreover, in this case, white stripes are replaced with "transparent" ones - when resizing the window, I can see the window border that is located ten pixels away from the visible application window (there is just empty space there due to some bug). The code is similar to what is written in the apply-rounded-corners) article.

https://user-images.githubusercontent.com/40211394/235235823-e5c6f53f-63b6-47d4-a05a-ba5360676044.mp4

aquinn39 commented 1 year ago

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF. So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised). First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

        [DllImport("dwmapi.dll", PreserveSig = true)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;
DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF. So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised). First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

        [DllImport("dwmapi.dll", PreserveSig = true)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;
DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

Yes, I have tried this option and it solves the problem with rounded edges, i.e. the rounded edges become square when the window is attached to the screen border. However, another problem arises - a flickering black bar appears above the window during application resizing. In addition, due to GlassFrameThickness="0", the window content adapts very abruptly and it looks terrible. Moreover, in this case, white stripes are replaced with "transparent" ones - when resizing the window, I can see the window border that is located ten pixels away from the visible application window (there is just empty space there due to some bug). The code is similar to what is written in the apply-rounded-corners) article.

WindowChrome2.mp4

That is very strange and does seem like an issue with the WindowChrome. Just out of curiosity, do you have AllowsTransparency set to true or false and also does the window itself have a background? And does changing any of those things affect the issue?

naumenkoff commented 1 year ago

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:


        [DllImport("dwmapi.dll", PreserveSig = true)]

        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):


var value = 2;

DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:


        [DllImport("dwmapi.dll", PreserveSig = true)]

        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):


var value = 2;

DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

Yes, I have tried this option and it solves the problem with rounded edges, i.e. the rounded edges become square when the window is attached to the screen border. However, another problem arises - a flickering black bar appears above the window during application resizing. In addition, due to GlassFrameThickness="0", the window content adapts very abruptly and it looks terrible. Moreover, in this case, white stripes are replaced with "transparent" ones - when resizing the window, I can see the window border that is located ten pixels away from the visible application window (there is just empty space there due to some bug). The code is similar to what is written in the apply-rounded-corners) article.

WindowChrome2.mp4

That is very strange and does seem like an issue with the WindowChrome.

Just out of curiosity, do you have AllowsTransparency set to true or false and also does the window itself have a background? And does changing any of those things affect the issue?

The window has no background and transparency is disabled.

aquinn39 commented 1 year ago

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

    [DllImport("dwmapi.dll", PreserveSig = true)]
    public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;

DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

I think I know what is happening here regarding the rounded corners issue and how to fix it. It seems like, when GlassFrameThickness is set to 0, WPF is applying its own rounded corners instead of Windows 11 doing it. To fix it, we just have to tell Windows 11 to handle the rounded corners instead of WPF.

So try setting CornerRadius to 0 and then running the following code. It uses PInvoke to call the DwmSetWindowAttribute function in order to set the DWM_WINDOW_CORNER_PREFERENCE to DWMWCP_ROUND which rounds the corners if appropriate (i.e. it will not round them when the Window is snapped, like what you want but will round them normally when the window is not maximised).

First, declare this function somewhere after adding "using System.Runtime.InteropServices;" to the top of the file:

    [DllImport("dwmapi.dll", PreserveSig = true)]
    public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

Then, call this function (if necessary, change 'this' to the window you want to affect) like so when your window loads (so on the window's loaded event for example since the window handle will be created by then and it is needed for this code):

var value = 2;

DwmSetWindowAttribute(new System.Windows.Interop.WindowInteropHelper(this).Handle, 33, ref value, System.Runtime.InteropServices.Marshal.SizeOf(value));

So first set the CornerRadius to 0 and then run the above code and the rounded corners should behave as they are supposed to with GlassFrameThickness set to 0.

Yes, I have tried this option and it solves the problem with rounded edges, i.e. the rounded edges become square when the window is attached to the screen border. However, another problem arises - a flickering black bar appears above the window during application resizing. In addition, due to GlassFrameThickness="0", the window content adapts very abruptly and it looks terrible. Moreover, in this case, white stripes are replaced with "transparent" ones - when resizing the window, I can see the window border that is located ten pixels away from the visible application window (there is just empty space there due to some bug). The code is similar to what is written in the apply-rounded-corners) article.

WindowChrome2.mp4

That is very strange and does seem like an issue with the WindowChrome. Just out of curiosity, do you have AllowsTransparency set to true or false and also does the window itself have a background? And does changing any of those things affect the issue?

The window has no background and transparency is disabled.

I have done some of my own testing and have the same issues. I did find that setting NonClientFrameEdges to anything other than None fixes it, but it seems to result in a small Windows border being added around the window.

naumenkoff commented 1 year ago

I have done some of my own testing and have the same issues. I did find that setting NonClientFrameEdges to anything other than None fixes it, but it seems to result in a small Windows border being added around the window.

Unfortunately, it didn't work for me :(

I'm a little surprised that I couldn't find a single program on my PC that didn't have the aforementioned issues. Some applications have white stripes, while others have issues with visual separation of elements (as at the end of the attached video), such as Telegram, Google Chrome, JetBrains Rider, Steam, Discord. The only ones that work perfectly are Microsoft and most of their applications (although performance could be better) - they don't have any of the aforementioned issues in this thread. For example, at Microsoft where everything works perfectly, the visual adaptation of window elements happens at 15 FPS, while using up to 10% CPU on a Ryzen 5 5600X. Telegram uses 14% CPU. JetBrains Rider uses 20% CPU. My application uses 17% CPU (I'm not using UseLayoutRounding and SnapsToDevicePixels). Google Chrome uses 26% CPU and 10% GPU. Interestingly, after adding UseLayoutRounding="True" to the MainWindow, CPU usage decreases to 3.5%.

https://user-images.githubusercontent.com/40211394/235310190-f8b4921a-915a-42d1-95a6-fbd7350bdc41.mp4

aquinn39 commented 1 year ago

I have done some of my own testing and have the same issues. I did find that setting NonClientFrameEdges to anything other than None fixes it, but it seems to result in a small Windows border being added around the window.

Unfortunately, it didn't work for me :(

I'm a little surprised that I couldn't find a single program on my PC that didn't have the aforementioned issues. Some applications have white stripes, while others have issues with visual separation of elements (as at the end of the attached video), such as Telegram, Google Chrome, JetBrains Rider, Steam, Discord. The only ones that work perfectly are Microsoft and most of their applications (although performance could be better) - they don't have any of the aforementioned issues in this thread. For example, at Microsoft where everything works perfectly, the visual adaptation of window elements happens at 15 FPS, while using up to 10% CPU on a Ryzen 5 5600X. Telegram uses 14% CPU. JetBrains Rider uses 20% CPU. My application uses 17% CPU (I'm not using UseLayoutRounding and SnapsToDevicePixels). Google Chrome uses 26% CPU and 10% GPU. Interestingly, after adding UseLayoutRounding="True" to the MainWindow, CPU usage decreases to 3.5%.

WindowChrome3.mp4

And just to confirm you're not using anything that would create child windows (such as WebBrowser, WebView, WebView2, WindowsFormsHost, etc.)? Either way it's definitely something Microsoft could improve on. As you've pointed out it is an issue a lot of applications have.

CodingOctocat commented 10 months ago

I'm using HandyControl and I'm experiencing similar problems:

hc:Window.NonClientAreaContent

  1. white area when changing window size (rendering lag?)
  2. 1px black line at the top of the window