Double Buffering Windows Forms

As much as we would like it not to be the case, graphics can be slow enough to watch as the screen is refreshed. Our eyes, sensitive to movement and particularly to edge detection so that we don't walk off of cliffs or run into trees, often pick up the redraw cycle of computer graphics which, at best is mildly annoying or at worst can cause headaches, eyestrain and in susceptible people, even fits.

Much of this effect is caused by the sequential re-drawing of many graphical elements such as a chart with many lines or a game with many moving items. In a perfect world, the sequential redraw would be completely hidden from the user and the completed graphic presented in its entirety. This is essentially the technique used in double buffering.

Windows Forms provides an automatic method of double buffering that can be used by simply setting a few styles in your form or control. For most applications, this is enough but in certain cases, more control over the process is desirable so manually double buffering a control is also possible.

First, take a look at the standard and built in method of double buffering. This is accomplished by setting the styles:

  • ControlStyles.AllPaintingInWmPaint
  • ControlStyles.UserPaint
  • ControlStyles.DoubleBuffer

When these are all set true, the draw process is modified so that instead of your Paint handler being passed a Graphics for the screen, it is passed a Graphics for an in-memory bitmap. When you draw to this Graphics object, you are drawing on an invisible image. At the end of the draw cycle, this bitmap is copied to the main window automatically and the actual pixels you see are all changed in a fraction of a second instead of one at a time as the draw cycle progresses.

To set up automatic double buffering for a Form, you would use the following line of code in the constructor, after the InitializeComponent method call.

C#

this.SetStyle(

ControlStyles.AllPaintingInWmPaint |

ControlStyles.UserPaint |

ControlStyles.DoubleBuffer,true);

VB

me.SetStyle(

ControlStyles.AllPaintingInWmPaint OR _

ControlStyles.UserPaint OR _

ControlStyles.DoubleBuffer,true)

Manual double buffering can be useful if you don't want the system so make assumptions for you such as whether the background is opaque or transparent or perhaps if you want to create a more complex buffering system. There are a few simple rules that you need to follow to get manual double buffering right.

First, don�t create a new back-buffer every draw cycle. Only create or destroy the bitmap when the window's client size changes. Second, only create a bitmap of the size you need. Clearing pixels takes time and so if there are more pixels than you need, you're just wasting processor cycles. Lastly, use the simplest draw method to copy the bitmap to the screen. DrawImageUnscaled is the way to go here.

Before the big demo, listing 1 shows all the salient points of a manual double buffered application.

private Bitmap _backBuffer;

protected override void OnPaint(PaintEventArgs e)

{

if(_backBuffer==null)

{

_backBuffer=new Bitmap(this.ClientSize.Width,this.ClientSize.Height);

}

Graphics g=Graphics.FromImage(_backBuffer);

//Paint your graphics on g here

g.Dispose();

//Copy the back buffer to the screen

e.Graphics.DrawImageUnscaled(_backBuffer,0,0);

//base.OnPaint (e); //optional but not recommended

}

protected override void OnPaintBackground(PaintEventArgs pevent)

{

//Don't allow the background to paint

}

protected override void OnSizeChanged(EventArgs e)

{

if(_backBuffer!=null)

{

_backBuffer.Dispose();

_backBuffer=null;

}

base.OnSizeChanged (e);

}

VB

Private _backBuffer As Bitmap

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)

If _backBuffer Is Nothing Then

_backBuffer = New Bitmap(Me.ClientSize.Width, Me.ClientSize.Height)

End If

Dim g As Graphics = Graphics.FromImage(_backBuffer)

'Paint on the Graphics object here

g.Dispose()

'Copy the back buffer to the screen

e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0)

End Sub 'OnPaint

'base.OnPaint (e); //optional but not recommended

Protected Overrides Sub OnPaintBackground(ByVal pevent As PaintEventArgs)

End Sub 'OnPaintBackground

'Don't allow the background to paint

Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)

If Not (_backBuffer Is Nothing) Then

_backBuffer.Dispose()

_backBuffer = Nothing

End If

MyBase.OnSizeChanged(e)

End Sub 'OnSizeChanged

The following listing shows a practical double buffered application. A checkbox enables you to choose whether double buffering is used or if the graphics are drawn directly to the screen for the sake of comparison.

