|A C# Oscilloscope Display In Windows Forms|
|Written by Mike James|
|Thursday, 04 June 2015|
Page 1 of 2
If you need a real time stripchart or oscilloscope style display for a Windows forms project then the good news is that it can be done without having to move outside of C#.
It's a simple enough idea - create a display something like a stripchart recorder or an oscilloscope display that works fast enough to be used for real time applications, preferably using nothing but .NET.
This may sound like an easy idea but there are lots of ways in which it can go wrong when speed is at issue. Let's take a look at two ways of doing the job - the first using the GDI+ and the second using the Windows API and for a project that makes use of the idea see Looking at Chaos.
The finished sine wave display - it scrolls to the right.
Shifting A Bitmap
The simplest idea is to use a picturebox with a Bitmap object to implement graphic persistence.
The Bitmap can be moved in any direction by simply re-drawing it slightly displaced. The problem is that for the shift to be permanent, so that subsequent shifts are cumulative, you have to replace the bitmap with the shifted version and this means the creation of a lot of temporary objects and a lot of juggling.
For any of this to make sense you have to keep in mind that a Bitmap object is a wrapper for a deeper API bitmap and there are various layers of resources used in its construction. You also need to keep in mind the fact that the variables that you declare are references to the bitmaps and not the bitmaps themselves - this will become clear as the program is explained.
Start a new C# Windows forms project and place a button and a picturebox on the form. We could create a new control descended from the picturebox or a class from Bitmap but it's easier and just as good in this case to use extension methods to add a new Scroll method to the Bitmap class.
To add an extension method to an existing class all we need is to add a static class to the project. An extension method for MyClass then takes the form:
Notice that you need to start the parameter list with "this" and the name of the class this is how the compiler links up the extension to the class it extends. It also has to be method of a static public class.
To add a Scroll method to the Bitmap class all we need is a static class:
and a static method with the appropriate signature:
dx and dy are the amounts the bitmap will be shifted and the this Bitmap indicates the class that the method is added to.
Next we need a temporary bitmap object to hold the shifted bitmap. We create a Graphics object from the bitmap and draw the existing bitmap on with a shift:
By returning the temporary bitmap the shift is completed.
Notice that you can't draw a bitmap onto itself - the GDI+ doesn't like it and so you do need the temporary bitmap. Also notice that you do need the Dispose call to tidy up after the Graphics object - without it you will eventually run out of memory as the garbage collection system fails to deal with the resources created.
To try the Scroll method out we first create a bitmap for the Image of the picturebox to make the graphics persist:
Next we need something to plot and what could be more obvious than a sine wave:
Don't worry about the details of how y is calculated - all that matters is to produce a reasonable value of y and a nice display. The important point is that we have set the pixel at (0,y) in the bitmap.
If you refresh the picturebox you would see a single point. And if the loop was repeated that point would move up and down as y varied to product a vertical line - but we intend to scroll the bitmap to move the plotted point to the left by one pixel:
Notice this is a sneaky way of drawing to the same bitmap as BM is set to reference the returned bitmap.
Notice also that as BM is a reference to the bitmap we now have BM referencing the bitmap returned by the method and the picturebox referencing the original unshifted bitmap. To make them reference the same bitmap once again we need:
We also need an:
Without a DoEvents you will discover that the application pauses for quite a while when the for loop completes as it processes the entire message queue that has been blocked for a long period. I discovered this by experimentation and at first I thought it was the garbage collection system causing the problem but no…
Of course to a certain extend the need for a DoEvents is created by the slightly artificial use of a for loop to generate the data. In a more realistic setting you would either use another thread or a timer to generate the data and then the main application thread wouldn't be blocked in this way.
If you try this out you will discover that it works as advertised but it is sometimes a little on the slow side.
You might think that it is something to do with creating and disposing of so many temporary objects but even a version that keeps the number of Graphics objects and Bitmaps down to two of each runs almost as slowly.
The full listing is:
|Last Updated ( Thursday, 04 June 2015 )|