The solution that I provide here is the minimal needed to explain the solution to your problem. The complete solution can be downloaded from DynamicUpdate.
As I mentioned earlier, one way to eliminate flicker is to use double buffering. Because I do a lot of graphics programming, I've developed a class called GraphicsBuffer. It basically does pretty much everything you need. If you use the GraphicsBuffer class, you will not be faced with cross thread errors. The entry points in GraphicsBuffer that will be of interest to you are:
- GraphicsBuffer - the class constructor
- CreateGraphicsBuffer - completes the creation of the GraphicsBuffer object
- DeleteGraphicsBuffer - deletes the current GraphicsBuffer instance
- Graphic - returns the current instance's Graphics object
- RenderGraphicsBuffer - renders the current GraphicsBuffer instance to the specified Graphics object
If you are interested in the complete GraphicsBuffer class, make a comment to this solution.
Here is the solution:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Timers;
using System.Windows.Forms;
namespace DynamicUpdate
{
public partial class Form1 : Form
{
static Color BACKGROUND_COLOR = SystemColors.Control;
const int BOXES_PER_ROW = 10;
const int ROWS_IN_DISPLAY = 10;
const int BOX_WIDTH = 50;
const int BOX_HEIGHT = BOX_WIDTH;
const int INTER_BOX_SPACING = 5;
const int LABEL_OFFSET = 1;
const int BITMAP_OFFSET = BOX_WIDTH / 5;
const int BITMAP_WIDTH = ( BOXES_PER_ROW *
( INTER_BOX_SPACING +
BOX_WIDTH ) ) +
INTER_BOX_SPACING ;
const int BITMAP_HEIGHT = BITMAP_WIDTH;
const int FORM1_WIDTH = BITMAP_WIDTH +
( 2 * BITMAP_OFFSET );
const int FORM1_HEIGHT = BITMAP_HEIGHT +
( BOX_HEIGHT );
const double TIMER_INTERVAL = 10000.0;
GraphicsBuffer display = null;
Random random = new Random ( );
int random_box = 0;
System.Timers.Timer timer = null;
protected override void OnFormClosing ( FormClosingEventArgs e )
{
base.OnFormClosing ( e );
if ( display != null )
{
display = display.DeleteGraphicsBuffer ( );
}
if ( timer != null )
{
if ( timer.Enabled )
{
timer.Stop ( );
}
timer.Dispose ( );
timer = null;
}
}
public Form1 ( )
{
InitializeComponent ( );
this.Width = FORM1_WIDTH;
this.Height = FORM1_HEIGHT;
this.SetStyle ( ( ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint ),
true );
this.UpdateStyles ( );
timer = new System.Timers.Timer ( TIMER_INTERVAL );
timer.Elapsed += new ElapsedEventHandler ( tick );
timer.Start ( );
this.Invalidate ( );
}
private void tick ( object sender,
EventArgs e )
{
this.Invalidate ( );
}
void create_display_graphic ( )
{
if ( display != null )
{
display = display.DeleteGraphicsBuffer ( );
}
display = new GraphicsBuffer ( );
display.CreateGraphicsBuffer ( BITMAP_WIDTH,
BITMAP_HEIGHT );
display.Graphic.SmoothingMode = SmoothingMode.HighQuality;
random_box = random.Next ( 0, 100 );
}
void draw_display_graphic ( Graphics graphics )
{
int box_count = 0;
int x = BITMAP_OFFSET;
int y = BITMAP_OFFSET;
for ( int i = 0; ( i < ROWS_IN_DISPLAY ); i++ )
{
for ( int j = 0; ( j < BOXES_PER_ROW ); j++ )
{
Brush brush = new SolidBrush ( Color.Cyan );
Pen pen = new Pen ( Color.Cyan );
Rectangle rectangle;
if ( box_count == random_box )
{
brush = new SolidBrush ( Color.Pink );
pen = new Pen ( Color.Pink );
}
rectangle = new Rectangle (
new Point ( x, y ),
new Size ( BOX_WIDTH,
BOX_HEIGHT ) );
graphics.DrawRectangle ( pen, rectangle );
graphics.FillRectangle ( brush, rectangle );
graphics.DrawString ( box_count.ToString ( ),
SystemFonts.IconTitleFont,
Brushes.Black,
x + LABEL_OFFSET,
y + LABEL_OFFSET );
pen.Dispose ( );
brush.Dispose ( );
box_count++;
x += ( BOX_WIDTH + INTER_BOX_SPACING );
}
x = BITMAP_OFFSET;
y += ( BOX_HEIGHT + INTER_BOX_SPACING );
}
}
protected override void OnPaint ( PaintEventArgs e )
{
base.OnPaint ( e );
e.Graphics.Clear ( BACKGROUND_COLOR );
create_display_graphic ( );
draw_display_graphic ( display.Graphic );
display.RenderGraphicsBuffer ( e.Graphics );
}
}
}
Although I disagree with your method of updating your UI (I think you should use an event handler), the code uses a timer. The following describes the basic function of the various methods.
- After the declaration of the constants and variables, appears a method override of OnFormClosing. This is required to dispose of objects created in the application that, if not disposed of, would cause a memory leak.
- In the Form1 constructor we set the size of the form, the form's styles, declare the timer, and invoke Invalidate (causing the form to paint the first time).
- Each time that the timer ticks, Invalidate is invoked and the OnPaint event handler is triggered.
- The bulk of processing occurs in the OnPaint event handler that:
- Clears the form
- Causes the GraphicsBuffer to be recreated
- Causes the GraphicsBuffer to be drawn
- Renders the GraphicsBuffer to the screen
Note that the program simulates an external event by randomly causing one of the boxes to paint in Pink. This is probably where you may need additional help.
Hope that helps.