C#

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

namespace DoubleBuffer

{

/// <summary>

/// Summary description for Form1.

/// </summary>

public class Form1 : System.Windows.Forms.Form

{

private System.Windows.Forms.Timer timer1;

private System.ComponentModel.IContainer components;

private System.Windows.Forms.CheckBox checkBox1;

float _angle;

bool _doBuffer;

public Form1()

{

//

// Required for Windows Form Designer support

//

InitializeComponent();

//

// TODO: Add any constructor code after InitializeComponent call

//

}

/// <summary>

/// Clean up any resources being used.

/// </summary>

protected override void Dispose( bool disposing )

{

if( disposing )

{

if (components != null)

{

components.Dispose();

}

}

base.Dispose( disposing );

}

#region Windows Form Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

this.components = new System.ComponentModel.Container();

this.timer1 = new System.Windows.Forms.Timer(this.components);

this.checkBox1 = new System.Windows.Forms.CheckBox();

this.SuspendLayout();

//

// timer1

//

this.timer1.Enabled = true;

this.timer1.Tick += new System.EventHandler(this.timer1_Tick);

//

// checkBox1

//

this.checkBox1.Location = new System.Drawing.Point(8, 8);

this.checkBox1.Name = "checkBox1";

this.checkBox1.TabIndex = 0;

this.checkBox1.Text = "Double Buffer";

this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged);

//

// Form1

//

this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

this.ClientSize = new System.Drawing.Size(292, 273);

this.Controls.Add(this.checkBox1);

this.Name = "Form1";

this.Text = "Form1";

this.ResumeLayout(false);

}

#endregion

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main()

{

Application.Run(new Form1());

}

private void timer1_Tick(object sender, System.EventArgs e)

{

_angle+=3;

if(_angle>359)

_angle=0;

Invalidate();

}

private Bitmap _backBuffer;

protected override void OnPaint(PaintEventArgs e)

{

if(_backBuffer==null)

{

_backBuffer=new Bitmap(this.ClientSize.Width,this.ClientSize.Height);

}

Graphics g=null;

if(_doBuffer)

g=Graphics.FromImage(_backBuffer);

else

g=e.Graphics;

g.Clear(Color.White);

g.SmoothingMode=SmoothingMode.AntiAlias;

Matrix mx=new Matrix();

mx.Rotate(_angle,MatrixOrder.Append);

mx.Translate(this.ClientSize.Width/2,this.ClientSize.Height/2,MatrixOrder.Append);

g.Transform=mx;

g.FillRectangle(Brushes.Red,-100,-100,200,200);

mx=new Matrix();

mx.Rotate(-_angle,MatrixOrder.Append);

mx.Translate(this.ClientSize.Width/2,this.ClientSize.Height/2,MatrixOrder.Append);

g.Transform=mx;

g.FillRectangle(Brushes.Green,-75,-75,149,149);

mx=new Matrix();

mx.Rotate(_angle*2,MatrixOrder.Append);

mx.Translate(this.ClientSize.Width/2,this.ClientSize.Height/2,MatrixOrder.Append);

g.Transform=mx;

g.FillRectangle(Brushes.Blue,-50,-50,100,100);

if(_doBuffer)

{

g.Dispose();

//Copy the back buffer to the screen

e.Graphics.DrawImageUnscaled(_backBuffer,0,0);

}

//base.OnPaint (e); //optional but not recommended

}

protected override void OnPaintBackground(PaintEventArgs pevent)

{

//Don't allow the background to paint

}

protected override void OnSizeChanged(EventArgs e)

{

if(_backBuffer!=null)

{

_backBuffer.Dispose();

_backBuffer=null;

}

base.OnSizeChanged (e);

}

private void checkBox1_CheckedChanged(object sender, System.EventArgs e)

{

_doBuffer=this.checkBox1.Checked;

}

}

}

VB

Imports System

Imports System.Drawing

Imports System.Drawing.Drawing2D

Imports System.Collections

Imports System.ComponentModel

Imports System.Windows.Forms

Imports System.Data

Namespace DoubleBuffer

'/ <summary>

'/ Summary description for Form1.

'/ </summary>

Public Class Form1

Inherits System.Windows.Forms.Form

