Reputation: 553
[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
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
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
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:
To fix these problems, you need a combination of things (the more the better)
Upvotes: 2
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