user2063351
user2063351

Reputation: 553

How to prevent a borderless Windows Form from flickering when resizing (C#)?

[C# .NET 4.0]

I'm learning C# and I'm trying to build a Windows Form using C# that has FormBorderStyle = FormBorderStyle.None and can be moved/resized using the Windows API. As an example, I'm using the rounded corner or custom (moveable/resizable) border designs used for Google Chrome and Norton 360 as the basis for my form.

I've made a lot of progress so far and gotten everything to work, except that when I resize the form, there is a black/white flicker along the length of the right and bottom borders when you resize the form quickly.

I've tried adding this.DoubleBuffer = true in the constructor and have also tried this.SetStyles(ControlStyles.AllPaintInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);.

Because I'm a sucker for the graphics side of things, I like having control over the full design of the form, so I can see this being something that would forever bother me...so if someone can help me resolve this so the flicker doesn't occur anymore, it would be incredibly useful toward my learning process.

I should also mention that I'm using Windows XP, so I'm not sure that this post will help me since it seems to be focused on Vista/7 (with DWM)...not that I'm advanced enough yet to understand everything in that post.

The two portions of the code that work with the API are below. I have a public enumeration for the WM_NCHITTEST for the Windows API...you can see the values in this link.

OnPaint Override Method:

protected override void OnPaint(PaintEventArgs e)
{
    System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
        this.ClientSize.Width, this.ClientSize.Height, 15, 15);

    SetWindowRgn(this.Handle, ptrBorder, true);

    Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
        this.ClientSize.Height - cGrip, cGrip, cGrip);
    ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    rc = new Rectangle(0, 0, this.ClientSize.Width, 32);
    e.Graphics.FillRectangle(Brushes.SlateGray, rc);
}

WndProc Override Method:

protected override void WndProc(ref Message m)
{
    if (m.Msg == (int)HitTest.WM_NCHITTEST)
    {
        // Trap WM_NCHITTEST
        Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
        pos = this.PointToClient(pos);

        if (pos.Y < cCaption)
        {
            m.Result = (IntPtr)HitTest.HTCAPTION;
            return;
        }

        if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cGrip &&
            pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTRIGHT;
            return;
        }

        if (pos.Y >= this.ClientSize.Height - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOM;
            return;
        }

        if (pos.X <= cBorder)
        {
            m.Result = (IntPtr)HitTest.HTLEFT;
            return;
        }
    }

    base.WndProc(ref m);
}

And here's the full code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace PracticeForm
{
    public partial class Form2 : Form
    {
        [DllImport("user32.dll")]
        private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2, int cx, int cy);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        private static extern bool DeleteObject(System.IntPtr hObject);

        private const int cGrip = 20;
        private const int cCaption = 35;
        private const int cBorder = 7;
        private Point mouseOffset;

        public Form2()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.MaximumSize = new Size(670, 440);
            this.DoubleBuffered = true;
            this.SetStyle(ControlStyles.ResizeRedraw |
                          ControlStyles.OptimizedDoubleBuffer |
                          ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
                this.ClientSize.Width, this.ClientSize.Height, 15, 15);

            SetWindowRgn(this.Handle, ptrBorder, true);

            Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
                this.ClientSize.Height - cGrip, cGrip, cGrip);
            ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == (int)HitTest.WM_NCHITTEST)
            {
                // Trap WM_NCHITTEST
                Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
                pos = this.PointToClient(pos);

                if (pos.Y < cCaption)
                {
                    m.Result = (IntPtr)HitTest.HTCAPTION;
                    return;
                }

                if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cGrip &&
                    pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTRIGHT;
                    return;
                }

                if (pos.Y >= this.ClientSize.Height - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOM;
                    return;
                }

                if (pos.X <= cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTLEFT;
                    return;
                }
            }

            base.WndProc(ref m);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void button2_MouseClick(object sender, MouseEventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

        private void panel1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }

        private void label1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void label1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }
    }
}

Thanks for the help.

Upvotes: 2

Views: 3947

Answers (4)

user13028588
user13028588

Reputation:

Try This:

how to stop flickering C# winforms

I have panel as title bar on my borderless form and I had problems with the flickering on the title bar panel and added this code in the form load and the flickering disappeared.

var prop = TitleBar_panel.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
            prop.SetValue(TitleBar_panel, true, null);

TitleBar_panel is the control that was flickering.

EDIT: Now its flickering only if i resize it from the left side of the Form. So its Not 100% Solved by this code

Upvotes: 0

DirectlyX
DirectlyX

Reputation: 31

Although this is a fairly old thread and the OP has likely found a solution to his problem and moved on, I wanted to add a couple of additional points in case they prove to be beneficial to a .NET developer who is working through a similar problem.

Firstly, my hat goes off to you for attempting to tackle this problem on Windows XP. I've been there, spent many hours there and learnt all of the hard lessons as a result. Unfortunately, because Windows XP lacks the DWM that most of us have become accustomed to, there is no easy solution.

Settings the ControlStyles properly is absolutely vital -- I would also include:

SetStyle(ControlStyles.Opaque, True)

Double-buffering the controls you intend to draw is important because the flicker is caused predominantly by the control being redrawn in the middle of the monitor vertical retrace. Just because you called Invalidate(), it doesn't necessarily mean that the control will be redrawn when you want it to -- you are at the mercy of Windows and the OS will do it when it is ready. You can work around this (as I did) on Windows XP by leveraging a function like WaitForVerticalBlank from DirecDraw 7 (lots of support on Windows XP for that API) and also by using GetVerticalBlankStatus and GetScanLine to time your rendering and presentation accordingly.

Upvotes: 1

Jason Williams
Jason Williams

Reputation: 57902

Flickering happens because areas of your display are changing colour rapidly, which in turn happens because you are overdrawing - drawing more than one thing at the same pixel.

This happens because:

  • if your redraw is slow, then stuff that was on the screen (e.g. Window borders) that you are drawing over will be visible for a while. e.g. The user might see two copies of your scroll bar until you have finished wiping away the old one with the contents of your form.
  • windows automatically erases the background of your window for you., usually in white. Any areas of your drawing that aren't white therefore flash white for a moment before you overdraw it with the correct image.
  • if you draw multiple things in the same place, you'll see flickering as you keep changing the colour in that area of the screen

To fix these problems, you need a combination of things (the more the better)

  • disable the erase background, or set the erase colour to the dominant colour in your image
  • optimise your redraw code to make it faster, so the flicker is less prominent
  • optimise your redraw code to eliminate over draw. E.g. to put a border around a rectangular page you could draw the background colour and overdraw it with the page, but this will flicker. Instead, draw the top border as a rectangle, then the bottom left and right, then drew the page in the middle. As nopixels are drawn more than once it won't flicker
  • enable the DoubleBuffered mode on your control. With this, all your drawing actually happens into a bitmap image in memory, and the final image is then copied to the screen, so that each pixel is only displayed once and there is no flicker.

Upvotes: 2

Idle_Mind
Idle_Mind

Reputation: 39132

Only set the Region when the Form actually changes SIZE, not each time in the Paint() event:

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);

        System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
            this.ClientSize.Width, this.ClientSize.Height, 15, 15);

        SetWindowRgn(this.Handle, ptrBorder, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {

        Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
            this.ClientSize.Height - cGrip, cGrip, cGrip);
        ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    }

Upvotes: 0

Related Questions