Click here to Skip to main content
15,867,771 members
Articles / Programming Languages / C#

Form With End Resize

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
25 Aug 2020CPOL3 min read 7K   205   6   2
OnResizeEnd does not supply all triggers you need, this form fixes that.
An inherited class of Form is made which supplies child and instance owner with options to listen to the several new events.

Introduction

In need of some bitmap drawing I had to do and the need for speed of it, I wanted to be able to only resize the section when a resize action had been finished and not during it. .NET on its WinForms Form does supply the function OnResizeEnd, but after testing a bit too soon, it turned out that it didn't deliver. There were cases of resizing ends it didn't trigger on. It didn't trigger on change of windowstate (normal/minimized/maximized), nor did it trigger when a Form was vertically maximized or restored from it back to normal. (Vertical maximisation can be reached by double clicking on the titlebar when having the vertical double arrow.)

Background

Analysing the web, it turned out that windowstate could be detected during the OnClientSizeChanged event. But this wasn't sufficient, vertical maximisation still had to be detected. The web supplied no answers. So it was time for good old Spy++. It turned out that WM_WINDOWPOSCHANGED fired during it. Pretty soon, it turned out there were several flags used that were not described by Microsoft, after some digging for these values, several were found. Though one flag was still a mystery, a flag which is present when we go from normal state towards vertically maximized state. Because of this, this flag has been named MaybeMaximizeVertically. To indicate it's been suspected of being MaximizeVertically flag, but this is still just an educated guess.

After adding OnClientSizeChanged and listening to WM_WINDOWPOSCHANGED, finally it seems all end of resize events are registered.

To not have to reimplement this over and over, an inherited class of Form is made which implements above and supplies child and instance owner with options to listen to the several new events.

The source is written in Visual Studio 2019.

SetWindowPostitionFlags

The enum holds the representations of the values (found or learned the hardway). It also shows the MaybeMaximizeVertically which is key to detecting the vertical maximisation.

C#
[Flags]
private enum SetWindowPostitionFlags : int
{
    None = 0,
    NoSize = 0x1,
    NoMove = 0x2,
    NoZOrder = 0x4,
    NoRedraw = 0x8,
    NoActivate = 0x10,
    FrameChanged = 0x20,
    ShowWindow = 0x40,
    HideWindow = 0x80,
    NoCopyBits = 0x100,
    NoOwnerZOrder = 0x200,
    NoSendChanging = 0x400,
    NoClientSize = 0x800,
    NoClientMove = 0x1000,
    DeferErase = 0x2000,
    AsyncWindowPos = 0x4000,
    StateChanged = 0x8000,
    DrawFrame = FrameChanged,
    NoReposition = NoOwnerZOrder,
    MaybeMaximizeVertically = 0x100000
}

Three Different Flag Sets for the Two State Movements

After watching close with spy++, it turned out there were three different flags values repeating for vertical maximisation and restore from it to normal. They are placed inside two IReadOnlyLists to be able to easily match the value of flags in WINDOWPOS with it. If the list contains the specific flags value, so far as learned, one of the two transformations has occured.

C#
private static readonly IReadOnlyList<SetWindowPostitionFlags> 
                        NormalToVerticalMax = new SetWindowPostitionFlags[]
{
    SetWindowPostitionFlags.MaybeMaximizeVertically |
    SetWindowPostitionFlags.NoOwnerZOrder,

    SetWindowPostitionFlags.MaybeMaximizeVertically |
    SetWindowPostitionFlags.NoOwnerZOrder |
    SetWindowPostitionFlags.NoZOrder,

    SetWindowPostitionFlags.MaybeMaximizeVertically |
    SetWindowPostitionFlags.NoZOrder |
    SetWindowPostitionFlags.NoReposition |
    SetWindowPostitionFlags.NoMove |
    SetWindowPostitionFlags.NoClientMove
};

