Don't do anything other than copy a back buffer to the control in the controls paint method.
Don't necessarily call Refresh() from event handlers that modify the image -- it's inefficient to call Refresh from a mouse movement handler that handles dragging of an object across the control, for example. Draw on a back buffer then Refresh() at set points in time (<= 250 ms) using a System.Windows.Forms.Timer.
Code snippets here as a reminder:
private System.Windows.Forms.Timer timer; private BufferedGraphicsContext context; private BufferedGraphics gfx; private System.Windows.Forms.Panel panel1; public Form() { this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1Paint); this.SetStyle(ControlStyles.AllPaintingInWmPaint ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); context = BufferedGraphicsManager.Current; context.MaximumBuffer = new Size(this.panel1.Width + 1, this.panel1.Height + 1); gfx = context.Allocate(this.panel1.CreateGraphics(), new Rectangle(0, 0, this.panel1.Width, this.panel1.Height)); timer = new System.Windows.Forms.Timer(); timer.Interval = 100; timer.Tick += new EventHandler(this.OnTimer); timer.Start(); } private void OnTimer(object sender, EventArgs e) { DrawToBuffer(gfx.Graphics); gfx.Render(Graphics.FromHwnd(this.panel1.Handle)); } void panel1Paint(object sender, System.Windows.Forms.PaintEventArgs e) { gfx.Render(); } private void panel1Resize(object sender, EventArgs e) { context.MaximumBuffer = new Size(this.panel1.Width + 1, this.panel1.Height + 1); if (gfx != null) { gfx.Dispose(); gfx = null; } gfx = context.Allocate(this.CreateGraphics(), new Rectangle(0, 0, this.panel1.Width, this.panel1.Height)); DrawToBuffer(gfx.Graphics); this.Refresh(); }