Private WithEvents timer1 As System.Windows.Forms.Timer

Private components As System.ComponentModel.IContainer

Private WithEvents checkBox1 As System.Windows.Forms.CheckBox

Private _angle As Single

Private _doBuffer As Boolean

Public Sub New()

'

' Required for Windows Form Designer support

'

InitializeComponent()

End Sub 'New

'

' TODO: Add any constructor code after InitializeComponent call

'

'/ <summary>

'/ Clean up any resources being used.

'/ </summary>

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing Then

If Not (components Is Nothing) Then

components.Dispose()

End If

End If

MyBase.Dispose(disposing)

End Sub 'Dispose

#Region "Windows Form Designer generated code"

'/ <summary>

'/ Required method for Designer support - do not modify

'/ the contents of this method with the code editor.

'/ </summary>

Private Sub InitializeComponent()

Me.components = New System.ComponentModel.Container

Me.timer1 = New System.Windows.Forms.Timer(Me.components)

Me.checkBox1 = New System.Windows.Forms.CheckBox

Me.SuspendLayout()

'

' timer1

'

Me.timer1.Enabled = True

'

' checkBox1

'

Me.checkBox1.Location = New System.Drawing.Point(8, 8)

Me.checkBox1.Name = "checkBox1"

Me.checkBox1.TabIndex = 0

Me.checkBox1.Text = "Double Buffer"

'

' Form1

'

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

Me.ClientSize = New System.Drawing.Size(292, 273)

Me.Controls.Add(checkBox1)

Me.Name = "Form1"

Me.Text = "Form1"

Me.ResumeLayout(False)

End Sub 'InitializeComponent

#End Region

'/ <summary>

'/ The main entry point for the application.

'/ </summary>

<STAThread()> _

Shared Sub Main()

Application.Run(New Form1)

End Sub 'Main

Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs)Handles timer1.Tick

_angle += 3

If _angle > 359 Then

_angle = 0

End If

Invalidate()

End Sub 'timer1_Tick

Private _backBuffer As Bitmap

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)

If _backBuffer Is Nothing Then

_backBuffer = New Bitmap(Me.ClientSize.Width, Me.ClientSize.Height)

End If

Dim g As Graphics = Nothing

If _doBuffer Then

g = Graphics.FromImage(_backBuffer)

Else

g = e.Graphics

End If

g.Clear(Color.White)

g.SmoothingMode = SmoothingMode.AntiAlias

Dim mx As New Matrix

mx.Rotate(_angle, MatrixOrder.Append)

mx.Translate(Me.ClientSize.Width / 2, Me.ClientSize.Height / 2, MatrixOrder.Append)

g.Transform = mx

g.FillRectangle(Brushes.Red, -100, -100, 200, 200)

mx = New Matrix

mx.Rotate(-_angle, MatrixOrder.Append)

mx.Translate(Me.ClientSize.Width / 2, Me.ClientSize.Height / 2, MatrixOrder.Append)

g.Transform = mx

g.FillRectangle(Brushes.Green, -75, -75, 149, 149)

mx = New Matrix

mx.Rotate(_angle * 2, MatrixOrder.Append)

mx.Translate(Me.ClientSize.Width / 2, Me.ClientSize.Height / 2, MatrixOrder.Append)

g.Transform = mx

g.FillRectangle(Brushes.Blue, -50, -50, 100, 100)

If _doBuffer Then

g.Dispose()

'Copy the back buffer to the screen

e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0)

End If

End Sub 'OnPaint

'base.OnPaint (e); //optional but not recommended

Protected Overrides Sub OnPaintBackground(ByVal pevent As PaintEventArgs)

End Sub 'OnPaintBackground

'Don't allow the background to paint

Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)

If Not (_backBuffer Is Nothing) Then

_backBuffer.Dispose()

_backBuffer = Nothing

End If

MyBase.OnSizeChanged(e)

End Sub 'OnSizeChanged

Private Sub checkBox1_CheckedChanged(ByVal sender As Object, ByVal e AsSystem.EventArgs) Handles checkBox1.CheckedChanged

_doBuffer = Me.checkBox1.Checked

End Sub 'checkBox1_CheckedChanged

End Class 'Form1

