Welcome : In Depth articles : Canvas (Part 1)
Canvas
A three part series
A constant requirement of the Windows Forms programmer is to represent a
document or drawn objecton screen as it would appear on a printer. This is to
say of course in a WYSIWYG fashion.
Often, the constraints of the screen and the need for the user to do detailed
work require that complex zooming and panning of the document is required. To do
this, a Canvas analog is needed but unfortunately, Windows Forms doesn't provide
this facility as standard and rolling your own can be a daunting task.
In this three part series you will discover how to create a control that takes
advantage of the best features of GDI+ to make a canvas style window that can
handle infinite zoom levels and scrolling of a virtual document with ease.
The final product, a Canvas class based on the Scrollable Control is shown in
figure 1. Canvas maintains a page size, may be zoomed to any level large or
small and may be scrolled as you would expect. Furthermore, Canvas backtracks
the mouse input so that the position of the mouse on the screen is translated to
the exact virtual coordinates of the mouse in the page.

Figure 1: The Canvas in action.
Getting started.
The canvas class needs to be able to display a virtual page of any size and be
able to intelligently decide which scrollbars should be displayed and where in
the page the viewport is situated.
The ScrollableControl class in Windows Forms is an ideal basis for this kind of
functionality but it lacks the finesse needed to make it perfect. As a first
step the code in the following listing creates a Windows Forms control which can
be dropped onto a form to create a canvas object.
The first incarnation of the canvas control sets the stage for the next two
articles by creating a custom Windows Forms object that obeys some simple ground
rules to make the users experience, and here I refer to the person using the
tool as well as the end user, a consistent and productive one.
The basic class derives from ScrollableControl and uses the
System, System.ComponentModel, System.Drawing, System.Drawing.Drawing2d and
System.Windows.Forms DLL's. In the constructor the control styles are set to
ensure that the painting style is suitable for double buffering if required.
using
System;
using
System.ComponentModel;
using
System.Drawing;
using
System.Drawing.Drawing2D;
using
System.Windows.Forms;
namespace
WellFormed
{
/// <summary>
/// The canvas class
/// </summary>
public
class Canvas : ScrollableControl
{
/// <summary>
/// Constructor
/// </summary>
public
Canvas()
{
this.SetStyle(
ControlStyles.AllPaintingInWmPaint
|
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw ,true);
}
}
}
The fundamental property of the Canvas control is the page size. This provides
the limits for all zooming, scrolling and editing operations. So that the Canvas
control behaves just like a Microsoft produced item, the PageSize property is
constructed as follows. This design guideline may be used on any property to
create a robust and consistent control.
-
A private field is used to store the basic information
-
An accessor property is provided and is supplied with the appropriate attributes
for design time user feedback
-
If required, an event that signals a property change is provided
-
if the event is provided, a protected Onxxx method is provided to raise that
event.
The PageSize property for the Canvas control is constructed as follows.
private
Size _pageSize=new Size(640,480);
[
Category("Appearance"),
Description("The size of the virtual page")
]
public
Size PageSize
{
get{return
_pageSize;}
set{
_pageSize=value;
OnPageSizeChanged(EventArgs.Empty);
}
}
protected
virtual
void OnPageSizeChanged(EventArgs e)
{
if(PageSizeChanged!=null)
PageSizeChanged(this,e);
}
public
event EventHandler PageSizeChanged;
We will return to the OnPageSizeChanged method shortly.
Initializing the scroll bars.
ScrollableControl enables us to set the minimum scroll size for the page. If the
window becomes smaller than this size, scrollbars will appear and the page may
be scrolled to it's maximum extents.
This is done in the CalcScroll method which will evolve in the next part of this
article.
void
CalcScroll()
{
Size cs =
new Size(this._pageSize.Width,this._pageSize.Height);
this.AutoScrollMinSize=cs;
Invalidate();
}
The scroll sizes need to be recalculated in several places. In this article they
will be calculated for a page size change and for a control size change. In the
next issue, the scroll sizes will be calculated when the zoom level changes.
Revisiting the OnPageSizeChanged method, the CalcScroll method is added to the
code.
protected
virtual void
OnPageSizeChanged(EventArgs e)
{
CalcScroll();
if(PageSizeChanged!=null)
PageSizeChanged(this,e);
}
The base class OnSizeChanged method is also overridden to call CalcSroll.
protected override
void OnSizeChanged(EventArgs e)
{
CalcScroll();
base.OnSizeChanged(e);
}
Painting the control
To make the appearance of the control professional and provide some flexibility
the control needs a basic page colour property and a flag that will specify
whether drawing will be clipped to the page or allowed to overflow the page
boundaries. The two properties follow.
private Color _pageColor=Color.White;
[
Category("Appearance"),
Description("The base
color of the page")
]
public Color PageColor
{
get{return
_pageColor;}
set{
_pageColor=value;
Invalidate();
}
}
private bool
_clipToPage;
[
Category("Behavior"),
Description("Gets or
sets the clipping flag. When true no drawing is allowed outside page
boundaries")
]
public bool
ClipToPage
{
get{return
_clipToPage;}
set{
_clipToPage=value;
Invalidate();
}
}
Drawing of the page must start with drawing of the base page appearance. This
sets the stage for any drawing operations the user should wish to add.
The Paint routine itself follows. An analysis of the method follows the code..
protected
override void
OnPaint(PaintEventArgs e)
{
base.OnPaintBackground(e);
Matrix mx=new Matrix(1,0,0,1,0,0);
Size s=new Size(this.ClientSize.Width,this.ClientSize.Height);
if(s.Width>PageSize.Width)
mx.Translate((s.Width/2)-(_pageSize.Width/2),0);
else
mx.Translate((float)this.AutoScrollPosition.X,0);
if(s.Height>PageSize.Height)
mx.Translate(0,(s.Height/2)-(this._pageSize.Height/2)+(this.AutoScrollPosition.Y));
else
mx.Translate(0,(float)this.AutoScrollPosition.Y);
e.Graphics.Transform=mx;
SolidBrush b=new SolidBrush(Color.FromArgb(64,Color.Black));
e.Graphics.FillRectangle(b,new Rectangle(new
Point(20,20),PageSize));
b.Color=PageColor;
e.Graphics.FillRectangle(b,new Rectangle(new
Point(0,0),PageSize));
if(ClipToPage)
e.Graphics.SetClip(new
Rectangle(0,0,PageSize.Width,PageSize.Height));
base.OnPaint(e);
}
Because the UserPaint style was used, we must explicitly call the
OnPaintBackground method or it will not be cleared.
The paint method sets up a transformation matrix that shifts the origin of the
current Graphics object to the required position.
An identity matrix is created, then the client size is used to determine which,
if any, of the dimensions of the page are larger than the visible client area.
If the width or height of the page is less than the visible client area, the
matrix is translated so that the origin is placed in such a way that the middle
of the page corresponds with the middle of the client area. If the page size is
larger in any dimension, the matrix is transformed by the corresponding
scroll-bar offset.
This matrix is applied to the Graphics object and the page is drawn, first the
shadow, created from an offset rectangle of semi-transparent black, then the
page itself.
If the ClipToPage flag is set, a clipping region is imposed that restricts
further drawing to the visible page boundaries.
Finally, the base OnPaint method and hence the PaintEvent is called giving the
user opportunity to continue painting. At this point, the graphics output may be
sent to the page without worrying about the positions of the scroll bars and
indeed, as you'll see in the next issue, the current zoom level.
Tidying up the control for design time use.
Functionally, the control is complete for this issue. However, a couple of small
items will make it immediately usable and a good experience all round.
This control provides a drawing surface that can be dropped onto a page so it
would be nice if a suitable icon were provided so that it can be recognized in
the toolbox.
Furthermore, the control as it is flickers badly because of the background,
shadow and page drawing so an option to double buffer the page is a must.
To create the icon, a suitable graphic must be added to the project solution.
This simple graphic is a 16 by 16 image. The garish Magenta provides a
transparent background.

Figure 2. The Canvas icon
In the image properties, the Build Action is set to "Embedded Resource"

Figure 3. Embedding the bitmap in the resources
Finally, the class can be adorned with the ToolboxBitmap attribute.
[
ToolboxItem(true),
ToolboxBitmap(typeof(Canvas),"CanvasIcon.bmp")
]
public
class Canvas : ScrollableControl
{
To cure the flicker of the redraw cycles, the standard control styles are used
to provide a double buffered solution. A property is used to set this up.
private
bool _doubleBuffer;
[
Category("Behavior"),
Description("Set true to enable
double buffering")
]
public
bool DoubleBuffer
{
get{return _doubleBuffer;}
set{
_doubleBuffer=value;
if(value)
{
SetStyle(ControlStyles.DoubleBuffer,true);
}
else
{
SetStyle(ControlStyles.DoubleBuffer,false);
}
Invalidate();
}
}
Remember that these control style changes will accumulate with the ones set up
in the constructor.
Testing the control
Once built, the control can be dragged onto any form, it's properties set up and
the events such as Paint handled to provide the first stage of the Canvas
control. Figure 4 shows the Canvas control on the design surface and running.

Figure 4. The Canvas Control (Mk I)
Summary.
In this part, you've seen how to create a graphical control which uses a
transformation matrix to position a virtual page within a visible area and
prepares the drawing surface for subsequent operations.
In the next part of the article you will see how to adapt this control to cope
with infinitely variable zooming which will allow the user to stand back from a
detailed page or get to grips with the individual pixels.
Carry on now to read part 2...
Bob Powell
Create your badge
| |