|
|
|
Select your preferred language |
So, you've seen how to create the beginnings of a canvas
class that added panning, zooming and selection capabilities that aren't built
in to the Windows Forms system as standard. Now, you'll see how to add the
capability to zoom in or out of a document. Previously, you saw
how the transform applied to the Graphics used to draw the document was used to
both pan the document in the case that the viewport was smaller than the page
or, if the page was smaller than the viewing area, to offset the origin of the
page to make a neat WYSIWYG representation of the document as a whole.
Figure 1 shows how the origin is offset for the two different
cases. Example A shows the origin offset by the amount dictated by the scroll
bars that supply a negative value which moves the page left or up to enable you
to see a different portion of the document. Example B shows the origin moved to
a position that lines up the center of the document with the center of the
viewing area.
Figure 1: Translating the origin of the document.
Building on the principle of using transformations to offset
the origin, lets look now at the ways a transform can be used to affect the
apparent size of objects on screen.
Scaling the transform
The matrix class provided by System.Drawing enables you to
perform transformations on coordinates. This means that you can subject any x,y
value such as the position of an output pixel or the contents of a point to the
transformation and the resulting position used to provide a new coordinate. When
we zoom into an image, we effectively expand or multiply the coordinates pushing
them further apart. Zooming out is performed by dividing the coordinate
positions by some factor, thereby pushing them together on the screen. The code
shown in listing 1 demonstrates how setting an explicit transform can change the
size and position of the output pixels.
private void
DrawRect(Graphics g, Color c)
{
SolidBrush
sb=new SolidBrush(c);
g.FillRectangle(sb,10,10,100,100);
sb.Dispose();
}
private
void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
Matrix
mx=new Matrix(2f,0,0,2f,0,0);
e.Graphics.Transform=mx;
DrawRect(e.Graphics,Color.Red);
mx=new
Matrix(1f,0,0,1f,0,0);
e.Graphics.Transform=mx;
DrawRect(e.Graphics,Color.Green);
mx=new
Matrix(0.5f,0,0,0.5f,0,0);
e.Graphics.Transform=mx;
DrawRect(e.Graphics,Color.Blue);
}
Private Sub
DrawRect(ByVal g As
Graphics, ByVal c As
Color)
Dim sb As
New SolidBrush(c)
g.FillRectangle(sb, 10, 10, 100, 100)
sb.Dispose()
End Sub
Private Sub
Form1_Paint(ByVal sender
As Object,
ByVal e As
System.Windows.Forms.PaintEventArgs) Handles
MyBase.Paint
Dim mx As
New Matrix(2.0F, 0, 0, 2.0F, 0, 0)
e.Graphics.Transform = mx
DrawRect(e.Graphics, Color.Red)
mx = New Matrix(1.0F, 0, 0, 1.0F, 0, 0)
e.Graphics.Transform = mx
DrawRect(e.Graphics, Color.Green)
mx = New Matrix(0.5F, 0, 0, 0.5F, 0, 0)
e.Graphics.Transform = mx
DrawRect(e.Graphics, Color.Blue)
End Sub
Figure 2 shows the output from this code. You can see that
regardless of the fact that the rectangle was drawn in exactly the same position
and size, the pixels actually output were moved and scaled with respect to the
origin.

