
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 hander 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 let 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. 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#.