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
' 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
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
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
' 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
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()
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()
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
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
' 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
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
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
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()
' 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 Usingclause and avoid to explicitly call the Unfreeze method, as follow:
Using New FormFreezer(Me, True)
' Update controls here
' ...
End Using
' Update controls here
' ...
End Using
Calls to Freeze and Unfreeze methods must be balanced (n calls to Freeze requires ncalles 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;
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(newPoint(rect.Left, rect.Top)), new Point(0, 0), newSize(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 ;
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;
FreezeCount = 0;
if (FreezeCount > 0)
return;
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);
}
}
0 comments:
Post a Comment