Creating a custom graph in Winforms - c#

I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:

using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;

Related

Using a background worker to efficiently write to a GUI thread

I am using a BackgroundWorker to pull video from a camera and write it to a PictureBox on my WinForms form. In the BW thread I simply pull a frame from the camera, put it into the PictureBox, sleep, and continue:
while (CaptureThreadRunning)
{
Thread.Sleep(5);
Image tmp = Camera.GetFrame(500);
pbCameraFeed.Image = tmp;
//this.BeginInvoke(new MethodInvoker(delegate { pbCameraFeed.Image = Camera.GetFrame(500); }));
}
The issue is that eventually adjusting or moving the form around my screen will throw the exception System.InvalidOperationException with the message Additional information: Object is currently in use elsewhere. on the line pbCameraFeed.Image = tmp;
I assume that the library is trying to paint something to do with the PictureBox at the same time as my while loop is, so I switched to the this.BeginInvoke implementation that is commented out above. Unfortunately that cuts my framerate significantly. I am running this code on a very slow Mini PC which may be contributing to the issue.
What I really want is a way to update my GUI with the image reliably that doesn't drop my framerate by nearly half. Are there other standard ways to do this? A BW thread seemed perfect for this application, but am I missing something?
Thanks
If I were you I would definitely check out the AForge.NET Framework. No need to reinvent the wheel ;)
http://www.aforgenet.com/framework/samples/video.html
AForge.NET is an open source C# framework designed for developers and
researchers in the fields of Computer Vision and Artificial
Intelligence - image processing, neural networks, genetic algorithms,
fuzzy logic, machine learning, robotics, etc.
The framework is comprised by the set of libraries and sample
applications, which demonstrate their features:
AForge.Imaging - library with image processing routines and filters;
AForge.Vision - computer vision library;
AForge.Video - set of libraries for video processing;
...
I would recommend not to use a PictureBox, and instead directly draw to a UserControl surface. This can be easily done by addig code to the Paint and Invalidate events of a UserControl.
This example below, creates a user control which has a BitMap property that it's drawed to its surface every time the control is invalidated.
So, for example, to randomly render JPG images from folder D:\MyPictures, you can do the following:
using System.Windows.Forms;
using System.Drawing;
void Main()
{
var pictures = Directory.GetFiles(#"D:\MyPictures", "*.jpg");
var rnd = new Random();
var form = new Form();
var control = new MyImageControl() { Dock = DockStyle.Fill };
form.Controls.Add(control);
var timer = new System.Windows.Forms.Timer();
timer.Interval = 50;
timer.Tick += (sender, args) =>
{
if (control.BitMap != null)
control.BitMap.Dispose();
control.BitMap = new Bitmap(pictures[rnd.Next(0, pictures.Length)]);
control.Invalidate();
};
timer.Enabled = true;
form.ShowDialog();
}
public class MyImageControl : UserControl // or PictureBox
{
public Bitmap BitMap { get; set; }
public MyImageControl()
{
this.Paint += Graph_Paint;
this.Resize += Graph_Resize;
}
private void Graph_Paint(object sender, PaintEventArgs e)
{
if (this.BitMap != null)
{
lock (this.BitMap)
{
Graphics g = e.Graphics;
g.DrawImage(this.BitMap, this.ClientRectangle);
}
}
}
private void Graph_Resize(object sender, System.EventArgs e)
{
this.Invalidate();
}
}
I think this can be easily changed to render the camera images instead of the picture files.
The code was tested on LinqPad

Growing Rectangle

I have a growing rectangle drawn on top of a TableLayoutPanel but when it grows it causes a horrible flicker even with Double Buffer.
I am using e.Graphics.FillRectangle and a global variable that increases the size of the rectangle. I set up a timer to make it grow every 1/10th of a second by 1 pixel. Why does it flicker so much and how can I stop the flicker?
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width-grow)/2, (tableLayoutPanel1.Height-grow)/2, grow,grow);
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Refresh();
}
In order to rule out all other possibilities I removed everything from my program and started from scratch with just a growing rectangle and it still creates this horrible flicker.
Ok, here is the code. You first need to make background buffer Bitmap with the size of your control. After that, you will need to draw everything on the Bitmap, and than draw that bitmap onto the control.
Bitmap backBuffer = null;
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
if (backBuffer == null)
backBuffer = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
Graphics g = Graphics.FromImage(backBuffer);
g.Clear(tableLayoutPanel1.BackColor);
g.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width - grow) / 2, (tableLayoutPanel1.Height - grow) / 2, grow, grow);
e.Graphics.DrawImage(backBuffer, 0, 0, backBuffer.Width, backBuffer.Height);
g.Dispose();
}
private void tableLayoutPanel1_Resize(object sender, EventArgs e)
{
backBuffer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Invalidate();
}
Note that you will need to create new Bitmap each time you resize the TableLayoutPanel. In addition I suggest using Invalidate() instead of Refresh().
But this will still include some potential flickering. In order to completely avoid flicker, in addition to the previous code, you will need to subclass the TableLayoutPanel and override the OnPaintBackground() method in such a way that base.OnPaintBackground is never called. This way way won't have any flickering at all. The reason why you have the flickering is because the background is redrawn before your Rectangle, any time you draw it. Switch the original TableLayoutPanel class with this BackgroundlessTableLayoutPanel
public class BackgroundlessTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Most controls have a Paint event where you can implement any custom drawing you need to do. It is also possible to implement your own control where you override the OnPaint method. See the article here.
Both these should give ok results.

