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 by two hundred pixels:
(send drawing-canvas min-width 200) (send drawing-canvas min-height 200)
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 ninety pixels from the top edge:
(send drawing-context draw-rectangle 50 90 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 (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 drawing 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 drawing 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.
Create a vector named palette, of length 16, in which the
element at position x is a color with red intensity 16x and
green intensity 16(15 - x).
Define and test a procedure that takes two arguments, a device context and
a vector of colors. When invoked, the procedure should first determine and
save the current background color stored in the device context (by sending
it a get-background message). Then it should run through the
colors in the vector, repainting with each color in turn. Finally, it
should repaint with the original background color.
The graphics operations on MathLAN workstations are so fast that a test run
of your procedure will probably appear as nothing more than a quick
flicker. You can slow your procedure down by inserting, in the body of
your kernel, a call to the DrScheme procedure sleep. This
procedure takes a positive real number as argument and causes DrScheme to
suspend its processing for the specified number of seconds.
To draw a foreground figure, such as a rectangle, in a color other than
black, we change the drawing 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 90 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.
Define a pen that draws solid lines, two pixels wide, in dark blue. Use it
to draw a circle in the middle of drawing-canvas. (To draw a
circle, send the draw-ellipse message, giving it the same
value for the width and height of the ellipse.)
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 drawing 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 115 20 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.
What happens if you draw an arc with a transparent-style pen and a solid-style brush?
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 182)
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)
Write a DrScheme program that displays a graphic of an archery target: on a gray background, concentric rings of white (the outermost), black, blue, and red, with a gold disk in the middle. The outer diameters of the rings are, respectively, five, four, three, and two times the diameter of the gold disk. If you like, print the point value of each color at the top of the ring or disk (1 for white, 2 for black, 3 for blue, 4 for red, and 5 for gold).
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/drawing-classes.xhtml
created May 5, 2000
last revised May 7, 2000