Introduction
Flicker free animated drawing had been a very hot issue with Win32 and MFC.
Many excellent articles are available to explain the techniques to get a flicker
free animated effect. As many of the reader know that most popular technique has
been to use off-screen DC (device context) to do the entire complex drawing and
then copying this off-screen DC to the screen DC directly. This technique is
also known as double buffering.
C# is projected by Microsoft as the future for C++ programmers. So like many
other C++ programmers, I used some of my spare time to play around with C# to
have a feel of it. A few days back I was trying to write an application in C# to
simulate an analog clock. After establishing a base frame work and seeing my
clock work (with flicker of course) I was excited to use the old double
buffering technique to let my clock animate smoothly. But my first dilemma was
when I could not find functions like CreateCompatibleDC,
CreateCompatibleBitmap and SelectObject etc. So I
started to search around MSDN and studied the Graphics class. After
some research I was able to find two ways to produce smooth animated effects and
these techniques I will be explaining below.
Double-buffering technique the old way
I was glad to know that there was a way in C# to use the old Win32 techniques
for smooth animation. Although one cannot find direct implementation for
functions like CreateCompatibleDC,
CreateCompatibleBitmap and SelectObject, but there is
an indirect way to use these functions for your GDI+ device context. The idea is
to let C# know that you will be using some functions from an unmanaged dll. You
can import a function that is exported by a dll using the DllImport
attribute. The detailed documentation for DllImport can be found in
.NET documentation. In short with the help of DllImport we can tell
the compiler that we will be using the specified function from the specified
dll. For example,
[DllImport("msvcrt.dll")]
public static extern int puts(string c);
The above declaration will declare the function named puts with
static and extern attributes and the actual implementation of this function will
be imported from msvcrt.dll. I used DllImport to import all
the necessary functions from gdi32.dll. To keep things managed, I
declared a separate class to import all such functions. The code below shows the
actual implementation for this class
public class Win32Support
{
public enum Bool
{
False = 0,
True
};
public enum TernaryRasterOperations
{
SRCCOPY = 0x00CC0020,
SRCPAINT = 0x00EE0086,
SRCAND = 0x008800C6,
SRCINVERT = 0x00660046,
SRCERASE = 0x00440328,
NOTSRCCOPY = 0x00330008,
NOTSRCERASE = 0x001100A6,
MERGECOPY = 0x00C000CA,
MERGEPAINT = 0x00BB0226,
PATCOPY = 0x00F00021,
PATPAINT = 0x00FB0A09,
PATINVERT = 0x005A0049,
DSTINVERT = 0x00550009,
BLACKNESS = 0x00000042,
WHITENESS = 0x00FF0062,
};
[DllImport("gdi32.dll", ExactSpelling=true,
SetLastError=true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling=true,
SetLastError=true)]
public static extern Bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling=true)]
public static extern IntPtr SelectObject(IntPtr hDC,
IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling=true,
SetLastError=true)]
public static extern Bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling=true,
SetLastError=true)]
public static extern IntPtr CreateCompatibleBitmap(
IntPtr hObject, int width, int height);
[DllImport("gdi32.dll", ExactSpelling=true,
SetLastError=true)]
public static extern Bool BitBlt(
IntPtr hObject,
int nXDest, int nYDest,
int nWidth, int nHeight,
IntPtr hObjSource, int nXSrc, int nYSrc,
TernaryRasterOperations dwRop);
}
Now I can use this Win32Support class to use my old techniques.
The code snippet below shows how to create a memory DC with help of
Win32Support from within your Form class.
Graphics memDC;
Bitmap memBmp;
memBmp = new Bitmap(this.Width, this.Height);
Graphics clientDC = this.CreateGraphics();
IntPtr hdc = clientDC.GetHdc();
IntPtr memdc = Win32Support.CreateCompatibleDC(hdc);
Win32Support.SelectObject(memdc, memBmp.GetHbitmap());
memDC = Graphics.FromHdc(memdc);
clientDC.ReleaseHdc(hdc);
One important point to note here is that every call to the function
Graphics.GetHdc on some DC must be paired with the call to
Graphics.ReleaseHdc. this is what MSDN has to say about this issue
"Calls to the GetHdc and ReleaseHdc methods must appear in pairs. During the
scope of a GetHdc- ReleaseHdc method pair, you usually make only calls to GDI
functions. Calls in that scope made to GDI+ methods of the Graphics object that
produced the hdc parameter fail with an ObjectBusy error. Also, GDI+ ignores any
state changes made to the Graphics object of the hdc parameter in subsequent
operations."
Once you have memDC, you can use it for off screen drawing and
then we will use BitBlt to copy the contents of memDC to actual
screen DC.
Graphics clientDC = this.CreateGraphics();
IntPtr hdc = clientDC.GetHdc();
IntPtr hMemdc = memDC.GetHdc();
Win32Support.BitBlt(hdc, 0, 0, this.Width, this.Height,
hMemdc, 0, 0, Win32Support.TernaryRasterOperations.SRCCOPY);
clientDC.ReleaseHdc(hdc);
memDC.ReleaseHdc(hMemdc);
This will have dramatic effect on the animation that you have been trying to
produce.
The sample application uses this technique when you click the "Offscreen
Drawing Using BitBlt" radio button.
Double-buffering technique the .NET way
Luckily we can achieve the same goal without any direct help from Win32 API.
Image rendering in .NET is very simple and efficient compared to MFC. There are
two functions in Graphics class to render your Image
object on screen, these are DrawImage and
DrawImageUnscaled. What makes these functions important is the fact
that .NET always uses BitBlt in background to render the image on
DC. So if we are able to do our off-screen drawing in an Image object, we can
use these functions to render this object directly to DC and have the smooth
animated effects.
The technique is same, but the way to implement it differs a little bit. In
the code fragment below, we are using a Bitmap object to do our
off-screen drawing. In order to draw on some Image object, it must
be attached to a Graphics object. We can create a new
Graphics object from an Image object using the static
member function, of Graphics, named FromImage. Once we
get a Graphics object from some Image object, any
drawing done on this Graphics object will actually be changing the
Image.
Bitmap offScreenBmp;
Graphics offScreenDC;
offScreenBmp = new Bitmap(this.Width, this.Height);
offScreenDC = Graphics.FromImage(offScreenBmp);
Provided that we have created offScreenDC as per shown in
example above, we can implement the double buffering technique as per the code
fragment below.
Graphics clientDC = this.CreateGraphics();
clientDC.DrawImage(offScreenBmp, 0, 0);
I will recommend this technique as it does not involve any call to
unmanaged code and is simpler in nature.
The sample application uses this technique when you click the "Offscreen
Drawing Using Image" radio button.
| You must Sign In to use this message board. |
|
|
 |