Figure 2: Scaled rectangles.
To set the zoom level of the canvas class then, we just need
to provide some method of setting the multiplying factor for the coordinates and
apply them before we draw the page. Unfortunately though, the equation isn't as
simple as that because the system also needs to cope with scrolling of the page,
especially now that the apparent size of the page could be many times that of
the original. For example, a 1024 by 768 document viewed at 200 percent will
require 2048 by 1536 to display it. At 200 percent then, the scroll bars, that
know nothing of the transform applied to the Graphics object, need to be able to
range over the apparent virtual size of the page. As the zoom changes then so
should the minimum scroll size of the page and the transform that keeps the
origin in position relative to scrollbars or viewport.
As well as adding the property that sets the zoom level, the
method that calculates scroll sizes and the paint routine will require extensive
changes. As before, to provide the maximum possibilities for the user, we should
also ensure that the control behaves well at design time and can inform the
application when things change so an event should be added which fires when the
zoom level changes.
Taking this laundry list one item at a time, the code is
modified as follows.
Adding the zoom property.
A private field and public accessor are added:
private float
_zoom=1.0f;
[Category("Behavior"),
Description("The
multiplying factor for the zoom level"),
DefaultValue(1.0f)]
public
float Zoom
{
get{return
_zoom;}
set{
_zoom
= value;
//A
zoom may not be negative
if(_zoom<0)
_zoom=Math.Abs(_zoom);
//a
zoom may be very small but never zero.
if(_zoom==0)
_zoom=0.000001f;
//The
scrollbars should be recalculated
CalcScroll();
//and
the host application code informed if needs be
OnZoomChanged(EventArgs.Empty);
}
}
Private _zoom As
Single = 1.0F
<Category("Behavior"), _
Description("The multiplying factor for the zoom level"), _
DefaultValue(1.0F)> _
Public Property
Zoom() As Single
Get
Return _zoom
End Get
Set(ByVal Value
As Single)
_zoom = Value
'A zoom may not be negative
If _zoom < 0 Then
_zoom = Math.Abs(_zoom)
End If
'a zoom may be very small but never zero.
If _zoom = 0 Then
_zoom = 0.000001F
End If
'The scrollbars should be recalculated
CalcScroll()
'and the host application code informed if needs be
OnZoomChanged(EventArgs.Empty)
End Set
End Property
Note how the zoom level is always kept positive and never
allowed to be zero.
Scrollbar calculations are modified.
You may remember that originally, the CalcScroll method looked like this;
void CalcScroll()
{
Size cs
= new Size(this._pageSize.Width,this._pageSize.Height);
this.AutoScrollMinSize=cs;
Invalidate();
}
Protected Sub CalcScroll()
Dim cs As
New Size(Me._pageSize.Width,
Me._pageSize.Height)
Me.AutoScrollMinSize = cs
Invalidate()
End
Sub
To cope with the zoom factor, the page-size in both
dimensions must be multiplied by the zoom factor. The updated listing below
shows this.
void CalcScroll()
{
Size cs
= new Size((int)(this._pageSize.Width*_zoom),(int)(this._pageSize.Height*_zoom));
this.AutoScrollMinSize=cs;
Invalidate();
}
Adding the property change event.
public event
EventHandler ZoomChanged;
protected
virtual void
OnZoomChanged(EventArgs e)
{
if(this.ZoomChanged!=null)
ZoomChanged(this,e);
Invalidate();
}
Public Event
ZoomChanged As EventHandler
Protected Sub
OnZoomChanged(ByVal e
As EventArgs)
RaiseEvent ZoomChanged(Me,
EventArgs.Empty)
End Sub
Modifying the OnPaint method.
This is the method in which the most extensive changes have to be made. This
method sets up the graphics transform so that all subsequent drawing operations
can be performed as though they were at a one to one ratio but the display shows
us the correct zoom and pan.
Beginning with the basic matrix settings, this time, instead of starting out
with the identity matrix as was used in the previous page, the zoom factor is used to
symmetrically scale the output;
Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);
Dim mx As
Matrix = New Matrix(_zoom, 0, 0, _zoom, 0, 0)
The size of the page, used to calculate which if any of the scrollbars should
be seen and their offsets must be modified to take the zoom level into account.
For this adjustment, the reciprocal of the zoom is used.
Size s=
new Size((int)(this.ClientSize.Width*(1f/_zoom)),(int)(this.ClientSize.Height*(1f/_zoom)));
Dim s as Size=new Size(2">.ClientSize.Width*(1f/_zoom)),(int)(this.ClientSize.Height*(1f/_zoom)));
The Size structure 's' now contains the apparent virtual page size which can
be used to move the origin of the page into the correct position, taking into
account the scrollbar offsets if any.
if(s.Width>PageSize.Width)
mx.Translate((s.Width/2)-(_pageSize.Width/2),0);
else
mx.Translate((float)this.AutoScrollPosition.X*(1f/_zoom),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*(1f/_zoom));
If s.Width > PageSize.Width
Then
mx.Translate((s.Width / 2) - (_pageSize.Width / 2), 0)
Else
mx.Translate(CSng(Me.AutoScrollPosition.X
* (1.0F / _zoom)), 0)
End If
If (s.Height > PageSize.Height)
Then
mx.Translate(0, (s.Height / 2) - (Me._pageSize.Height
/ 2) + (Me.AutoScrollPosition.Y))
Else
mx.Translate(0, CSng(Me.AutoScrollPosition.Y
* (1.0F / _zoom)))
End If
The Matrix mx now contains the transformation used to draw
the rest of the canvas UI and the graphics which comprise the document.
Testing the Canvas control.
The added dimension of zooming requires a zoom selection so to test the
Canvas control this time, a TrackBar control has been added to the main display
that enables you to zoom from a 10,000 foot view to around 250 percent of full
size.
Zooming is handled very simply. The trackbar is set for a minimum of zero and
a maximum of 100 and the Scroll event is handled by the code shown below.
private void
trackBar1_Scroll(object sender, System.EventArgs
e)
{
this.canvas1.Zoom=0.025f*this.trackBar1.Value;
}
Private Sub
TrackBar1_Scroll(ByVal sender
As System.Object, ByVal
e As System.EventArgs)
Handles TrackBar1.Scroll
Me.Canvas1.Zoom = 0.025F *
Me.TrackBar1.Value
End Sub
Figure 3 shows the
Canvas control at work in the test program.

Figure 3: infinitely scalable zooming with the Canvas control.
Summary
So now you've
seen how to apply a transformation to scale the output of the page and how that
scaling modifies the behaviour of the scrollbars. Futhermore, you've seen how
the virtual page relates to the actual page. This will be important for the
final installment, adding mouse handling to the Canvas control that relates the
mouse cursor to the document regardless of zoom or scroll position.
CS Version
VB Version
Return to page 1 or Proceed to the next page