One of the things that we can place inside a frame, as part of a graphical user interface, is a canvas -- a window on which our programs can draw things. The only initialization argument that a canvas needs is the frame that will contain it.
(define drawing-frame (make-object frame% "Drawing example")) (define drawing-canvas (make-object canvas% drawing-frame))
Initially, let's set up a blank canvas, two hundred fifty by two hundred fifty pixels:
(send drawing-canvas min-width 250) (send drawing-canvas min-height 250)
We can ensure that the size of the canvas will never be changed by setting
its ``stretchability'' fields to #f:
(send drawing-canvas stretchable-width #f) (send drawing-canvas stretchable-height #f)
Although the user can still try to resize the enclosing frame, this will not change the size of a non-stretchable canvas. If the user makes the frame larger, the canvas will occupy only part of it. If the user tries to make the frame smaller than that canvas, the attempt will fail.
At this point, we're ready to make the frame (and therefore the canvas that it contains) visible.
(send drawing-frame show #t)
One of the fields of a canvas contains its device context, an
object that translates messages it receives into low-level operations on
the particular output device where the canvas will be displayed. To create
pictures on the canvas, the program sends such messages to its drawing
canvas. The canvas% class provides a selector message, get-dc, by which the programmer can obtain the canvas's device context,
and since we'll be sending messages to it a lot it will be convenient to
give it a name:
(define drawing-context (send drawing-canvas get-dc))
For example, we might ask the device context to draw a rectangle twenty-five pixels wide and seventy-five pixels high, with its upper left-hand corner fifty pixels from the left edge of the canvas and one hundred forty pixels from the top edge:
(send drawing-context draw-rectangle 50 140 25 75)
By default, lines are drawn in black on a white background, but we can
switch to different colors if we like. Colors, not surprisingly, are
objects of DrScheme's primitive class color%. Their initialization
arguments can be of one of two forms: If we want a color that has a common
name, one that appears in DrScheme's predefined color database, we can use
that name (as a string) as the initialization argument.
(define cerulean (make-object color% "sky blue"))
To create a customized color, however, we have to describe it in terms of the intensities of the red, green, and blue components of light of that color. Each of these intensities is represented by an exact integer in the range from 0 up to, but not including, 256. These intensities are the initialization arguments for customized colors. So, for example, a pure but not very bright red color might have a red intensity of 128 (about half of the maximum), a green intensity of 0, and a blue intensity of 0:
(define dull-red (make-object color% 128 0 0))
A subtler color would combine light of different components:
(define peru (make-object color% 205 133 63))
To change the canvas background to some particular color, we send the device
context a set-background message with the color as an argument:
(send drawing-context set-background cerulean)
The device context does not immediately repaint the background when it
receives this message, but subsequent operations that use the background
color will use the new one. For example, the clear message
directs the device context to repaint the entire canvas using the current
background color, so if we now execute
(send drawing-context clear)
we get a sky-blue canvas.
To draw a foreground figure, such as a rectangle, in a color other than
black, we change the device context's pen. Pens are objects.
When you create a pen, you specify its color, the width (in pixels) of the
line it draws, and its ``style,'' which is a symbol indicating the
structure of the line it draws. Most pens use the solid style,
which draws a continuous line. (On rare occasions, you might want to use
another style, such as short-dash, which draws a dashed line.)
(define peru-drawing-pen (make-object pen% peru 4 'solid))
Let's ask the device context to use this pen instead of its original one, then redraw the rectangle:
(send drawing-context set-pen peru-drawing-pen) (send drawing-context draw-rectangle 50 140 25 75)
A device context accepts messages for drawing points, straight lines, circular and elliptical arcs, polygons, rectangles with rounded corners, and splines. (A spline is a smooth curve from one given point to another that passes through a third given point, the spline's control point.) You can read about all of these in the MrEd graphical toolbox manual, starting at the page dealing with device contexts.
A pen just draws the outline of a figure. If you want to fill in the
interior of an ellipse or a polygon, you need a brush. A brush
has a color and a style, like a pen, but since it is used to fill in areas
rather than to draw lines it has no width field. A device context always
has a ``current brush'' as well as a ``current pen,'' but we haven't seen
the effects of the brush yet because the default brush uses a special
style, transparent, that has no effect on the appearance of the
canvas.
Let's create a brush, tell the device context to use it, and draw an ellipse:
(define red-drawing-brush (make-object brush% dull-red 'solid)) (send drawing-context set-brush red-drawing-brush) (send drawing-context draw-ellipse 135 30 70 50)
You can turn ``filling'' on and off either by sending the device context
set-brush messages, enclosing a solid-style brush to turn filling on
and a transparent-style one to turn it off, or by sending the current brush
set-style messages, enclosing the symbol solid to turn the
filling on and transparent to turn it off.
A transparent style is also provided for pens, so that the programmer can control whether the enclosing boundary of a figure will be drawn in the same kind of way.
It is possible to write text onto a canvas -- for instance, to label some
component of the graphic -- by sending a draw-text message to
the canvas's device context, specifying the string to be written, the
distance in pixels from the left edge of the canvas to the beginning of the
text, and the distance in pixels from the top edge of the canvas to the
top of the text.
(send drawing-context draw-text "Hello, world!" 8 232)
By default, the text is drawn in black, but you can send a set-text-foreground message to the device context to change the color. It
is also possible to vary the type font, by sending the set-font
message with a font argument. Fonts, too, are objects, requiring four
initialization arguments: size (an exact integer in the range from 1 up to,
but not including, 256), family (one of a limited range of symbols,
including decorative, roman, script, swiss,
and modern), style (normal or italic), and weight
(normal, bold, or light).
For instance, here's how to create a dull-red 24-point pseudo-Helvetica bold font, install it in the device context, and print a monogram in the upper left corner of the canvas:
(define monogram-font (make-object font% 24 'swiss 'normal 'bold)) (send drawing-context set-font monogram-font) (send drawing-context set-text-foreground dull-red) (send drawing-context draw-text "JDS" 8 8)