End Namespace 'DoubleBuffer

When running the program looks like this�

Figure 1: Double buffering in action.

原地址:

http://bobpowell.net/doublebuffer.aspx

Double Buffering Windows Forms的更多相关文章

  1. .net chart(图表)控件的使用-System.Windows.Forms.DataVisualization.dll

    这个案例指在介绍微软这套免费又功能强大的图表控件Microsoft Chart Controls for Microsoft .NET Framework 3.5,通过它,可让您的项目及报表,轻松套用 ...

  2. create Context Menu in Windows Forms application using C# z

    In this article let us see how to create Context Menu in Windows Forms application using C# Introduc ...

  3. DotNetBar For Windows Forms 12.5.0.2 官方原版及注册

    转自原文DotNetBar For Windows Forms 12.5.0.2 官方原版及注册 DotNetBar是一款带有56个 Windows Form 控件的工具箱,使开发人员可以轻而易举地创 ...

  4. C#Windows Forms 计算器--xdd

    一.计算器 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data ...

  5. Wizard Framework:一个自己开发的基于Windows Forms的向导开发框架

    最近因项目需要,我自己设计开发了一个基于Windows Forms的向导开发框架,目前我已经将其开源,并发布了一个NuGet安装包.比较囧的一件事是,当我发布了NuGet安装包以后,发现原来已经有一个 ...

  6. windows forms 上一个类似于wpf snoop 的工具: Hawkeye

    windows forms 上一个类似于wpf snoop 的工具: Hawkeye 周银辉 WPF上有snoop这样的run time object editor让人用着很爽, 今天搜到了一个for ...

  7. WPF中实例化Com组件,调用组件的方法时报System.Windows.Forms.AxHost+InvalidActiveXStateException的异常

    WPF中实例化Com组件,调用组件的方法时报System.Windows.Forms.AxHost+InvalidActiveXStateException的异常 在wpf中封装Com组件时,调用组件 ...

  8. DotNetBar for Windows Forms 12.9.0.0_冰河之刃重打包版及制作Visual Studio C#项目模板文件详解

    关于 DotNetBar for Windows Forms 12.9.0.0_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...

  9. DotNetBar for Windows Forms 12.7.0.10_冰河之刃重打包版原创发布-带官方示例程序版

    关于 DotNetBar for Windows Forms 12.7.0.10_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版------------- ...

随机推荐

  1. Gruntfile.js

    module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), clea ...

  2. Web前端开发基础 第一天(Html和CSS)

    学习web前端开发基础技术需要掌握:HTML.CSS.JavaScript语言.下面我们就来了解下这三门技术都是用来实现什么的: 1. HTML是网页内容的载体.内容就是网页制作者放在页面上想要让用户 ...

  3. request获取请求头和请求数据

    package cn.itcast.request; import java.io.IOException; import java.io.InputStream; import java.io.Pr ...

  4. lua 可变参数

    问题:对可变参数传递的时候,采用如下方案: local cellData = {MsgText = msgText,Param = ...,CallBackFunc = callBackFunc,Ca ...

  5. 使用 Grafana、collectd 和 InfluxDB 打造现代监控系统

    想打造 New Relic 那样漂亮的实时监控系统我们只需要 InfluxDB/collectd/Grafana 这三个工具,这三个工具的关系是这样的: 采集数据(collectd)-> 存储数 ...

  6. Codevs 3287 货车运输

    题目描述 Description A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过 ...

  7. C#编程之委托与事件四(一)【转】

    C#编程之委托与事件(一)     本文试图在.net Framework环境下,使用C#语言来描述委托.事件的概貌.希望本文能有助于大家理解委托.事件的概念,理解委托.事件的用途,理解它的C#实现方 ...

  8. linux常用操作指令

    Linux常用操作指令: 常用指令 ls        显示文件或目录 -l           列出文件详细信息l(list) -a          列出当前目录下所有文件及目录,包括隐藏的a(a ...

  9. 下拉列表select显示ng-options

    js中如何处理: it-equipment-list-maintenance-create-controller.js 'use strict'; myApp.controller( 'itEquip ...

  10. Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名

    Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf  动态库的后缀为*.so  静态库的后缀为 libxxx.a   ldconfig   目录名 转载自:http://b ...