Graphics.DrawImage speed

In my program, I'm coding a basic image editor. Part of this allows the user to draw a rectangular region and I pop up a display that shows that region zoomed by 3x or so (which they can adjust further with the mouse wheel). If they right click and drag this image, it will move the zoom region around on the original image, basically acting as a magnifying glass.
The problem is, I'm seeing some serious performance issues even on relatively small bitmaps. If the bitmap showing the zoomed region is around 400x400 it's still updating as fast as mouse can move and is perfectly smooth, but if I mouse wheel the zoom up to around 450x450, it immediately starts chunking, only down to around 2 updates per second, if that. I don't understand why such a small increase incurs such an enormous performance problem... it's like I've hit some internal memory limit or something. It doesn't seem to matter the size of the source bitmap that is being zoomed, just the size of the zoomed bitmap.
The problem is that I'm using Graphics.DrawImage and a PictureBox. Reading around this site, I see that the performance for both of these is typically not very good, but I don't know enough about the inner workings of GDI to improve my speed. I was hoping some of you might know where my bottlenecks are, as I'm likely just using these tools in poor ways or don't know of a better tool to use in its place.
Here are some snippets of my mouse events and related functions.
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// slide the zoomed part to look at a different area of the original image
if (zoomFactor > 1)
{
isMovingZoom = true;
// try saving the graphics object?? are these settings helping at all??
zoomingGraphics = Graphics.FromImage(displayImage);
zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
}
}
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMovingZoom)
{
// some computation on where they moved mouse ommitted here
zoomRegion.X = originalZoomRegion.X + delta.X;
zoomRegion.Y = originalZoomRegion.Y + delta.Y;
zoomRegionEnlarged = scaleToOriginal(zoomRegion);
// overwrite the existing displayImage to prevent more Bitmaps being allocated
createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
}
}
private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
{
Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));
outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);
if (displayImage != originalDisplayImage && displayImage != output)
displayImage.Dispose();
setImageInBox(output);
}
// sets the picture box image, as well as resizes the window to fit
void setImageInBox(Bitmap bmp)
{
pictureBox.Image = bmp;
displayImage = bmp;
this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (isMovingZoom)
{
isMovingZoom = false;
zoomingGraphics.Dispose();
}
}
}
As you can see, I'm not declaring a new Bitmap every time I want to draw something, I'm reusing an old Bitmap (and the Bitmap's graphics object, though I don't know if there is much cost with calling Graphics.FromImage repeatedly). I tried adding Stopwatches around to benchmark my code, but I think DrawImage passes functionality to another thread so the function claims to be done relatively quickly. I'm trying to Dispose all my Bitmap and Graphics objects when I'm not using them, and avoid repeated calls to allocate/deallocate resources during the MouseMove event. I'm using a PictureBox but I don't think that's the problem here.
Any help to speed up this code or teach me what's happening in DrawImage is appreciated! I've trimmed some excess code to make it more presentable, but if I've accidentally trimmed something important, or don't show how I'm using something which may be causing problems, please let me know and I'll revise the post.
The way I handle issues like that is when receiving the Paint event, I draw the whole image to a memory bitmap, and then BLT it to the window.
That way, all visual flash is eliminated, and it looks fast, even if it actually is not.
To be more clear, I don't do any painting from within the mouse event handlers.
I just set up what's needed for the main Paint handler, and then do Invalidate.
So the painting happens after the mouse event completes.
ADDED: To answer Tom's question in a comment, here's how I do it. Remember, I don't claim it's fast, only that it looks fast, because the _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0)); appears instantaneous. It just bips from one image to the next.
The user doesn't see the window being cleared and then repainted, thing by thing.
It gives the same effect as double-buffering.
Graphics grToDrawOn = null;
Bitmap bmToDrawOn = null;
private void DgmWin_Paint(object sender, PaintEventArgs _e){
int w = ClientRectangle.Width;
int h = ClientRectangle.Height;
Graphics gr = _e.Graphics;
// if the bitmap needs to be made, do so
if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
// if the bitmap needs to be changed in size, do so
if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
bmToDrawOn = new Bitmap(w, h, gr);
}
// hook the bitmap into the graphics object
grToDrawOn = Graphics.FromImage(bmToDrawOn);
// clear the graphics object before drawing
grToDrawOn.Clear(Color.White);
// paint everything
DoPainting();
// copy the bitmap onto the real screen
_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
}
private void DoPainting(){
grToDrawOn.blahblah....
}

