Resolved Flickering while resizing app window

alikim

Member
Joined
Oct 30, 2023
Messages
15
Programming Experience
Beginner
I'm trying to resize window (only width so far) to keep constant aspect ratio of the client area. When I resize without Form1_ResizeEnd handler, I see the form resizing properly while dragging the window border, but when it's released the height is reset to the original value set in InitializeComponent.

I added Form1_ResizeEnd handler, now the form size is set correctly at the end, but still, while dragging, I see the window is repainted twice, flickering between the original size (set before the dragging) and the final size.

What am I doing wrong? How to resize without flickering effect?

C#:
namespace WinFormsApp1;

public partial class Form1 : Form

{

    int cWidth, cHeight;

    readonly double aspect;

    public Form1()

    {

        InitializeComponent();

        cWidth = this.ClientSize.Width;

        cHeight = this.ClientSize.Height;

        aspect = (double)cWidth / cHeight;

    }

    private void Form1_Load(object sender, EventArgs e)

    {

        tbox.Text = "loaded";

    }

    private void Form1_Resize(object sender, EventArgs e)

    {

        var width = this.ClientSize.Width;

        var height = this.ClientSize.Height;

        var newAspect = (double)width / height;

        if (aspect < 1)

            if (width != cWidth)

            {

                cHeight = (int)(width / aspect);

                this.ClientSize = new Size(width, cHeight);

            }

        

        cWidth = width;

    }

    private void Form1_ResizeEnd(object sender, EventArgs e)

    {

        if (this.ClientSize.Width != cWidth || this.ClientSize.Height != cHeight)

            this.ClientSize = new Size(cWidth, cHeight);

    }

}


To clarify more:

Without Form1_ResizeEnd, while changing the window size (dragging), the window is repainted quickly between (the new width x calculated height) and (the new width x the original height). When the dragging is over, the window settles on (the new width x the original height), so the new height is not ultimately applied.

If you comment the body of Form1_ResizeEnd, you should see that you can't change the height of the window. Applying Form1_ResizeEnd helps the new height to settle but doesn't solve the problem of repainting the window with two different heights while dragging.
 
I suggest intercepting the WM_SIZING message (Winuser.h) - Win32 apps
Some definitions for this use:
C#:
private const int WM_SIZING = 0x214;

private enum WMSZ
{
    LEFT = 1,
    RIGHT = 2,
    TOP = 3,
    TOPLEFT = 4,
    TOPRIGHT = 5,
    BOTTOM = 6,
    BOTTOMLEFT = 7,
    BOTTOMRIGHT = 8
}       

private struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
Then you can for example set the fixed ratio on load:
C#:
private void Form1_Load(object sender, EventArgs e)
{
   ratio = (double)Width / Height;
}

private double ratio;
To handle the message you need to override WndProc, do the calculation and override the RECT parameter to maintain aspect ratio:
C#:
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_SIZING)
    {
        var rc = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT))!;
        var rc2 = Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
        
        switch ((WMSZ)m.WParam.ToInt32())
        {
            case WMSZ.LEFT:
            case WMSZ.RIGHT:
            case WMSZ.TOPLEFT:
            case WMSZ.TOPRIGHT:
            case WMSZ.BOTTOMLEFT:
            case WMSZ.BOTTOMRIGHT:
                // width has changed, adjust height (bottom)
                rc.Bottom = rc.Top + (int)(rc2.Width / ratio);
                break;
            case WMSZ.TOP:
            case WMSZ.BOTTOM:
                // height has changed, adjust width (right)
                rc.Right = rc.Left + (int)(rc2.Height * ratio);
                break;
        }               
        Marshal.StructureToPtr(rc, m.LParam, true);       
    }
    base.WndProc(ref m);           
}
 
Thank you!
A quick question - if I need a constant client area aspect, not the whole window, do I need to memorize thickness of horizontal and vertical window borders (and top menu), assuming they do not change and only depend on the window theme; then figure out client area size, fix aspect and recalculate back to the whole window?
 
Yes.

Add private field private Size nonClient; and initialize it along with ratio nonClient = Size - ClientSize;
In WndProc remove it from rc2 before calculation:
C#:
rc2.Width -= nonClient.Width;
rc2.Height -= nonClient.Height;
Each calculation now is basically the same, but you add back the nonClient.Width/Height afterwards:
C#:
rc.Bottom = rc.Top + (int)(rc2.Width / ratio) + nonClient.Height;
C#:
rc.Right = rc.Left + (int)(rc2.Height * ratio) + nonClient.Width;
 
I'm not getting any flickering with the following code when I release the button:
C#:
using System;
using System.Windows.Forms;
using System.Drawing;

namespace WinForms
{
    class MainForm : Form
    {
        double _aspectRatio;
        Size _originalSize;

        MainForm()
        {
            Size = new Size(800, 600);
            ResizeRedraw = false;

            ResizeBegin += (o, e) =>
            {
                _aspectRatio = Width / (double)Height;
                _originalSize = Size;
            };

            Resize += (o, e) =>
            {
                var delta = Size - _originalSize;
                if (Math.Abs(delta.Width) >= Math.Abs(delta.Height))
                    Size = new Size(Size.Width, (int)(Size.Width / _aspectRatio));
                else
                    Size = new Size((int)(Size.Height * _aspectRatio), Size.Height);
            };
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}
 
when I release the button:

Not sure what you mean by that, I'm resizing the main app window by dragging its borders. Your code makes the window jump around in size like crazy while dragging, much worse that my original code.
The reason for that is actually because when you call new Size() inside Resize(), it immediately calls itself, so if you look at the stack you will see at least two recursive calls, which makes resizing a nightmare.
ResizeRedraw = false; does do anything about this problem.
Unfortunately, good ol WndProc is the simplest solution.
 
I must have misread your original post. I thought that you didn't mind the flickering while dragging, but was annoyed by the flickering when the mouse is finally released and the form painting twice, as well, as needing the resize end event handler to force the final size.

Yes, overriding the default WinForms handing of WM_SIZING is the correct all around solution. This is because default WinForms handler forces the paints.
 

Latest posts

Back
Top Bottom