Introduction
Let’s say you have made a few cool mathematical J# applications and want to show the results graphically. The Visual Studio IDE does not come with a graph user control, so what to do next? You could buy a user control from a third party, or make one for yourself. This graph control will only work for positive numbers. It’s up to you to make this work for negative numbers. This is not an article for showing how to build an advanced mathematical graph control, but an article that will show you how to build a custom user control using custom painting in J#. We will be giving the graph, x and y points in an array, and not a mathematical function f(x).
Get to the Point!
Let’s stop muttering and get busy writing the coding. Open Visual Studio and create a new J# Windows Control Library project.
When this article was written in J# (.NET 1.0 Visual Studio 2002 and 1.1 Visual Studio 2003), it did not support operator overloading. In C#/VB.NET/Managed C++, we could have overridden the OnPaint
method to be able to paint on the Panel
. In J#, we register a paint handler with the Panel
. To register a paint handler with the Panel
, please do this in the constructor:
this.add_Paint( new System.Windows.Forms.PaintEventHandler(this.Paint) );
This is how the Paint
method will look like:
private void Paint( System.Object object,
System.Windows.Forms.PaintEventArgs args )
{
System.Drawing.Graphics g = args.get_Graphics();
}
The System.Drawing.Graphics
provides us with basic drawing functions to the Panel
. Among them are functions to draw lines, pies, images, strings and so on. You can read more about the Graphics
class in the MSDN library:
Background Color and Image
We want our graph control to look nice, so we will give the application writer the capability to change the background color. As a bonus, we will give the control a nice image background property. With a background image, we can give a nice 3D-effect:
private System.Drawing.Color _backgroundColor = System.Drawing.Color.get_White();
private System.Drawing.Image _backgroundImage = null;
public System.Drawing.Color get_BackgroundColor( )
{
return this._backgroundColor;
}
public void set_BackgroundColor( System.Drawing.Color _in )
{
this._backgroundColor = _in;
}
public void set_BackgroundImage( System.Drawing.Image _img )
{
this._backgroundImage = _img;
}
public System.Drawing.Image get_BackgroundImage( )
{
return this._backgroundImage;
}
private void graph_SetBGColorImage( System.Drawing.Graphics g )
{
this.set_BackColor( this._backgroundColor );
if ( this._backgroundImage != null )
{
System.Drawing.TextureBrush textureBrush =
new System.Drawing.TextureBrush(
this._backgroundImage,
System.Drawing.Drawing2D.WrapMode.TileFlipXY );
g.FillRectangles(
textureBrush,
new System.Drawing.Rectangle[]{new System.Drawing.Rectangle(0,
0, this.get_Width(), this.get_Height())}
);
}
}
Titles
Our graph control must have a heading or a title. To do so, we will be using a title, title color and a font property for the application:
private System.Drawing.Font _titleFont = this.get_Font();
private String _title = "";
private System.Drawing.Color _titleColor =
System.Drawing.Color.get_Black();
public void set_Title( String _in )
{
this._title = new String( _in );
}
public String get_Title( )
{
return new String( this._title );
}
public void set_TitleFont( System.Drawing.Font _font )
{
this._titleFont = (System.Drawing.Font)_font.Clone();
}
public System.Drawing.Font get_TitleFont( )
{
return (System.Drawing.Font)this._titleFont.Clone();
}
public System.Drawing.Color get_TitleColor( )
{
return this._titleColor;
}
public void set_TitleColor( System.Drawing.Color _color )
{
this._titleColor = _color;
}
The title
should be centered on the Panel
, so we will calculate the center of the window and the center of the title text width. Center of the title
minus the center of the window width will give us the start X
position of the title
. Please remember to take action if the title
is too wide for the panel. The next step is to calculate or give a Y
position. We put this code in a separate private
method. We measure the title string
with the following code:
float titleWidth = g.MeasureString( this._title,
this._titleFont).get_Width();
The method for showing the title
will be:
private void graph_ShowTitle( System.Drawing.Graphics g )
{
try
{
int panelWidth = this.get_Width();
float titleWidth = g.MeasureString( this._title,
this._titleFont).get_Width();
int theTitleXPos =
System.Convert.ToInt32((panelWidth/2.0f)-(titleWidth/2.0f));
int theTitleYPos = int theTitleYPos = 10;
if ( theTitleXPos < 0 ) theTitleXPos = 0;
g.DrawString( this._title,
this._titleFont,
new System.Drawing.SolidBrush(this._titleColor),
theTitleXPos,
theTitleYPos,
System.Drawing.StringFormat.get_GenericDefault()
);
}
catch ( System.Exception ee )
{
System.Windows.Forms.MessageBox.Show( ee.get_Source() );
}
}
The Graph Points
The points on the graph are given as an array of PointF
to a property:
private System.Drawing.PointF[] _points = null;
public System.Drawing.PointF[] get_Points( )
{
return this._points;
}
The X and Y Lines
The application writer must be able to give the X
and Y
lines colors. We insert a new Color
property into our control:
private System.Drawing.Color _XYLineColor =
System.Drawing.Color.get_White();
public System.Drawing.Color get_XYLineColor( )
{
return this._XYLineColor;
}
public void set_XYLineColor( System.Drawing.Color _col )
{
this._XYLineColor = _col;
}
The Graph
The graph
drawing method will look like this:
private void graph_DrawXYLines( System.Drawing.Graphics g )
{
if ( this._points.length > 1 )
{
float _minX = this._points[0].get_X();
float _maxX = this._points[0].get_X();
float _maxY = this._points[0].get_Y();
float _minY = this._points[0].get_Y();
for (int counter = 0; counter < this._points.length; counter++)
{
if ( _maxX < this._points[counter].get_X() ) _maxX =
this._points[counter].get_X();
if ( _minX > this._points[counter].get_X() ) _minX =
this._points[counter].get_X();
if ( _minY > this._points[counter].get_Y() ) _minY =
this._points[counter].get_Y();
if ( _maxY < this._points[counter].get_Y() ) _maxY =
this._points[counter].get_Y();
}
float _rateX = 0.0f;
int startX = 0;
if ( (_maxX > 0 ) && (( _minX > 0 ) || (_minX == 0)) )
{
_rateX = ( _maxX ) / ( this.get_Width() );
startX =
System.Convert.ToInt32(((_maxX )/_rateX)-this.get_Width());
}
else
{
System.Windows.Forms.MessageBox.Show("Sorry, not supported.");
Application.Exit();
}
float _rateY = 0.0f;
int startY = 0;
if ( ( _maxY > 0 ) & (( _minY > 0 ) || (_minY == 0)) )
{
_rateY = ( _maxY ) / ( this.get_Height() );
startY = System.Convert.ToInt32(( _maxY ) / _rateY);
}
else
{
System.Windows.Forms.MessageBox.Show("Sorry, not supported.");
Application.Exit();
}
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( startX ),
System.Convert.ToInt32( 0 ),
System.Convert.ToInt32( startX ),
System.Convert.ToInt32( this.get_Height() )
);
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( 0 ),
System.Convert.ToInt32( startY ),
System.Convert.ToInt32( this.get_Width() ),
System.Convert.ToInt32( startY )
);
for (int countThroughAllPoints = 0;
countThroughAllPoints < this._points.length; countThroughAllPoints++)
{
System.Drawing.PointF point = this._points[countThroughAllPoints];
if ( countThroughAllPoints > 0 )
{
System.Drawing.PointF prevPPoint =
this._points[countThroughAllPoints - 1];
int prevXpoint =
System.Convert.ToInt32( prevPPoint.get_X() / _rateX );
int prevYPoint = System.Convert.ToInt32(
this.get_Height()-(prevPPoint.get_Y()/_rateY));
int thisXpoint =
System.Convert.ToInt32( point.get_X() / _rateX );
int thisYpoint =
System.Convert.ToInt32(this.get_Height()-(point.get_Y()/_rateY));
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( thisXpoint ),
System.Convert.ToInt32( startY-5 ),
System.Convert.ToInt32( thisXpoint ),
System.Convert.ToInt32( startY+5 )
);
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( startX-5 ),
System.Convert.ToInt32( thisYpoint ),
System.Convert.ToInt32( startX+5 ),
System.Convert.ToInt32( thisYpoint )
);
g.DrawString( ""+point.get_X(),
this._titleFont,
new System.Drawing.SolidBrush( this._XYLineColor ),
System.Convert.ToInt32(thisXpoint - System.Math.Ceiling(
g.MeasureString( ""+point.get_X(),
this._titleFont).get_Width())),
startY - this._titleFont.get_Height() );
g.DrawString( ""+point.get_Y(),
this._titleFont,
new System.Drawing.SolidBrush( this._XYLineColor ),
startX,
thisYpoint);
g.DrawLine(new System.Drawing.Pen(new
System.Drawing.SolidBrush(this._XYLineColor),2.0f),
prevXpoint,
prevYPoint,
thisXpoint,
thisYpoint
);
}
else
{
int thisXpoint =
System.Convert.ToInt32( point.get_X() / _rateX );
int thisYpoint =
System.Convert.ToInt32(this.get_Height()-point.get_Y()/_rateY);
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( thisXpoint ),
System.Convert.ToInt32( startY-5 ),
System.Convert.ToInt32( thisXpoint ),
System.Convert.ToInt32( startY+5 )
);
g.DrawLine( new System.Drawing.Pen( new
System.Drawing.SolidBrush(this._titleColor), 2.0f),
System.Convert.ToInt32( startX-5 ),
System.Convert.ToInt32( thisYpoint ),
System.Convert.ToInt32( startX+5 ),
System.Convert.ToInt32( thisYpoint )
);
g.DrawString( ""+point.get_X(),
this._titleFont,
new System.Drawing.SolidBrush( this._XYLineColor ),
System.Convert.ToInt32(thisXpoint - System.Math.Ceiling(
g.MeasureString( ""+point.get_X(),
this._titleFont).get_Width())),
startY - this._titleFont.get_Height());
g.DrawString( ""+point.get_Y(),
this._titleFont,
new System.Drawing.SolidBrush( this._XYLineColor ),
startX,
thisYpoint);
}
}
Add the User Control to an Application
Put a Panel
on your application, resize the Panel
to the size of your user control. Add the user control to the Panel
with this code. Please remember to add a reference to the user control DLL file:
GraphUserControl.DrawGraph GraphCtl =
new GraphUserControl.DrawGraph();
GraphCtl.set_Dock( System.Windows.Forms.DockStyle.Fill );
GraphCtl.set_Title( "This is a title" );
GraphCtl.set_TitleFont(new
System.Drawing.Font(System.Drawing.FontFamily.get_GenericSansSerif(),
12.0f));
GraphCtl.set_TitleColor( System.Drawing.Color.get_Blue() );
GraphCtl.set_BackgroundColor(System.Drawing.Color.get_White() );
GraphCtl.set_BackgroundImage(System.Drawing.Image.FromFile(
"..\\..\\background.bmp"));
GraphCtl.set_XYLineColor( System.Drawing.Color.get_BlueViolet() );
System.Drawing.PointF[] pointsF = new System.Drawing.PointF[]
{
new System.Drawing.PointF(1.2f,2.3f),
new System.Drawing.PointF(2.4f,7.5f),
new System.Drawing.PointF(5.1f,5.3f)
};
GraphCtl.set_Points( pointsF );
this.panel1.get_Controls().Add( GraphCtl );
Source Files
The Microsoft Visual Studio 2003 solution is stored in the \GraphUserControl\GraphUserControl file. This will open both the user control and the test application projects.
Conclusion
As we have seen in this article, it is not possible to override the Paint
method in J#. The tricky part is that we have to implement a paint handler in J#.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.