2

[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.

Community
  • 1
  • 1
user2063351
  • 503
  • 2
  • 13
  • 31

4 Answers4

2

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.
Jason Williams
  • 56,972
  • 11
  • 108
  • 137
  • I appreciate you taking the time to explain things...however, since I'm still learning, a lot of what you wrote doesn't make any sense yet (although I will look up some things). I do want to point out that I indicated in my post that I'm not only using the DoubleBuffered mode you alluded to, but also the SetStyles(ControlStyles...) to no avail. – user2063351 May 09 '13 at 19:06
  • I do see that I'm painting two things in the same place at the same time, but I'm not experienced enough yet to implement your suggestions to address the pixel-painting conflict between the rectangle I'm drawing that rounds the corners of my form and the rectangle I'm drawing the serves as the SizeGrip. – user2063351 May 09 '13 at 19:12
  • You're doing quite advanced stuff for "not experienced enough" :-) Try overriding OnPaintBackground (to do nothing, to avoid a double paint), and optimising your code so that you don't do anything in your OnPaint that you don't need to - e.g. create your region/rects in OnSizeChanged rather than on every paint request. Note also that changing the window region may cause a redraw to occur which in itself could cause flicker. – Jason Williams May 09 '13 at 20:34
  • Well, I've got a little basic C++ console experince, so the fundamental code isn't that different and I was able to piece a lot of this together from examples I collected from the Web. What would I put in the override for OnPaintBackground...just the default `base.OnPaintBackground(e)` and that's it? – user2063351 May 10 '13 at 15:45
  • No, you want to stop it calling the base.OnPaintPackground - either paint nothing (if your painting will fill the entire region) or paint a colour that will minimise flicker. Usually it's best to do nothing and then fill the background in OnPaint so that the time between clearing the window and painting its content is minimised. – Jason Williams May 10 '13 at 19:34
1

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.

DirectlyX
  • 31
  • 2
0

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);
    }
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • This maybe made it slightly better, but it is still flickering very badly. Plus, your code seems to have overridden my forms start position (CenterScreen) for some reason. – user2063351 May 09 '13 at 18:00
  • Do you have an image as the background of the form?...or possibly some more complicated painting going on in the Paint() event? – Idle_Mind May 09 '13 at 18:25
  • No, everything in the form is created in the code (and all my code, except the enumeration class, is listed in my post). It's just a rectanglular region that overlays the borderless form to create the rounded corners (as I understand it). And, of course, there's the additional rectangle for the custom SizeGrip. I think that the rectangle that creates the rounded corners is what is causing the problem because it doesn't seem to flicker (or flicker nearly as bad) when I remove that CreateRoundRectRgn code altogether. But the rounded corners are an important visual implementation for me. – user2063351 May 09 '13 at 19:03
  • I'm running Win 8 so I'm not sure if the problem is with XP. I get almost no flicker at all on my system. – Idle_Mind May 09 '13 at 19:55
0

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

Stefan27
  • 845
  • 8
  • 19