Much has been written
here and
here about double buffering.
Here you can read how DB is implemented in
Java .
I will tell how double buffering is implemented on C #. Much of what is written here can be read in MSDN, but without implementation details.
')
Manual double buffering (hereinafter referred to as DB)
For manual double buffering, the .NET Framework provides the following 3 classes:
BufferedGraphicsManager
The
BufferedGraphicsManager class is used to access (through the static
Current property) an object of the
BufferedGraphicsContext class associated with the current application domain (
AppDomain ). In fact,
Current returns a
BufferedGraphicsContext class object created in the static constructor. Here is the source code of the
BufferedGraphicsManager class:
public sealed class BufferedGraphicsManager { private static BufferedGraphicsContext bufferedGraphicsContext; public static BufferedGraphicsContext Current { get { return BufferedGraphicsManager.bufferedGraphicsContext; } } static BufferedGraphicsManager() { BufferedGraphicsManager.bufferedGraphicsContext = new BufferedGraphicsContext(); AppDomain.CurrentDomain.ProcessExit += new EventHandler(BufferedGraphicsManager.OnShutdown); AppDomain.CurrentDomain.DomainUnload += new EventHandler(BufferedGraphicsManager.OnShutdown); } private static void OnShutdown(object sender, EventArgs e) { BufferedGraphicsManager.Current.Invalidate(); } }
From this code, you can see that the object of the class
BufferedGraphicsContext stored inside the
BufferedGraphicsManager is destroyed when the current application domain is unloaded.
BufferedGraphicsContext
BufferedGraphicsContext provides the creation (and also destruction) of a new
BufferedGraphics instance based on the
Graphics object, providing for this a single
Allocate method:
public BufferedGraphics Allocate(Graphics targetGraphics, Rectangle targetRectangle) { if (targetRectangle.Width * targetRectangle.Height > this.MaximumBuffer.Width * this.MaximumBuffer.Height) return this.AllocBufferInTempManager(targetGraphics, IntPtr.Zero, targetRectangle); else return this.AllocBuffer(targetGraphics, IntPtr.Zero, targetRectangle); }
The method takes as a parameter a
Graphics object and an area on it for which you want to create a buffer.
If the area of ​​this area does not exceed the area specified by the
MaximumBuffer property, the
AllocBuffer method is
called and the resulting
BufferedGraphics object is returned. The
AllocBuffer method creates inside of itself (using the
CreateBuffer method described below), a new offscreen graph, wraps it in
BufferedGraphics , saves it into a variable of the
buffer object and returns it. This variable is used to further, when destroying an instance of
BufferedGraphicsContext (using the
Dispose method), to destroy the associated instance of
BufferedGraphics .
The creationBuffer method is responsible for creating an offscreen (i.e., stored only in memory without displaying on the screen) of the
Graphics instance . Using the native function
CreateDIBSection, it creates a “hardware-independent bitmap” (DIB), on the basis of which it creates a new
Graphics object, and returns it as a result.
If the area of ​​the transferred area exceeds the
MaximumBuffer area, then the
AllocBufferInTempManager method is
called , the source code of which is given below:
private BufferedGraphics AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle) { // "" var bufferedGraphicsContext= new BufferedGraphicsContext(); // (graphics), ( context) // var bufferedGraphics = bufferedGraphicsContext.AllocBuffer(targetGraphics, targetDC, targetRectangle); // , , bufferedGraphics // // bufferedGraphicsContext: bufferedGraphics.DisposeContext = true; return bufferedGraphics; }
From this code, you can see that inside the
AllocBufferInTempManager method, a new instance of
BufferedGraphicsContext is created , in which the
AllocBuffer method is
called , and the resulting
BufferedGraphics is returned as a result. Moreover, the temporary
BufferedGraphicsContext object created is not immediately destroyed, but only when the
BufferedGraphics created by it is destroyed. For this,
BufferedGraphics keeps a reciprocal link to its creator, and when destroyed, if the
DisposeContext property is
true , it takes it with itself.
BufferedGraphics
The
BufferedGraphics class is very small. Its source code takes a little more than 100 lines. It is a simple wrapper over a
Graphics object, and provides a Render method for copying it onto another
Graphics :
public void Render(Graphics target)
Copying is done by the native BitBlt function.
Automatic db
The simplest way to use db to render a control is to turn on automatic db for the desired control:
control.DoubleBuffered = true;
or
control.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Consider what happens to the control when we enable automatic DB for it. The
DoubleBuffered property, as well as the
SetStyle method, is located in the
Control class. Let's look at the source code of this class. The code for the
DoubleBuffered property looks like this:
protected virtual bool DoubleBuffered { get { return this.GetStyle(ControlStyles.OptimizedDoubleBuffer); } set { if (value != this.DoubleBuffered) { if (value) this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, value); else this.SetStyle(ControlStyles.OptimizedDoubleBuffer, value); } } }
As you can see from this code snippet, the above 2 ways of including dBs do not differ from each other, except that the
ControlStyles.AllPaintingInWmPaint flag is set in the
DoubleBuffered setter. But since this flag is also set in the constructor of the control
1 , then if you did not reset it manually, both of these methods have the same effect.
From the source code of the
Control class, you can also see that the
ControlStyles.AllPaintingInWmPaint flag is checked only inside the private
WmEraseBkgnd method (and is set only in the constructor and setter of the
DoubleBuffered property), which is responsible for processing the
WM_ERASEBKGND 2 system message, and when it is received, it draws the background of the control. Here is its implementation:
private void WmEraseBkgnd(ref Message m) { if (this.GetStyle(ControlStyles.UserPaint) && !this.GetStyle(ControlStyles.AllPaintingInWmPaint)) { ... using (PaintEventArgs e = new PaintEventArgs(wparam, ClientRect)) this.PaintWithErrorHandling(e, (short) 1); } ... }
This shows that if the
AllPaintingInWmPaint flag
is NOT set, then when the window
receives a WM_ERASEBKGND message, it calls the
PaintWithErrorHandling method, with the
layer parameter equal to 1
3 , which in turn causes the redrawing of the background of the control
4 .
It is also worth considering the
ControlStyles.UserPaint flag. This flag indicates that the contents of the control will be rendered by the Framework .NET tools, and not by the system tools. For example, if you specify a background image for your form, and
reset the UserPaint flag, the picture will not be drawn.
The main actions on DB are deployed inside the
WmPaint method. This method is responsible for processing the
WM_PAINT system message, which arrives when a section of the control needs to be redrawn. The
WmPaint method is private and only called from the
WndProc method, provided that the
ControlStyles.UserPaint flag is set:
protected virtual void WndProc(ref Message m) { switch (m.Msg) { ... case WM_PAINT: if (this.GetStyle(ControlStyles.UserPaint)) this.WmPaint(ref m); break; ... } }
If we omit the details not related to the DB, the implementation of the
WmPaint method is as follows:
private void WmPaint(ref Message m) { if (this.DoubleBuffered || this.GetStyle(ControlStyles.AllPaintingInWmPaint) && this.DoubleBufferingEnabled) { IntPtr num; // Graphics Rectangle rectangle; // // num rectangle... if (rectangle.Width > 0 && rectangle.Height > 0) { Rectangle clientRectangle = this.ClientRectangle; using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(num, clientRectangle)) { Graphics graphics = bufferedGraphics.Graphics; graphics.SetClip(rectangle); System.Drawing.Drawing2D.GraphicsState gstate = graphics.Save(); using (PaintEventArgs e = new PaintEventArgs(graphics, rectangle)) { this.PaintWithErrorHandling(e, (short) 1, false); graphics.Restore(gstate); this.PaintWithErrorHandling(e, (short) 2, false); bufferedGraphics.Render(); } } } ... } else { // ... } }
As can be seen from the above code snippet, in order for graphics to be drawn with
db, DoubleBuffered should be
true , or
ControlStyles flag should be set.
AllPaintingInWmPaint (
DoubleBufferingEnabled is not taken into account because it is always
true 5 here ).
Next, using the default BufferedGrpahicsContext, a graphic buffer is created.
It sets a draw rectangle equal to the area for which a redraw is required and the current state is saved.
After that, the OnBackgroundPaint and OnPaint methods are called for it through a call to the PaintWithErrorHandling
3 method, and the resulting image is copied into the graphics control.
As it can be seen, the same DB method is used for automatic buffering as for manual one.
1. A fragment of the source code of the
Control class constructor in which the
ControlStyles flags are set:
internal Control(bool autoInstallSyncContext) { ... this.SetStyle(ControlStyles.UserPaint | ControlStyles.StandardClick | ControlStyles.Selectable | ControlStyles.StandardDoubleClick | ControlStyles.AllPaintingInWmPaint | ControlStyles.UseTextForAccessibility, true); ... }
2. The
WM_ERASEBKGND message arrives when the control is resized. Source code fragment in which the
WM_ERASEBKGND message is
processed :
protected virtual void WndProc(ref Message m) { switch (m.Msg) { ... case WM_ERASEBKGND: this.WmEraseBkgnd(ref m); break; ... } }
3. Implementation of the PaintWithErrorHandling method:
private void PaintWithErrorHandling(PaintEventArgs e, short layer) { ... switch (layer) { case (short) 1: if (!this.GetStyle(ControlStyles.Opaque)) this.OnPaintBackground(e); break; case (short) 2: this.OnPaint(e); break; } ... }
4. The background of the control is not redrawn if the
ControlStyles.Opaque flag is set.
5. Private property
DoubleBufferingEnabled , has the following implementation:
bool DoubleBufferingEnabled { private get { return this.GetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer); } }
Since the WmPaint method is called only if the
ControlStyles.UserPaint flag is set,
DoubleBufferingEnabled will always be
true here . And since it is closed and is not checked anywhere except
WmPaint , its purpose is not clear.