private static readonly IReadOnlyList<SetWindowPostitionFlags> ToNormal = 
                                                   new SetWindowPostitionFlags[]
{
    SetWindowPostitionFlags.DrawFrame,

    SetWindowPostitionFlags.DrawFrame |
    SetWindowPostitionFlags.NoZOrder,

    SetWindowPostitionFlags.DrawFrame |
    SetWindowPostitionFlags.NoZOrder |
    SetWindowPostitionFlags.NoMove |
    SetWindowPostitionFlags.NoClientMove
};

Overriding the WndProc

This part is mostly written for the ones who have not done this before.

To be able to get the information supplied by WM_WINDOWPOSCHANGED, we need to override the WndProc of the Form. With overriding the WndProc, access is obtained to the messages sent to the window.

C#
protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);

    //do this after .NET stuff we only want to learn unsupplied information 
    //not influence .NET workings
    switch (m.Msg)
    {
        case WM_WINDOWPOSCHANGED:
            if (m.LParam != IntPtr.Zero)
            {
                WINDOWPOS? changes = null;

                try
                {
                    changes = Marshal.PtrToStructure<WINDOWPOS>(m.LParam);
                }
                catch { }

                if (changes.HasValue)
                {
                    SetWindowPostitionFlags flags = changes.Value.flags;

                    if (NormalToVerticalMax.Contains(flags))
                    {
                        IsResized();
                        VerticallyMaximized(EventArgs.Empty);
                        _OnVerticallyMaximized?.Invoke(this, EventArgs.Empty);
                    }
                    else if (ToNormal.Contains(flags))
                    {
                        IsResized();
                        NormalFrameRestored(EventArgs.Empty);
                        _OnNormalFrameRestored?.Invoke(this, EventArgs.Empty);
                    }
                }
            }
            break;
    }
}

Because there is no need to alter any of the windows normal behaviour, we let the Form handle the messages as normal. If the message (m.Msg) turns out to be WM_WINDOWPOSCHANGED, m.LParam is checked if it is atleast unequal to zero. Normally, it contains a pointer to a WINDOWPOS structure. The pointer is marshalled within a try catch block to prevent crashing on retrieving wrongly send messages. If a special transformation is detected, depending events are fired.

Using the Code

The FinalResizeForm can be used as a normal Form. It adds four events supplied through overriding virtual functions and external events. The standard layout is reused for these functions, though e is always equal to EventArgs.Empty.

Inheritance:

C#
public class TestForm : FinalResizeForm 
{
    protected override void WindowStateChanged(EventArgs e)
    {
    }

    protected override void NormalFrameRestored(EventArgs e)
    {
    }

    protected override void VerticallyMaximized(EventArgs e)
    {
    }

    protected override void FinalResize(EventArgs e)
    {
    }
}

External use:

C#
public class UserOfForm
{
    FinalResizeForm test = new FinalResizeForm();

    public UserOfForm()
    {
        test.OnFinalResize += Test_OnFinalResize;
        test.OnNormalFrameRestored += Test_OnNormalFrameRestored;
        test.OnVerticallyMaximized += Test_OnVerticallyMaximized;
        test.OnWindowStateChanged += Test_OnWindowStateChanged;
    }

    private void Test_OnWindowStateChanged(object sender, EventArgs e)
    {
    }

    private void Test_OnVerticallyMaximized(object sender, EventArgs e)
    {
    }

    private void Test_OnNormalFrameRestored(object sender, EventArgs e)
    {
    }

    private void Test_OnFinalResize(object sender, EventArgs e)
    {
    }
}

Points of Interest

What I learned from making this FinalResizeForm is that Microsoft is still into undocumented information.

History

  • v1.0 Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseMy vote of 5 Pin
Stylianos Polychroniadis28-Aug-20 5:45
Stylianos Polychroniadis28-Aug-20 5:45 
GeneralMy vote of 5 Pin
LightTempler26-Aug-20 10:10
LightTempler26-Aug-20 10:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.