If you're a Windows Form developer, I think that at least once in your life
you've seen the problem of the noisy flickering effect when you've to update a
big amount of controls on a form via code.
The common controls that usually needs to be updated (for example by adding
lots of data to them) are ListBox, ComboBox, TreeView and ListView, and
.NET have two wonderful methods to help this process: BeginUpdate and EndUpdate. This methods are really useful to
increase performances on a Winform application and must be always used when
you've to add a big amount of data to one of these controls.
The usage is really simple:
Public Sub AddToMyListBox()
' Stop the ListBox from drawing while items are
added.
listBox1.BeginUpdate()
' Loop through and add five
thousand new items.
Dim x As Integer
For x = 1 To
4999
listBox1.Items.Add("Item " & x.ToString())
Next x
' End the update process and force a repaint of the
ListBox.
listBox1.EndUpdate()
End Sub
In summary, BeginUpdate maintains
performance while items are added to the ListBox (or one of the other controls
above) one at a time by preventing the control from drawing until the EndUpdate method is called.
But what happens when you've to update a control that doesn't expose these
two methods? Apparently, there's not a real solution to this problem.
Stimulated by this question, Francesco Balena (one that always
has brilliant ideas) has found an interesting
solution, unfortunately blogged only in italian, but that I think it merits
to be published to the entire community.
His idea can be summarized as follow:
- Save the aspect of your form pixel by pixel by copying it on a bitmap
image
- Create a PictureBox control with the size of your form and load the
created bitmap into this PictureBox
- Add your PictureBox to the Controls collection and bring it to front, in
order to cover all the other controls
- Update all your controls (using BeginUpdate/EndUpdate methods) while the
user is seeing the fixed image of your form (no flickering effect)
- When the update operation is completed, delete the PictureBox and show the
real form content
The class that you can use to make this work is this:
Public Class FormFreezer
Implements IDisposable
' The form being frozen
Dim Form As
Form
' the auxiliary PictureBox that will
cover the form
Dim PictureBox As
PictureBox
' the number of times the Freeze method
has been called
Dim FreezeCount As Integer = 0
' create an instance associated with a given form
' and optionally freeze the form right away
Public Sub New(ByVal form As Form, Optional ByVal freezeIt As Boolean = False)
Me.Form = form
If freezeIt Then Me.Freeze()
End Sub
' freeze the form
Public Sub
Freeze()
' Remember we have
frozen the form once more
FreezeCount += 1
' Do nothing if it was already
frozen
If FreezeCount > 1 Then Exit Sub
' Create a PictureBox that
resizes with its contents
PictureBox = New PictureBox()
PictureBox.SizeMode =
PictureBoxSizeMode.AutoSize
' create a bitmap as large as the form's
client area and with same color depth
Dim frmGraphics As Graphics =
Form.CreateGraphics()
Dim rect As
Rectangle = Form.ClientRectangle
PictureBox.Image = New Bitmap(rect.Width, rect.Height, frmGraphics)
frmGraphics.Dispose()
' copy the screen contents,
from the form's client area to the hidden bitmap
Dim picGraphics As Graphics =
Graphics.FromImage(PictureBox.Image)
picGraphics.CopyFromScreen(Form.PointToScreen(New Point(rect.Left, rect.Top)), New Point(0, 0), New
Size(rect.Width, rect.Height))
picGraphics.Dispose()
' Display the bitmap in the
picture box, and show the picture box in front of all other
controls
Form.Controls.Add(PictureBox)
PictureBox.BringToFront()
End Sub
' unfreeze the form
' Note: calls to
Freeze and Unfreeze must be balanced, unless force=true
Public Sub Unfreeze(Optional ByVal force As Boolean = False)
' exit if nothing to
unfreeze
If FreezeCount = 0 Then Exit Sub
' remember we've unfrozen the form, but
exit if it is still frozen
FreezeCount -= 1
' force the unfreeze if so
required
If force Then
FreezeCount = 0
If FreezeCount > 0 Then Exit Sub
' remove the picture box
control and clean up
Form.Controls.Remove(PictureBox)
PictureBox.Dispose()
PictureBox = Nothing
End Sub
' return true if the form is currently frozen
Public ReadOnly Property IsFrozen()
As Boolean
Get
Return FreezeCount > 0
End Get
End Property
' ensure that resources are cleaned up correctly
Public Overridable Sub Dispose() Implements IDisposable.Dispose
Me.Unfreeze(True)
End Sub
End
Class
This is a nice class with a simple usage. If you have a Windows Form and you
need to update its controls as previously described, you can use the code as
follow:
Dim ff As New FormFreezer(Me, True)
' update controls here
' ...
ff.Unfreeze()
Here, Me is your current form, where
the controls must be updated.
As pointed by Francesco, the class
implements IDisposable so you can use the Using clause and avoid to explicitly call the Unfreeze method, as follow:
Using New FormFreezer(Me, True)
' Update controls
here
' ...
End Using
Calls to Freeze and Unfreeze methods must be balanced (n calls to
Freeze requires n calles to Unfreeze at the end).
This is a brilliant idea for a problem that sometimes is really a noise for
the end user. Thanks to Francesco for this nice tip and I hope that he permits
me to share his ideas to all the "non-Italian" community. 
If someone has a more brilliant way to solve this problem, I'm glad to
receive suggestions (and I think Francesco will be too).
P.S. the C# version of the class is this:
public
class FormFreezer: IDisposable
{
// The form being
frozen
Form form;
// the auxiliary PictureBox that will cover the
form
PictureBox pictureBox;
// the number of times the Freeze method has been
called
int FreezeCount = 0;
// create an instance associated with a given
form
// and freeze the form in base of flag
freezeIt
public FormFreezer(Form form, bool
freezeIt)
{
this.form = form;
if (freezeIt) this.Freeze();
}
// freeze the form
public void
Freeze()
{
// Remember we have frozen the form once
more
// Do nothing if it was already
frozen
if (++FreezeCount >
1)
return;
// Create a PictureBox that resizes with its
contents
pictureBox
= new PictureBox();
pictureBox.SizeMode
= PictureBoxSizeMode.AutoSize;
// create a bitmap as large as the form's client area and
with same color depth
Graphics frmGraphics =
form.CreateGraphics();
Rectangle rect =
form.ClientRectangle;
pictureBox.Image
= new Bitmap(rect.Width, rect.Height,
frmGraphics);
frmGraphics.Dispose();
// copy the screen contents, from the form's client area to
the hidden bitmap
Graphics picGraphics = Graphics.FromImage(pictureBox.Image);
picGraphics.CopyFromScreen(form.PointToScreen(new Point(rect.Left,
rect.Top)), new Point(0, 0), new
Size(rect.Width,
rect.Height));
picGraphics.Dispose();
// Display the bitmap in the picture box, and
show the picture box in front of all other controls
form.Controls.Add(pictureBox);
pictureBox.BringToFront();
}
// unfreeze the form
// Note: calls to Freeze and Unfreeze must be balanced,
unless force=true
public void
Unfreeze(bool force)
{
// exit if nothing to unfreeze
if ( FreezeCount == 0
)
return ;
// remember we've unfrozen the form, but exit if it is
still frozen
FreezeCount
-= 1;
// force the unfreeze if so
required
if
(force)
FreezeCount =
0;
if (FreezeCount >
0)
return;
// remove the picture box control and clean
up
pictureBox.Controls.Remove(pictureBox);
pictureBox.Dispose();
pictureBox
= null;
}
// return true if the form is currently frozen
public bool
IsFrozen
{
get { return (FreezeCount > 0); }
}
void IDisposable.Dispose()
{
this.Unfreeze(true);
}
}