dotnet / winforms

Windows Forms is a .NET UI framework for building Windows desktop applications.
MIT License
4.42k stars 984 forks source link

designer form doesn't take care about window state Activation #8107

Closed memoarfaa closed 3 weeks ago

memoarfaa commented 2 years ago

Environment

Microsoft Visual Studio Enterprise 2019 Version 16.11.20

.NET version

.NET Core 3.1 and.NET 5

Did this work in a previous version of Visual Studio and/or previous .NET release?

No

Issue description

designer doesn't take care about window state Activation state of form

Steps to reproduce

open new form in design mode click Properties

2022-11-02_23-31-26

.net framwork

2022-11-02_23-34-13

Diagnostics

No response

RussKie commented 2 years ago

@memoarfaa just to confirm I understand the issue correctly - is the issue related to to the fact the form remains in focus? If yes - why is it important, and how is it affecting your developer experience?

memoarfaa commented 2 years ago

is the issue related to to the fact the form remains in focus? yes Save time while designing

https://user-images.githubusercontent.com/12494184/199631863-68c21bdc-80c7-4e04-bb56-c236f8058710.mp4

https://user-images.githubusercontent.com/12494184/199631872-739918bc-f686-4ba3-bc36-a24ac7119f83.mp4

RussKie commented 2 years ago

Looking at your animation, it looks like you're running a custom form with custom properties and behaviours (doing NC-painting?)....

memoarfaa commented 2 years ago

Yes true https://github.com/memoarfaa/SkinFormCore

I just started a new theme library that has been converted from Winforms .Net framework to .net Core but .net Core designer is not responding to any Nc painting until Close and reopen the form every time

memoarfaa commented 2 years ago

I fixed the Nc painting by add SetWindowPos to every property but I still can't get Inactive title bar color because .net Core designer doesn't take care about WM_NCACTIVATE message . any idea.

SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, (int)(SWPFlags.SWP_NOACTIVATE | SWPFlags.SWP_NOMOVE | SWPFlags.SWP_NOSIZE | SWPFlags.SWP_NOZORDER | SWPFlags.SWP_FRAMECHANGED));

https://user-images.githubusercontent.com/12494184/200195050-21649345-63c2-4afe-9801-c9e8a4c07ff2.mp4

RussKie commented 2 years ago

Yes true memoarfaa/SkinFormCore

This looks pretty cool. What license have you put your code under?

memoarfaa commented 2 years ago

MIT License Any suggestion to make it widely used in .Net Core Winforms is Welcome

RussKie commented 2 years ago

If you're up for a challenge, we have some long-standing issues with MDI child window rendering - https://github.com/dotnet/winforms/issues/3691. The root issue is within the realms of Windows code, but there's no hope it'll ever be fixed. So an alternative would be to come up with a Windows Forms opt-in functionality (e.g., a renderer of some kind) that would allow rendering NC-area of the MDI child windows to look native. 😉

memoarfaa commented 2 years ago

In short answer issues https://github.com/dotnet/winforms/issues/3691. solved in memoarfaa/SkinFormCore 1- download SkinFormCore v1 2- Add refence to SkinFramWorkCore.dll in your project 3- Create new MDI Window 4- Create methods to Show child window new SkinForm() { MdiParent = this }.Show();

Result is

2022-11-08_12-19-27

I'm also attached simple project

WinFormsApp2.zip

I'm waiting the answer about your test result in any operating system 8,8.1,10,11

merriemcgaw commented 2 years ago

@lonitra would be super interested in what you're doing with the skinning, thank you for sharing! This is something we will have to investigate in our out of process designer. It might be that the window needs to be invalidated to force it to repaint.

merriemcgaw commented 3 weeks ago

We're tracking that child window in VS on our end. We've also got some dark mode and styling work starting in .NET 9, so I think we can close this.

memoarfaa commented 2 weeks ago

@merriemcgaw

We're tracking that child window in VS on our end. We've also got some dark mode and styling work starting in .NET 9, so I think we can close this.