|
 |
Just recently I had to fix someone else's code that had a flickering bitmap in C#. There are a couple standard things to do to get rid of the flicker (and make things go faster).
Do not remake the memory Bitmap (or Image) every time you change it. It only needs to be remade if the size is changing and you want the scaling to match perfect and don't want any anti-aliasing. Make the memory Bitmap once and put it into a member variable or property. Do the same thing with the the Graphics. Do not do things like put in a "new Font ( "Arial", 10 )" every time you need to DrawString. Make that font once and put it into a property.
And beware of the Paint event. It may have to be used to get your memory bitmap on the screen initially. But after that, make your own function or method to invoke calling the DrawImage with that Graphics property. The Paint event seems to have some nasty overhead of creating the "PaintEventArgs e" repeatedly. And that may be where your flicker is coming from. Or at least it was in my case and then everything started working just like Rehan's article shows.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
hi author,
will this work in Compact framework 2.0? (C#) I am trying to draw a clock on compact framework and it flicks every time I draw the second hand.
Please help. Thank you
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
How can I make Xor operations between two bitmaps using bitblt in c# and show the result on picture box ;
Nour Laban
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
My code flickers all the time, when implementing it the new way, according the text in the article. Can I still call this.Invalidate() as in the old days, or what is wrong?
And something is wrong with the downloaded source code. Radio button 2 and 3 seem to call the same function! In the 'source code' it looks like things are drawn directly without using 'Invalidate()' and catching the Paint event. What is going on? I am confused.
|
| Sign In·View Thread·PermaLink | 2.75/5 |
|
|
|
 |
 | ...  alucardx | 2:12 19 Jun '04 |
|
 |
Oh boy, you seem to mess up .NET with GDI+.
The title should be "Flicker free drawing using GDI+ in C#"
Graphics and Image classes are clasified under GDI+ and .NET uses this new set of API.
GDI+ is also available in C++, not only in .NET.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I'm trying to use some of concepts in your Flicker Free code examples to perform a double buffered falling raster display. I'm using two Bitmap objects and copying between them while shifting up a row and then adding new data on the bottom. Then DrawImage from the updated Bitmap and do the same on the other Bitmap, over and over.
Its not really working because the only way I see to copy data between Bitmaps is to use the Clone method which causes the system to create a new one every time and I run out of memory quick.
Any ideas on this?
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
GDI+ is not SOLELY a .net stuff, you can use GDI+ directly in native C/C++ code, without the present of .net framework.
See: http://www.codeproject.com/useritems/GDI__and_MFC.asp
If you cannot manage your own code, don't use C/C++
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Is the "Simple Drawing" option in the sample app supposed to cause the lines to flicker? I didn't see any difference at all in any of the options. They were all flicker free.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
It would be more accurate to create the backbuffer as compatible with the front buffer. This makes the final BLT much faster.
there is an overload to the bitmap constructor which takes a graphics object which specifies the format for the buffer.
Bitmap backBuffer; using(Graphics displayG = this.CreateGraphics() ) { backBuffer = new Bitmap(Width, Height, displayG); } Graphics backBufferG = Graphics.FromImage(backBuffer);
now you could draw to the backBuffer, and BLT with DrawImageUnscaled. This will give you an actual BLT unlike some other options which will force the image to be converted from one format to another.
|
| Sign In·View Thread·PermaLink | 2.20/5 |
|
|
|
 |
|
 |
Hi,
I'm trying to draw a customer list view with gdi plus. WS_EX_COMPOSITED is a nice window style that would give double buffering, but it screws up gdi plus alpha stuff and i can't use it. i would double buffer myself, but i am in cListControl::DrawItem(DRAWITEMSTRUCT* DrawItemInfo), which means i would have to double buffer each item individually.
any thoughts?
much thanx!
Check out Aephid Photokeeper, the powerful digital photo album solution at www.aephid.com.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
http://codeproject.com/vcpp/gdiplus/gdiplus.asp
<html>Mazy</html>
"The path you tread is narrow and the drop is shear and very high, The ravens all are watching from a vantage point near by, Apprehension creeping like a choo-train uo your spine, Will the tightrope reach the end;will the final cuplet rhyme?"Cymbaline-Pink Floyd
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
 | Ermm  Matthew Adams | 2:34 1 Apr '02 |
|
 |
What about .SetStyle( ControlStyles.DoubleBuffer, true );
This coerces the PaintEvent to give you an offscreen buffer, and handles the blit afterwards for you...
Matthew Adams Development Manager Digital Healthcare Ltd
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
 | Re: Ermm  James T. Johnson | 19:13 1 Apr '02 |
|
 |
It may not be double buffering the .NET way, but you can see how to obtain graphics objects from non .NET forms/controls 
James
Sonork: Hasaki "I left there in the morning with their God tucked underneath my arm their half-assed smiles and the book of rules. So I asked this God a question and by way of firm reply, He said - I'm not the kind you have to wind up on Sundays." "Wind Up" from Aqualung, Jethro Tull 1971
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yes My Friend, You are right, but as I said that I am new to the world of C#, so I did not know about this .SetStyle. Nevertheless, due to this article now we know that there is yet another way to avoid flicker. I tried this option and works beautifully  In order to use ControlStyles.DoubleBuffer efficiently, one should also set ControlStyles.AllPaintingInWmPaint and ControlStyles.UserPaint to true, this will redirect all sort of drawing events to your OnPaint event handler. Specially it will bypass the WM_ERASEBKGND messages. So in your Form's OnLoad event handler, write this code
this.SetStyle(ControlStyles.DoubleBuffer, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint , true); this.SetStyle(ControlStyles.UserPaint, true);
And now what ever drawing you do in OnPaint will use double buffering and there will be no flicker.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
the problem with these flags is, i can only set them at the form, not on a control itself (protected inheritance). but when i set the whole form to doublebuffered and userpainted, the toolbar looks weired. (maybe because it doesn't receive an erase background msg...)
so i will try the image-way now for my specific control (a panel in der middle of my app)
:wq
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
ok, it works, but it was very complicated. i used the image-drawing trick described above, but the background was still erased, so flickering was still there.
now i made a new panel-class (baseclass System.Windows.Forms.Panel) and just set the three flags in the constructor, nothing more.
when i insert that new control into my app (refer to this tutorial on codeproject) it is indeed painted without erasing it's background, and thus flickering is gone.
phu. long way and one more dll in the project
...isn't there a way to capture certain window-messages for a specific control?
:wq
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Try and compile this: (Just change the .bmp to one that exists on your system)
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Reflection; using System.Runtime.CompilerServices; using System.Drawing.Drawing2D;
namespace ScrollablePictureBox { /// /// Summary description for Form1. /// public class Form1 : Form { private PictureBoxControl.UserPictureBox userPictureBox1; /// /// Required designer variable. /// private Component components = null;
public Form1() { // // Required for Windows Form Designer support // InitializeComponent();
// // TODO: Add any constructor code after InitializeComponent call // }
/// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { //System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1)); this.userPictureBox1 = new PictureBoxControl.UserPictureBox(); this.SuspendLayout(); // // userPictureBox1 // this.userPictureBox1.Image = ((System.Drawing.Bitmap)(Image.FromFile("Setup.bmp"))); this.userPictureBox1.Location = new System.Drawing.Point(48, 48); this.userPictureBox1.Name = "userPictureBox1"; this.userPictureBox1.OffsetX = 0; this.userPictureBox1.OffsetY = 0; this.userPictureBox1.Size = new System.Drawing.Size(312, 248); this.userPictureBox1.TabIndex = 0; // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(400, 317); this.Controls.AddRange(new System.Windows.Forms.Control[] {this.userPictureBox1}); this.Name = "Form1"; this.Text = "Scrollable Picture Box"; this.ResumeLayout(false);
} //End Form1 #endregion
/// /// The main entry point for the application. /// [STAThread] static void Main() { Application.Run(new Form1()); } } }
namespace PictureBoxControl { /// /// Summary description for UserPictureBox. /// public class UserPictureBox : UserControl { /// /// Required designer variable. /// private System.ComponentModel.Container components = null;
public UserPictureBox() { // This call is required by the Windows.Forms Form Designer. InitializeComponent();
// TODO: Add any initialization after the InitForm call
}
/// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.hScrollBar1 = new System.Windows.Forms.HScrollBar(); this.vScrollBar1 = new System.Windows.Forms.VScrollBar(); this.SuspendLayout(); // // hScrollBar1 // this.hScrollBar1.Location = new System.Drawing.Point(0, 128); this.hScrollBar1.Name = "hScrollBar1"; this.hScrollBar1.Size = new System.Drawing.Size(136, 16); this.hScrollBar1.TabIndex = 0; this.hScrollBar1.Scroll += new System.Windows.Forms.ScrollEventHandler(this.hScrollBar1_Scroll); // // vScrollBar1 // this.vScrollBar1.Location = new System.Drawing.Point(136, 8); this.vScrollBar1.Name = "vScrollBar1"; this.vScrollBar1.Size = new System.Drawing.Size(16, 112); this.vScrollBar1.TabIndex = 1; this.vScrollBar1.Scroll += new System.Windows.Forms.ScrollEventHandler(this.vScrollBar1_Scroll); // // UserPictureBox // this.Controls.AddRange(new System.Windows.Forms.Control[] { this.vScrollBar1, this.hScrollBar1}); this.Name = "UserPictureBox"; this.SizeChanged += new System.EventHandler(this.UserPictureBox_SizeChanged); this.Paint += new System.Windows.Forms.PaintEventHandler(this.UserPictureBox_Paint); this.SetStyle( ControlStyles.DoubleBuffer, true ); this.SetStyle( ControlStyles.UserPaint, true ); this.SetStyle( ControlStyles.AllPaintingInWmPaint, true ); this.ResumeLayout(false);
} #endregion
private System.Windows.Forms.HScrollBar hScrollBar1; private System.Windows.Forms.VScrollBar vScrollBar1;
private Image TheImage = null;
private void UserPictureBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Graphics g = e.Graphics; g.FillRectangle(Brushes.White,this.ClientRectangle); if (TheImage != null) { g.DrawImageUnscaled(TheImage, -OffsetX, -OffsetY, TheImage.Width, TheImage.Height); g.FillRectangle(Brushes.Gray, ClientRectangle.Width - vScrollBar1.Width, ClientRectangle.Height - hScrollBar1.Height, vScrollBar1.Width, hScrollBar1.Height); } } public Image Image { get { return TheImage; } set { TheImage = value; SizeScrollBars(); } }
// private string TheBitmap = "";
private int iOffsetX = 0; public int OffsetX { get { return iOffsetX; } set { iOffsetX = value; Invalidate(); } }
private int iOffsetY = 0;
private void hScrollBar1_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) { int nVal = e.NewValue ; OffsetX = nVal;
}
private void vScrollBar1_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) { int nVal = e.NewValue ; OffsetY = nVal; }
private void SizeScrollBars() { hScrollBar1.Minimum = 0; vScrollBar1.Minimum = 0; hScrollBar1.SetBounds(0, ClientRectangle.Height - hScrollBar1.Height, ClientRectangle.Width - vScrollBar1.Width, hScrollBar1.Height); vScrollBar1.SetBounds(ClientRectangle.Right - vScrollBar1.Width, 0, vScrollBar1.Width, ClientRectangle.Height - hScrollBar1.Height);
if (TheImage != null) { hScrollBar1.Maximum = TheImage.Width + vScrollBar1.Width*2+ - ClientRectangle.Width; vScrollBar1.Maximum = TheImage.Height + hScrollBar1.Height*2 - ClientRectangle.Height; } else { hScrollBar1.Maximum = 10; vScrollBar1.Maximum = 10; }
Invalidate(); }
private void UserPictureBox_SizeChanged(object sender, System.EventArgs e) { SizeScrollBars(); } public int OffsetY { get { return iOffsetY; } set { iOffsetY = value; Invalidate(); } }
}
}
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
FYI due to a bug in the .NET Framework for Win9x/Me, using ControlStyles.DoubleBuffer causes the brushes you draw with to be drawn in a color 8 higher than you specify (if Red part is 192 it is drawn in 200) in the OnPaint event. If you create a Graphics object FromHandle then the brushes are drawn in the correct color. Strange bug.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
This code is useful for the .NET Compact Framework for mobile devices. As of now, there is no 'ControlStyles' class or 'SetStyle' method in the .Net Compact Framework.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|