C# Winforms Ultra Slow Update Of Screen Using Drawline method

I've had a look around the web regarding this, but haven't found the exact answer I'm looking for or I've tried what is suggested and it doesn't work!
I'm having issues in that I have a screen which has approximately 72 Checkboxes on it in a matrix which I have connected together using lines the coordinates of which I store in a list.
To draw the lines I use the Drawline method in an override method for OnPaint to iterate through the list as follows :-
protected override void OnPaint(PaintEventArgs e)
{
Pen myPen = new Pen(System.Drawing.Color.Black);
Graphics g = this.CreateGraphics();
myPen.Width = 5;
foreach(ConnectionLine cl in connectionLines)
{
g.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
myPen.Dispose();
g.Dispose();
}
The strange thing about this is that it doesn't appear to be the lines that take the time to draw - it's now the checkboxes, if I remove the line functionality these refresh in the blink of an eye.
Any ideas much appreciated.
Thanks,
Dave
Part of the problem could be that you are recreating the Graphics object each time the control is painted. Instead you should use the e.Graphics object that is provided in PaintEventArgs. You could also try using only one instance of Pen.
private readonly Pen _myPen = new Pen(System.Drawing.Color.Black) {Width = 5};
protected override void OnPaint(PaintEventArgs e)
{
foreach (var cl in connectionLines)
e.Graphics.DrawLine(_myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
There is no need to create your own and dispose of a Graphics object. Use what is available with the event handler. ALso you should use using rather than calling Dispose explicitly.
protected override void OnPaint(PaintEventArgs e)
{
using (Pen myPen = new Pen(System.Drawing.Color.Black, 5.0))
{
foreach(ConnectionLine cl in connectionLines)
e.Graphics.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
}
Also, if your lines connect you should get better performance and cleaner code with Graphic's DrawLines method. You would have to change how you store your points or extract them from your conncetionLines collection before calling.

C#, double buffer in WinForms?

private void button3_Click(object sender, EventArgs e)
{
this.DoubleBuffered = true;
for (int i = 0; i < 350; i++)
{
using (Graphics g = this.CreateGraphics() )
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
}
Yet thought I have the DoubleBuffered set to true, the image still flickers. Any ideas what am I doing wrong?
Thanks!
As Neil noted, you don't need to (and shouldn't) create a new Graphics object in each iteration of the loop. These are relatively expensive resources and should not be created willy nilly.
Also, you shouldn't be painting like that inside of a button Click handler by calling CreateGraphics. It can lead to problems, most notably your drawing being "undone" when the paint handler is invoked (i.e., every time the window is receives a WM_PAINT message and is refreshed). You should do all of your painting by overriding OnPaint and simply call Invalidate() when you need to update your form.
As for the flickering, setting DoubleBuffered to true will usually take care of it, but rolling your own double buffering is trivial. Give it a try. Also realize that drawing in a loop like that probably isn't what you want to do. Use a timer if you want to update once per some interval. Your code is being executed as fast as the loop can execute, which is not usually desirable.
private void someTimer_Tick( ... )
{
Invalidate();
}
protected override void OnPaint( PaintEventArgs e )
{
using( var tempBmp = new Bitmap( ... ) )
using( var g = Graphics.FromImage( tempBmp ) )
{
// draw to tempBmp
e.Graphics.DrawImage( tempBmp, new Point( 0, 0 ) );
}
}
The problem is you are creating a new graphics object on each iteration of the loop
Move the for statement within the using statement, and you should see a dramatic performance increase:
using (Graphics g = this.CreateGraphics() )
{
for (int i = 0; i < 350; i++)
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
That, and it may also be a good idea to move the Resource file you are loading into a local variable.
Double-buffering is only enabled for the Paint event. You are directly drawing to the screen with CreateGraphics(), the g.Clear() call is very noticeable since it instantly erases the drawn image. Not drawing in the Paint event or OnPaint method is almost always a mistake.

Categories