This issue was related to the non-client area of ​​the window at design time. In .NET 9, the non-client area has become less stable and more vulnerable not only in design mode but also at runtime. This is due to the new win32 API "DwmSetWindowAttribute" that was introduced internally in .NET 9.

see https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute

If I want to test my dark mode at design time eg:

public partial class Form1 : Form
{
        public Form1()
        {
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
            Application.SetColorMode(SystemColorMode.Dark);
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

            InitializeComponent();

        }
 }

https://github.com/user-attachments/assets/1aaf9110-218f-43d0-9dc4-7a8cfb784fc9

1- VS Designer look bad in dark mode at least the the non-client area Back Color need to be changed.

all custom non-client area painting not working Correctly in dark mode at runtime !. (I will open an issue if required).

why all custom non-client area painting not working Correctly in dark mode at runtime.

this is because the first issue about new win32 API "DwmSetWindowAttribute" that was introduced internally in .NET 9 is it prevent remove the form theme using "SetWindowTheme" win32 API from being applied in "OnHandleCreated" event

related issue is https://github.com/dotnet/winforms/issues/12014 eg:

    public partial class Form1 : Form
    {
        public Form1()
        {

#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
            if (Application.ColorMode != SystemColorMode.Dark)
                Application.SetColorMode(SystemColorMode.Dark);
#pragma warning restore WFO5001 // Type is for ev

            InitializeComponent();
        }

        private const uint WM_NCUAHDRAWCAPTION = 0x00AE;
        private const uint WM_NCUAHDRAWFRAME = 0x00AF;
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            PInvoke.SetWindowTheme((HWND)Handle, "", "");
        }

        protected override void SetVisibleCore(bool value)
        {
            base.SetVisibleCore(value);
          //  PInvoke.SetWindowTheme((HWND)Handle, "", "");

        }

        protected override void WndProc(ref Message m)
        {
            switch ((uint)m.Msg)
            {

                case PInvoke.WM_NCACTIVATE:
                case PInvoke.WM_NCPAINT:
                    base.WndProc(ref m);
                    OnWmNcPain(ref m);
                    break;
                case WM_NCUAHDRAWCAPTION:
                case WM_NCUAHDRAWFRAME:
                    m.Result = 0;
                    break;
                case PInvoke.WM_NCHITTEST:
                    OnWmNcHitTest(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        private void ExcludeClipRect(HDC hdc, RECT rcExclude)
        {
            PInvoke.ExcludeClipRect(hdc, rcExclude.left, rcExclude.top, rcExclude.right, rcExclude.bottom);
        }

        private unsafe void OnWmNcPain(ref Message m)
        {

            HWND hWnd = (HWND)m.HWnd;
            HRGN region = HRGN.Null;
            HRGN copy = PInvoke.CreateRectRgn(0, 0, 0, 0);
            if (PInvoke.CombineRgn(copy, (HRGN)(IntPtr)m.WParam, HRGN.Null, RGN_COMBINE_MODE.RGN_COPY) != GDI_REGION_TYPE.RGN_ERROR)
            {
                region = copy;
            }
            else
            {
                PInvoke.DeleteObject(copy);
            }

            GET_DCX_FLAGS flags = GET_DCX_FLAGS.DCX_WINDOW | GET_DCX_FLAGS.DCX_CACHE | GET_DCX_FLAGS.DCX_INTERSECTRGN;
            if (m.Msg == PInvoke.WM_NCLBUTTONDOWN)
            {
                flags = GET_DCX_FLAGS.DCX_WINDOW | (GET_DCX_FLAGS)0x00010000;
                region = HRGN.Null;
            }
            HDC hdc = PInvoke.GetDCEx(hWnd, region, flags);
            if (((HRGN)m.WParam == (int)GDI_REGION_TYPE.NULLREGION) || hdc.Value == IntPtr.Zero || m.Msg != PInvoke.WM_NCPAINT)
                hdc = PInvoke.GetWindowDC(hWnd);
            if (hdc.Value != IntPtr.Zero)
            {
                RECT rcWin = new RECT();
                RECT rcClient = new RECT();
                PInvoke.GetWindowRect(hWnd, out rcWin);
                PInvoke.GetClientRect(hWnd, out rcClient);
                PInvoke.MapWindowPoints(HWND.Null, (HWND)m.HWnd, (Point*)&rcWin, 2);
                PInvoke.OffsetRect(ref rcClient, -rcWin.left, -rcWin.top);
                ExcludeClipRect(hdc, rcClient);
                PInvoke.OffsetRect(ref rcWin, -rcWin.left, -rcWin.top);
                using Graphics g = Graphics.FromHdc(hdc);
                VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.CreateElement("Window", 0, 0));
                g.Clear(SystemColors.WindowFrame);
                RECT rcClose = new RECT(rcWin.Width - 42, 10, rcWin.Width - 10, 28);
                int width = rcClose.Width;
                renderer.SetParameters(VisualStyleElement.Window.CloseButton.Normal);
                renderer.DrawBackground(g, rcClose);
                rcClose.left -= width + 2;
                rcClose.right -= width + 2;
                renderer.SetParameters(VisualStyleElement.Window.MaxButton.Normal);
                renderer.DrawBackground(g, rcClose);
                rcClose.left -= width + 2;
                rcClose.right -= width + 2;
                renderer.SetParameters(VisualStyleElement.Window.MinButton.Normal);
                renderer.DrawBackground(g, rcClose);
                if (Icon is not null && ShowIcon)
                {
                    g.DrawIcon(new Icon(Icon, SystemInformation.SmallIconSize), 10, 10);
                }
                if (!string.IsNullOrEmpty(Text))
                {
                    TextRenderer.DrawText(g, Text, SystemFonts.CaptionFont, new Rectangle(30, 10, rcWin.Width - 225, 30), Color.White, TextFormatFlags.Left | TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis | TextFormatFlags.ExternalLeading);
                }
                PInvoke.ReleaseDC(hWnd, hdc);
            }

            m.Result = 0;
        }

        private void OnWmNcHitTest(ref Message m)
        {
            Point point = default;
            point.X = LOWORD(m.LParam.ToInt32());
            point.Y = HIWORD(m.LParam.ToInt32());
            RECT rcWin = new RECT();
            PInvoke.GetWindowRect((HWND)Handle, out rcWin);
            int width = rcWin.right - rcWin.left;
            point.X -= rcWin.left;
            point.Y -= rcWin.top;
            RECT rcClose = new RECT(rcWin.Width - 42, 10, rcWin.Width - 10, 28);
            int rcCloseWidth = rcClose.Width;
            Rectangle closeBtnRect = rcClose;
            rcClose.left -= rcCloseWidth + 2;
            rcClose.right -= rcCloseWidth + 2;
            Rectangle restoreRect = rcClose;
            rcClose.left -= rcCloseWidth + 2;
            rcClose.right -= rcCloseWidth + 2;
            Rectangle minRect = rcClose;

            if (IsMdiChild && WindowState == FormWindowState.Minimized)
            {
                base.WndProc(ref m);
            }

            else
            {
                if (closeBtnRect.Contains(point))
                {
                    m.Result = 20; // HTCLOSE;
                }

                else if (restoreRect.Contains(point))
                {
                    m.Result = 9; // HTMAXBUTTON;
                }

                else if (minRect.Contains(point))
                {
                    m.Result = 8; //HTMINBUTTON;
                }
                else
                    base.WndProc(ref m);
            }
        }

        private static int LOWORD(int i)
        {
            return (short)(i & 0xFFFF);
        }

        private static int HIWORD(int i)
        {
            return (short)(i >> 16);
        }
    }
merriemcgaw commented 2 weeks ago

all custom non-client area painting not working Correctly in dark mode at runtime !. (I will open an issue if required).

Please do open a separate issue for that. Thanks!