Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Macintosh Toolbox Essentials /
Chapter 5 - Control Manager / Using the Control Manager


Scrolling Through a Document

Earlier sections of this chapter explain how to create scroll bars, determine when a mouse-down event occurs in a scroll bar, track user actions in a scroll bar, and determine and change scroll bar settings. This section discusses how your application actually scrolls through documents in response to users' mouse activity in the scroll bars. For example, your application scrolls toward the bottom of the document under the following conditions:

As a first step, your application must determine the distance by which to scroll. When the user drags a scroll box to a new location on the scroll bar, you scroll a corresponding distance to a new location in the document.

When the user clicks a scroll arrow, your application determines an appropriate amount to scroll. In general, a word processor scrolls vertically by one line of text and horizon- tally by the average character width, and a database or spreadsheet scrolls by one field. Graphics applications should scroll to display an entire object when possible. (Typically, applications convert these distances to pixels when using Control Manager, QuickDraw, and TextEdit routines.)

When the user clicks a gray area of a scroll bar, your application should scroll by a distance of just less than the height or width of the window. To determine this height and width, you can use the contrlOwner field of the scroll bar's control record. This field contains a pointer to the window record. When you scroll by a distance of one window, it is best to retain part of the previous window. This retained portion helps the user place the material in context. For example, if the user scrolls down by a distance of one window in a text document, the line at the top of the window should be the one that previously appeared at the bottom of the window.

The scrolling direction is determined by whether the scrolling distance is expressed as a positive or negative number. When the user scrolls down or to the right, the scrolling distance is a negative number; when the user scrolls up or to the left, the scrolling distance is a positive number. For example, when the user scrolls from the beginning of a document to a line located 200 pixels down, the scrolling distance is -200 pixels on the vertical scroll bar. When the user scrolls from there back to the start of the document, the scrolling distance is 200 pixels.

Determining the scrolling distance is only the first step. In brief, your application should take the following steps to scroll through a document in response to the user's manipulation of a scroll bar.

  1. Use the FindControl, GetControlValue, and TrackControl functions to help calculate the scrolling distance.
  2. If you are scrolling for any reason other than the user dragging the scroll box, use the SetControlValue procedure to move the scroll box a corresponding amount.
  3. Use a routine--such as the QuickDraw procedure ScrollRect or the TextEdit procedure TEPinScroll--to move the bits displayed in the window by the calculated scrolling distance. Then either use a call that generates an update event
    or else directly call your application's DoUpdate routine, which should perform
    the rest of these steps.
  4. Use the UpdateControls procedure to update the scroll bars and then call the Window Manager procedure DrawGrowIcon to redraw the size box.
  5. Use the QuickDraw procedure SetOrigin to change the window origin by an amount equal to the scroll bar settings so that the upper-left corner of the document lies at (0,0) in the window's local coordinate system. (You perform this step so that your application's document-drawing routines can draw in the correct area of the window.)
  6. Call your application's routines for redrawing the document inside the window.
  7. Use the SetOrigin procedure to reset the window origin to (0,0) so that future Window Manager and Control Manager routines draw in the correct area of the window.
  8. Return to your event loop.

These steps are explained in greater detail in the rest of this section.

Note
It is not necessary to use SetOrigin as described in the rest of this chapter. This procedure merely helps you to offset the window origin
by the scroll bars' current settings when you update the window, so
that you can locate objects in a document using a coordinate system where the upper-left corner of the document is always at (0,0). As an alternative to this approach, your application can leave the upper-left corner of the window (called the window origin) located at (0,0) and instead offset the items in your document by an amount equal to the scroll bars' settings. The QuickDraw procedures OffsetRect, OffsetRgn, SubPt, and AddPt, which are described in Inside Macintosh: Imaging, are useful if you pursue this alternate approach.
When the user saves a document, your application should store the data in your own application-defined data structures. (For example, the sample code in this chapter
stores a handle to a TextEdit edit record in a document record. The edit record contains information about the text, such as it length and its own local coordinate system, and
a handle to the text itself.) You typically store information about the objects your application displays onscreen by using coordinates local to the document, where the upper-left corner of the document is located at (0,0).

The left side of Figure 5-16 on the next page illustrates a case in which the user has just opened an existing document, and the SurfWriter sample application displays the top of the document. In this example, the document consists of 35 lines of monostyled text, and the line height throughout is 10 pixels. Therefore, the document is 350 pixels long. When the user first opens the document, the window origin is identical to the upper-left point of the document's space: both are at (0,0).

In this example, the window displays 15 lines of text, which amount to 150 pixels. Hence, the maximum setting for the scroll bar is 200 because the vertical scroll bar's maximum setting is the length of the document minus the height of its window.

Imagine that the user drags the scroll box halfway down the vertical scroll bar. Because the user wishes to scroll down, the SurfWriter application must move the text of the document up so that more of the bottom of the document shows. Moving a document up in response to a user request to scroll down requires a scrolling distance with a negative value. (Likewise, moving a document down in response to a user request to scroll up requires a scrolling distance with a positive value.)

Using FindControl, TrackControl, and GetControlValue, the SurfWriter application determines that it must move the document up by 100 pixels--that is,
by a scrolling distance of -100 pixels. (Using FindControl, TrackControl, and GetControlValue to determine the scrolling distance is explained in detail in "Scrolling in Response to Events in the Scroll Box" beginning on page 5-48.)

Figure 5-16 Moving a document relative to its window

The SurfWriter application then uses the QuickDraw procedure ScrollRect to shift
the bits displayed in the window by a distance of -100 pixels. The ScrollRect procedure moves the document upward by 100 pixels (that is, by 10 lines); 5 lines from the bottom of the previous window display now appear at the top of the window,
and the SurfWriter application adds the rest of the window to an update region for
later updating.

The ScrollRect procedure doesn't change the coordinate system of the window; instead it moves the bits in the window to new coordinates that are still in the window's local coordinate system. For purposes of updating the window, you can think of this
as changing the coordinates of the entire document, as illustrated in the right side of Figure 5-16.

The ScrollRect procedure takes four parameters: a rectangle to scroll, a horizontal distance to scroll, a vertical distance to scroll, and a region handle. Typically, when specifying the rectangle to scroll, your application passes a value representing the content region minus the scroll bar regions, as shown in Listing 5-17.

Listing 5-17 Using ScrollRect to scroll the bits displayed in the window

PROCEDURE DoGraphicsScroll (window: WindowPtr;
                            hDistance, vDistance: Integer);
VAR
   myScrollRect: Rect;
   updateRegion: RgnHandle;
BEGIN
   {initially, use the window's portRect as the rectangle to scroll}
   myScrollRect := window^.portRect;
   {subtract vertical and horizontal scroll bars from rectangle}
   myScrollRect.right := myScrollRect.right - 15;
   myScrollRect.bottom := myScrollRect.bottom - 15;
   updateRegion := NewRgn;    {always initialize the update region}
   ScrollRect(myScrollRect, hDistance, vDistance, updateRegion);
   InvalRgn(updateRegion);
   DisposeRgn(updateRegion);
END; {of DoGraphicsScroll}
IMPORTANT
You must first pass a horizontal distance as a parameter to ScrollRect and then pass a vertical distance. Notice that when you specify a point in the QuickDraw coordinate system, the opposite is true: you name the vertical coordinate first and the horizontal coordinate second.
Although each scroll bar is 16 pixels along its shorter dimension, the DoGraphicsScroll procedure shown in Listing 5-17 subtracts only 15 pixels
because the edge of the scroll bar overlaps the edge of the window frame, leaving
only 15 pixels of the scroll bar in the content region of the window.

The bits that ScrollRect shifts outside of the rectangle specified by myScrollRect are not drawn on the screen, and they are not saved--it is your application's responsibility to keep track of this data.

The ScrollRect procedure shifts the bits a distance of hDistance pixels horizontally and vDistance pixels vertically; when DoGraphicsScroll passes positive values in these parameters, ScrollRect shifts the bits in the myScrollRect parameter to the right and down, respectively. This is appropriate when the user intends to scroll left or up, because when the SurfWriter application finishes updating the window, the user sees more of the left and top of the document, respectively. (Remember: to scroll up or left, move the document down or right, both of which are in the positive direction.)

When DoGraphicsScroll passes negative values in these parameters, ScrollRect shifts the bits in the myScrollRect parameter to the left or up. This is appropriate when the user intends to scroll right or down, because when the SurfWriter application finishes updating the window, the user sees more of the right and the bottom of the document. (Remember: to scroll down or right, move the document up or left, both of which are in the negative direction.)

In Figure 5-16, the SurfWriter application determines a vertical scrolling distance of -100, which it passes in the vDistance parameter as shown here:

ScrollRect(myScrollRect, 0, -100, updateRegion);
If, however, the user were to move the scroll box back to the beginning of the document at this point, the SurfWriter application would determine that it has a distance of
100 pixels to scroll up, and it would therefore pass a positive value of 100 in the vDistance parameter.

After using ScrollRect to move the bits that already exist in the window, the SurfWriter application should draw the bits in the update region of the window by using its standard window-updating code.

As previously explained, ScrollRect in effect changes the coordinates of the document relative to the local coordinates of the window. In terms of the window's local coordinate system, the upper-left corner of the document is now at (-100, 0), as shown on the right side of Figure 5-16. To facilitate updating the window, the SurfWriter application uses the QuickDraw procedure SetOrigin to change the local coordinate system of the window so that the SurfWriter application can treat the upper-left corner of the document as again lying at (0,0).

The SetOrigin procedure takes two parameters: the first is a new horizontal coordinate for the window origin, and the second is a new vertical coordinate for the window origin.

IMPORTANT
Like ScrollRect, SetOrigin requires you to pass a horizontal coordinate and then a vertical coordinate. Notice that when you
specify a point in the QuickDraw coordinate system, the opposite
is true: you name the vertical coordinate first and the horizontal coordinate second.
Any time you are ready to update a window (such as after scrolling it), you can use GetControlValue to determine the current setting of the horizontal scroll bar and
pass this value as the new horizontal coordinate for the window origin. Then use GetControlValue to determine the current setting of the vertical scroll bar and pass this value as the new vertical coordinate for the window origin. Using SetOrigin in this fashion shifts the window's local coordinate system so that the upper-left corner of the document is always at (0,0) when you redraw the document within its window.

For example, after the user manipulates the vertical scroll bar to move (either up or down) to a location 100 pixels from the top of the document, the SurfWriter application makes the following call:

SetOrigin(0, 100);
Although the scrolling distance was -100, which is relative, the current setting for the scroll bar is now at 100. (Because you specify a point in the QuickDraw coordinate system by its vertical coordinate first and then its horizontal coordinate, the order of parameters to SetOrigin may be initially confusing.)

The left side of Figure 5-17 shows how the SurfWriter application uses the SetOrigin procedure to move the window origin to the point (100,0) so that the upper-left corner of the document is now at (0,0) in the window's local coordinate system. This restores the document's original coordinate space and makes it easier for the application to draw in the update region of the window.

Figure 5-17 Updating the contents of a scrolled window

After restoring the document's original coordinates, the SurfWriter application updates the window, as shown on right side of Figure 5-17. The application draws lines 16 through 24, which it stores in its document record as beginning at (160,0) and ending
at (250,0).

To review what has happened up to this point: the user has dragged the scroll box one-half of the distance down the vertical scroll bar; the SurfWriter application determines that this distance amounts to a scroll distance of -100 pixels; the SurfWriter application passes this distance to ScrollRect, which shifts the bits in the window
100 pixels upward and creates an update region for the rest of the window; the SurfWriter application passes the vertical scroll bar's current setting (100 pixels) in a parameter to SetOrigin so that the document's local coordinates are used when the update region of the window is redrawn; and, finally, the SurfWriter application draws the text in the update region of the window.

However, the window origin cannot be left at (100,0); instead, the SurfWriter application must use SetOrigin to reset it to (0,0) after performing its own drawing, because the Window and Control Managers always assume the window's upper-left point is at (0,0) when they draw in a window. Figure 5-18 shows how the application uses SetOrigin to set the window origin back to (0,0) at the conclusion of its window-updating routine. After the update, the application begins processing events in its event loop again.

Figure 5-18 Restoring the window origin to (0,0)

The left side of Figure 5-19 illustrates what happens when the user scrolls all the way
to the end of the document--a distance of another 10 lines, or 100 pixels. After the SurfWriter application calls ScrollRect, the bottom 5 lines from the previous window display appear at the top of the new window and the bottom of the window becomes
a new update region. Because the user has scrolled a total distance of 200 pixels, the application uses SetOrigin to change the window origin to (200,0), as shown on the right side of Figure 5-19.

The left side of Figure 5-20 shows the SurfWriter application drawing in the update region of the window; the right side of the figure shows the SurfWriter application restoring the window origin to (0,0).

Figure 5-19 Scrolling to the end of a document

Figure 5-20 Updating a window's contents and returning the window origin to (0,0)

How your application determines a scrolling distance and how it then moves the bits in the window by this distance are explained in greater detail in the next two sections, "Scrolling in Response to Events in the Scroll Box" and "Scrolling in Response to Events in Scroll Arrows and Gray Areas." "Drawing a Scrolled Document Inside a Window," which follows these two sections, describes what your application should do in its window-updating code to draw in a window that has been scrolled. You can find more detailed information about the SetOrigin and ScrollRect procedures in Inside Macintosh: Imaging.

So far, this discussion has assumed that you are scrolling in response to the user's manipulation of a scroll bar. Most of the time, the user decides when and where to scroll. However, in addition to user manipulation of scroll bars, there are four cases in which your application must scroll through the document. Your application design must take these cases into account.

When designing the document-scrolling routines for your application, also try to keep the following user interface guidelines in mind:

Scrolling in Response to Events in the Scroll Box

"Responding to Mouse Events in a Control" beginning on page 5-27 describes in
general how to use FindControl and TrackControl in your event-handling code. Listing 5-18 shows how to use these routines to respond in particular to mouse events
in a scroll bar.

Listing 5-18 Responding to mouse events in a scroll bar

PROCEDURE DoContentClick (window: WindowPtr; event: EventRecord);
VAR
   mouse:               Point;
   control:             ControlHandle;
   part:                Integer;
   myData:              MyDocRecHnd;
   oldSetting:          Integer;
   scrollDistance:      Integer;
   windowType:          Integer;
BEGIN
   windowType := MyGetWindowType(window);
   CASE windowType OF
      kMyDocWindow:
         BEGIN
            myData := MyDocRecHnd(GetWRefCon(window));
            HLock(Handle(myData));
            mouse := event.where;
            GlobalToLocal(mouse);   {convert to local coordinates}
            part := FindControl(mouse, window, control);
            CASE part OF
               {handle all other parts first; handle scroll bar parts last}
            inThumb:    {mouse-down in scroll box}
               BEGIN    {get scroll bar setting}
                  oldSetting := GetControlValue(control);
                  {let user drag scroll box around}
                  part := TrackControl(control, mouse, NIL);
                  {until user releases mouse button}
                  IF part = inThumb THEN
                  BEGIN                {get new distance to scroll}
                     scrollDistance := oldSetting - GetControlValue(control);
                     IF scrollDistance <> 0 THEN
                        IF control = myData^^.vScrollBar THEN
                           TEPinScroll(0, scrollDistance * 
                                       myData^^.editRec^^.lineHeight,
                                       myData^^.editRec);
                        ELSE
                           TEPinScroll(scrollDistance, 0, myData^^.editRec);
                  END; {of handling mouse-up in scroll box}
               END; {of handling mouse-down in scroll box}
            inUpButton, inDownButton, inPageUp, in PageDown:
            {mouse-down in scroll arrows or gray areas}
               IF control = myData^^.vScrollBar THEN  
                        {handle vertical scroll}
                  part := TrackControl(control, mouse, @MyVerticalActionProc)
               ELSE     {handle horizontal scroll}
                  part := TrackControl(control, mouse, @MyHorzntlActionProc);
            OTHERWISE   ;
            END; {of CASE part}
            HUnLock(Handle(myData));
         END; {of kMyDocWindowType}
         {handle other window types here}
   END; {of CASE windowType}
END;
When the user presses the mouse button while the cursor is in a visible, active scroll box, FindControl returns as its result the part code for a scroll box. That part code and the constant you can use to represent it are listed here:
ConstantPart codeControl part
inThumb129Scroll box

As shown in Listing 5-18, when FindControl returns the value for inThumb, your application should immediately call GetControlValue to determine the current setting of the scroll bar. If the user drags the scroll box, you subtract from this setting the new current setting that becomes available when the user releases the mouse button, and you use this result for your scrolling distance.

After using GetControlValue to determine the current setting of the scroll bar, use TrackControl to follow the movements of the cursor inside the scroll box and to drag a dotted outline of the scroll box in response to the user's movements.

When the user releases the mouse button, TrackControl returns inThumb if the cursor is still in the scroll box or 0 if the cursor is outside the scroll box. When TrackControl returns 0, your application does nothing. Otherwise, your application again uses GetControlValue to calculate the distance to scroll.

Calculate the distance to scroll by calling GetControlValue and subtracting the new current setting of the scroll bar from its previous setting, which you determine by calling GetControlValue before the user releases the mouse button. If this distance is not 0, you should move the bits in the window by this distance and update the contents of the rest of the window.

Before scrolling, you must determine if the scroll bar is a vertical scroll bar or a horizontal scroll bar. As previously explained in this chapter, you should store handles to your scroll bars in a document record, one of which you create for every document. By
comparing the field containing the vertical scroll bar handle, you can determine whether the control handle returned by FindControl is the handle to the vertical scroll bar. If so, the user has moved the scroll box of the vertical scroll box. If not, the user has moved the scroll box of the horizontal scroll bar.

After determining which scroll bar contains the scroll box that the user has dragged, you move the document contents of the window by the appropriate scrolling distance. That is, for a positive scrolling distance in the vertical scroll bar, move the bits in the window down by that distance. When you update the window, this displays more lines from the top of the document--which is appropriate when the user moves the scroll box up. For a positive scrolling distance in the horizontal scroll bar, move the bits in the window to the right by that distance. When you update the window, this displays more lines from the left side of the document--which is appropriate when the user moves the scroll box to the left. (Remember: to scroll up or left, move the document down or right, both of which are in the positive direction.)

For a negative scrolling distance in the vertical scroll bar (such as that shown in
Figure 5-16 on page 5-42), move the bits in the window up by that distance. When you update the window, this displays more lines from the bottom of the document--which
is appropriate when the user moves the scroll box down. For a negative scrolling distance in the horizontal scroll bar, move the bits in the window to the left by that distance. When you update the window, this displays more lines from the right side of the document--which is appropriate when the user moves the scroll box to the right. (Remember: to scroll down or right, move the document up or left, both of which are in the negative direction.)

The previous examples in this chapter have shown an application that uses a TextEdit edit record to store monostyled text created by the user. For simple text-handling
needs, TextEdit provides many routines that simplify your work; for example, the TEPinScroll procedure scrolls through the text in the view rectangle of an edit record by the number of pixels specified by your application; TEPinScroll stops scrolling when the last line scrolls into the view rectangle.

The TEPinScroll procedure takes three parameters: the number of pixels to move the text horizontally, the number of pixels to move the text vertically, and a handle to an edit record. Positive values in the first two parameters move the text right and down, respectively, and negative values move the text left and up.

The DoContentClick procedure, illustrated in Listing 5-18 on page 5-48, passes the scrolling distance in the second parameter of TEPinScroll for a vertical scroll bar, and it passes the scrolling distance in the first parameter for a horizontal scroll bar.

Listing 5-16 on page 5-38 shows an application-defined routine, MyAdjustHV, called by the SurfWriter sample application whenever it creates, opens, or resizes a window. This routine defines the current and maximum settings for a vertical scroll bar in terms of lines of text.

The DoContentClick procedure on page 5-48 uses GetControlValue to determine the control's current setting--which for the vertical scroll bar DoContentClick calculates as some number of lines. When determining the vertical scroll bar's scrolling distance, DoContentClick again calculates a value representing some number of lines.

However, TEPinScroll expects pixels, not lines, to be passed in its parameters. Therefore, DoContentClick multiplies the scrolling distance (which it calculates as some number of lines of text) by the line height (which is maintained in the edit record for monostyled text as some number of pixels). In this way, DoContentClick passes a scrolling distance--in terms of pixels--to TEPinScroll, as shown in this code fragment.

IF control = myData^^.vScrollBar THEN
   TEPinScroll(0, scrollDistance * myData^^.editRec^^.lineHeight,
               myData^^.editRec);
Figure 5-16 on page 5-42 illustrates a scrolling distance of -10 lines. If the line height
is 10 pixels, the SurfWriter application passes -100 as the second parameter to TEPinScroll.

The TEPinScroll procedure adds the scrolled-away area to the update region and generates an update event so that the text in the edit record's view rectangle can be updated. In its code that handles update events for windows, the SurfWriter sample application then uses the TEUpdate procedure--as described in "Drawing a Scrolled Document Inside a Window" beginning on page 5-56-- for its windows that include TextEdit edit records.

To learn more about TEPinScroll, the TextEdit edit record, and other facilities offered by TextEdit, see Inside Macintosh: Text.

The QuickDraw procedure ScrollRect is a more general-purpose routine for moving bits in a window when scrolling. If you use ScrollRect to scroll the bits displayed
in the window, you should define a routine like DoGraphicsScroll, shown in
Listing 5-17 on page 5-43, and use it instead of TEPinScroll, which is used in
Listing 5-18 on page 5-48.

The ScrollRect procedure returns in the updateRegion parameter the area that needs to be updated. The DoGraphicsScroll procedure shown in Listing 5-17 on page 5-43 then uses the QuickDraw procedure InvalRgn to add this area to the update region, forcing an update event. In your code for handling update events, you draw in the area of the window from which ScrollRect has moved the bits, as described in "Drawing a Scrolled Document Inside a Window" beginning on page 5-56.

When a mouse-down event occurs in the scroll arrows or gray areas of the vertical
scroll bar, the DoContentClick routine in Listing 5-18 on page 5-48 calls TrackControl and passes it a pointer to an application-defined action procedure
called MyVerticalActionProc. For the horizontal scroll bar, DoContentClick
calls TrackControl and passes it a pointer to an action procedure called MyHorzntlActionProc. These action procedures are described in the next section.

Scrolling in Response to Events in Scroll Arrows and Gray Areas

With each click in a scroll arrow, your application should scroll by a distance of one
unit (that is, by a single line, character, cell, or whatever your application deems appropriate) in the chosen direction. When the user holds the mouse button down
while the cursor is in a scroll arrow, your application should scroll continuously by single units until the user releases the mouse button or until your application has scrolled as far as possible in the document.

With each click in a gray area, your application should scroll in the appropriate direction by a distance of just less than the height or width of one window to show part of the previous window (thus placing the newly displayed material in context). When the user holds the mouse button down while the cursor is in a gray area, your application should scroll continuously in units of this distance until the user releases the mouse button or until your application has scrolled as far as possible in the document.

When your application finishes scrolling, it should use SetControlValue to move the scroll box accordingly.

As previously described in this chapter, you use FindControl to determine when a mouse-down event has occurred in a control in one of your windows, and you use TrackControl to follow the movements of the cursor inside the control, to give the user visual feedback, and then to inform your application when the user releases the mouse button.

When a mouse-down event occurs in the scroll arrows or the gray areas of an active scroll bar, FindControl returns as its result the appropriate part code. The part codes for the scroll arrows and gray areas, and the constants you can use to represent them, are listed here:
ConstantPart codeControl part
inUpButton20Up scroll arrow for a vertical scroll bar, left scroll arrow for a horizontal scroll bar
inDownButton21Down scroll arrow for a vertical scroll bar, right scroll arrow for a horizontal scroll bar
inPageUp22Gray area above scroll box for a vertical scroll bar, gray area to left of scroll box for a horizontal scroll bar
inPageDown23Gray area below scroll box for a vertical scroll bar, gray area to right of scroll box for a horizontal scroll bar

When FindControl returns one of these part codes, your application should immediately call TrackControl. As long as the user holds down the mouse button while the cursor is in a scroll arrow, TrackControl highlights the scroll arrow,
as shown in Figure 5-8 on page 5-10. When the user releases the mouse button, TrackControl removes the highlighting.

For all of the other standard controls, as well as for the scroll box in a scroll bar, your application doesn't respond until TrackControl reports a mouse-up event in the same control part where the mouse-down event initially occurred. However, for scroll arrows and gray areas, your application must respond by scrolling the document before TrackControl reports that the user has released the mouse button. When you call TrackControl for scroll arrows and gray areas, you must define an action procedure that scrolls appropriately until TrackControl reports that the user has released the mouse button.

When the user releases the mouse button or moves the cursor away from the scroll arrow or gray area, TrackControl returns as its result one of the previously listed values that represent the control part. As shown in Listing 5-18 on page 5-48, the DoContentClick procedure tests for the part codes inUpButton, inDownButton, inPageUp, and inPageDown to determine when a mouse-down event occurs in a
scroll arrow or a gray area.

When the user presses or holds down the mouse button while the cursor is in either
the scroll arrow or the gray area of the vertical scroll bar, DoContentClick calls TrackControl and passes it a pointer to an application-defined action procedure
called MyVerticalActionProc. For the horizontal scroll bar, DoContentClick
calls TrackControl and passes it a pointer to an action procedure called MyVerticalActionProc. In turn, TrackControl calls these action procedures to scroll continuously until the user releases the mouse button.

Note
As an alternative to passing a pointer to your action procedure
in a parameter to TrackControl, you can use the SetControlAction procedure to store a pointer to the action procedure in the contrlAction field in the control record. When
you pass Pointer(-1) instead of a procedure pointer to TrackControl, TrackControl uses the action procedure
pointed to in the control record.
Listing 5-19 shows two sample action procedures: MyVerticalActionProc--which responds to mouse events in the scroll arrows and gray areas of a vertical scroll bar--
and MyHorzntlActionProc--which responds to those same events in a horizontal scroll bar. When TrackControl calls these action procedures, it passes a control handle and an integer representing the part of the control in which the mouse event occurred. Both MyVerticalActionProc and MyHorzntlActionProc use the constants inUpButton, inDownButton, inPageUp, and inPageDown to test for the control part passed by TrackControl.

Listing 5-19 Action procedures for scrolling through a text document

PROCEDURE MyVerticalActionProc (control: ControlHandle; part: Integer);
VAR
   scrollDistance:   Integer;
   window:           WindowPtr;
   myData:           MyDocRecHnd;
BEGIN
   IF part <> 0 THEN
   BEGIN
      window := control^^.contrlOwner; {get the control's window}
      myData := MyDocRecHnd(GetWRefCon(window));
      HLock(Handle(myData));
      CASE part OF
         inUpButton, inDownButton:  {get one line to scroll}
            scrollDistance := 1;
         inPageUp, inPageDown:   {get the window's height}
         BEGIN
            scrollDistance := (myData^^.editRec^^.viewRect.bottom -
                              myData^^.editRec^^.viewRect.top) 
                              DIV myData^^.editRec^^.lineHeight;
               {subtract 1 line so user sees part of previous window}
            scrollDistance := scrollDistance - 1;
         END;
      END;  {of part CASE}
      IF (part = inDownButton) OR (part = inPageDown) THEN
         scrollDistance := -scrollDistance;
      MyMoveScrollBox(control, scrollDistance);
      IF scrollDistance <> 0 THEN   {scroll by line or by window}
         TEPinScroll(0, scrollDistance * myData^^.editRec^^.lineHeight,
                     myData^^.editRec);
      HUnLock(Handle(myData));
   END;
END; {of MyVerticalActionProc}

PROCEDURE MyHorzntlActionProc (control: ControlHandle; part: Integer);
VAR
   scrollDistance:   Integer;
   window:           WindowPtr;
   myData:           MyDocRecHnd;
BEGIN
   IF part <> 0 THEN
   BEGIN
      window := control^^.contrlOwner; {get the control's window}
      myData := MyDocRecHnd(GetWRefCon(window));
      HLock(Handle(myData));
      CASE part OF
         inUpButton, inDownButton:  {get a few pixels}
            scrollDistance := kButtonScroll;
         inPageUp, inPageDown:   {get a window's width}
            scrollDistance := myData^^.editRec^^.viewRect.right -
                               myData^^.editRec^^.viewRect.left;
      END;  {of part CASE}
      IF (part = inDownButton) OR (part = inPageDown) THEN
         scrollDistance := -scrollDistance;
      MyMoveScrollBox(control, scrollDistance);
      IF scrollDistance <> 0 THEN
         TEPinScroll(scrollDistance, 0, myData^^.editRec);
      HUnLock(Handle(myData));
   END;
END; {of MyHorzntlActionProc}
Each action procedure begins by determining an appropriate scrolling distance. For
the scroll arrows in a vertical scroll bar, MyVerticalActionProc defines the
scrolling distance as one line. For the gray areas in a vertical scroll bar, MyVerticalActionProc determines the scrolling distance in lines by dividing the window height by the line height; the window height is determined by subtracting
the bottom coordinate of the view rectangle (defined in the edit record) from its top coordinate. Then MyVerticalActionProc subtracts 1 from this distance so that
when the user presses the mouse button while the cursor is in a gray area, MyVerticalActionProc scrolls one line less than the total number of lines in
the window.

The MyVerticalActionProc procedure later multiplies these line distances by the
line height to derive pixel distances to pass in parameters to TEPinScroll. Also, MyVerticalActionProc turns these distances into negative values when the mouse-down event occurs in the lower scroll arrow or in the gray area below the
scroll box.

For the scrolling distance of the scroll arrows in horizontal scroll bars, MyHorzntlActionProc uses a predetermined pixel distance--roughly the
document's average character width. For the scrolling distance of the gray areas MyHorzntlActionProc uses the window width (which is derived by
subtracting the left coordinate of the view rectangle from its right coordinate). The MyHorzntlActionProc routine turns these distances into negative values when
the mouse-down event occurs in the right scroll arrow or in the gray area to the
right of the scroll box.

After calling MyMoveScrollBox, an application-defined routine that moves the scroll box, both action procedures use TEPinScroll to move the text displayed in the window by the scrolling distance. (In this example, the SurfWriter application is
scrolling a simple monostyled text document stored as a TextEdit edit record. For
a discussion of using the more general-purpose QuickDraw scrolling routine ScrollRect, see the previous section, "Scrolling in Response to Events in the Scroll Box" beginning on page 5-48.)

The TEPinScroll procedure automatically creates an update region and invokes an update event. In its window-updating code, the SurfWriter application uses the TEUpdate procedure to draw the text in the update region, as shown in Listing 5-23 on page 5-59.

The action procedures continue moving the text by the specified distances over and over until the user releases the mouse button and TrackControl completes. If there is no more area to scroll through, TEPinScroll automatically stops scrolling, as your application should if you implement your own scrolling routine.

Listing 5-20 shows how the application-defined procedure MyMoveScrollBox uses GetControlValue, GetControlMaximum, and SetControlValue to move the scroll box an appropriate distance while the action procedures scroll through the document. The MyMoveScrollBox procedure uses GetControlMaximum to determine the maximum scrolling distance, GetControlValue to determine the current setting for the scroll box, and SetControlValue to assign the new setting and move the scroll box. Use of the SetControlMaximum and SetControlValue routines is described in "Determining and Changing Control Settings" beginning on page 5-34; GetControlMaximum is described in detail on page 5-104.

Listing 5-20 Moving the scroll box from the action procedures

PROCEDURE MyMoveScrollBox (control: ControlHandle;
                           scrollDistance: Integer);
VAR
   oldSetting, setting, max:  Integer;
BEGIN
   oldSetting := GetControlValue(control);   {get last setting}
   max := GetControlMaximum(control);        {get maximum setting}
{subtract action procs' scroll amount from last setting to get new setting}
   setting := oldSetting - scrollDistance;
   IF setting < 0 THEN 
      setting := 0
   ELSE IF setting > max THEN 
      setting := max;
   SetControlValue(control, setting); {assign new current setting}
END; {of MyMoveScrollBox}
The previous two sections have described how to move the bits displayed in the window; the next section describes how to draw into the update region.

Drawing a Scrolled Document Inside a Window

The previous two sections have described how to use the QuickDraw procedure ScrollRect and the TextEdit procedure TEPinScroll in response to the user manipulating any of the five parts of a scroll bar. After using these or your own routines for moving the bits in your window, your application must draw into the update region. Typically, you use your own window-updating code for this purpose.

Both InvalRect and TEPinScroll, which are used in the examples shown earlier in this chapter, create update regions that cause update events. As described in the chapters "Window Manager" and "Event Manager" in this book, your application should draw in the update regions of your windows when it receives update events. If you create your own scrolling routine to use instead of ScrollRect or TEPinScroll, you should guarantee that it generates an update event or that it explicitly calls your own window-updating routine.

Listing 5-21 shows an application-defined routine, DoUpdate, that the SurfWriter application calls whenever it receives an update event. In this procedure, the application tests for two different types of windows: windows containing graphics objects and windows containing text created with TextEdit routines.

Listing 5-21 An application-defined update routine

PROCEDURE DoUpdate (window: WindowPtr);
VAR
   windowType: Integer;
BEGIN
   windowType := MyGetWindowType(window);
   CASE windowType OF
   kMyGraphicsWindow:   {window containing graphics objects}
      BEGIN
         BeginUpdate(window);
         MyDrawGraphicsWindow(window);
         EndUpdate(window);
      END;  {of updating graphics windows}
   kMyDocWindow:        {window containing TextEdit text}
      BEGIN
         BeginUpdate(window);
         MyDrawWindow(window);
         EndUpdate(window);
      END;  {of updating TextEdit document windows}
   {handle other window types----modeless dialogs, etc.----here}
   END;  {of windowType CASE}
END;  {of DoUpdate}
In this example, when the window requiring updating is of type kMyGraphicsWindow, DoUpdate uses another application-defined routine called MyDrawGraphicsWindow. When the window requiring updating is of type kMyDocWindow, DoUpdate uses another application-defined routine--namely, MyDrawWindow. Listing 5-22 shows
the MyDrawGraphicsWindow routine and Listing 5-23 on page 5-59 shows the MyDrawWindow routine.

Before drawing into the scrolled-away portion of the window, both of these routines
use the QuickDraw, Window Manager, and Control Manager routines necessary for updating windows. ("Updating a Control" beginning on page 5-25 describes the UpdateControls procedure; see the chapter "Window Manager" in this book for a detailed description of how to use the rest of these routines to update a window.)

Listing 5-22 Redrawing a window containing graphics objects

PROCEDURE MyDrawGraphicsWindow (window: WindowPtr);
VAR
   myData:  MyDocRecHnd;
   i:       Integer;
BEGIN
   SetPort(window);
   myData := MyDocRecHnd(GetWRefCon(window));
   HLock(Handle(myData));
   WITH window^ DO
      BEGIN
         EraseRect(portRect);
         UpdateControls(window, visRgn);
         DrawGrowIcon(window);
         SetOrigin(GetControlValue(myData^^.hScrollBar),
                   GetControlValue(myData^^.vScrollBar));
         i := 1;
         WHILE i <= myData^^.numObjects DO
            DrawMyObjects(portRect, myData^^.numObjects[i]);
            i := i + 1;
         END; {of WHILE}
         SetOrigin(0, 0);
      END;
   HUnLock(Handle(myData));
END;  {of MyDrawGraphicsWindow}
The MyDrawGraphicsWindow routine uses the QuickDraw procedure SetOrigin to change the window origin by an amount equal to the scroll bar settings, so that the upper-left corner of the document lies at (0,0) in the window's local coordinate system. The SurfWriter sample application performs this step so that its own drawing routines can draw into the correct area of the window.

Notice that MyDrawGraphicsWindow calls SetOrigin only after calling the necessary Window Manager and Control Manager routines, because the Window Manager and Control Manager always expect the window origin to be at (0,0).

By using SetOrigin to change the window origin, MyDrawGraphicsWindow can treat the objects in its document as being located in a coordinate system where the upper-left corner of the document is always at (0,0). Then MyDrawGraphicsWindow calls another of its own routines, DrawMyObjects, to draw the objects it has stored in its document record for the window.

After performing all its own drawing in the window, MyDrawGraphicsWindow again uses SetOrigin--this time to reset the window origin to (0,0) so that future Window Manager and Control Manager routines will draw into the correct area of the window.

Figure 5-16 through Figure 5-20 earlier in this chapter help to illustrate how to use SetOrigin to offset the window's coordinate system so that you can treat the objects
in your document as fixed in the document's own coordinate space. However, it is not necessary for your application to use SetOrigin. Your application can leave the window's coordinate system fixed and instead offset the items in your document by the amount equal to the scroll bar settings. The QuickDraw procedures OffsetRect, OffsetRgn, SubPt, and AddPt, which are described in Inside Macintosh: Imaging,
are useful if you pursue this approach.

Note
The SetOrigin procedure does not move the window's clipping region. If you use clipping regions in your windows, use the QuickDraw procedure GetClip to store your clipping region immediately after your first call to SetOrigin. Before calling your own window-drawing routine, use the QuickDraw procedure ClipRect to define a new clipping region--to avoid drawing over your scroll bars, for example. After calling your own window-drawing routine, use the QuickDraw procedure ClipRect to restore the original clipping region. You
can then call SetOrigin again to restore the window origin to (0,0) with your original clipping region intact. See Inside Macintosh:
Imaging
for detailed descriptions of clipping regions and of these
QuickDraw routines.
The previous examples in this chapter have shown an application that uses a TextEdit edit record to store the information created by the user. For simple text-handling
needs, TextEdit provides many routines that simplify your work; for example, the TEPinScroll procedure (used in Listing 5-18 on page 5-48 and Listing 5-19 on page 5-53) resets the view rectangle of text stored in an edit record by the amount of pixels specified by the application. The TEPinScroll procedure then generates an update event for the window. The TextEdit procedure TEUpdate should then be called in an application's update routine to draw the update region of the scrolled window.

Listing 5-23 shows an application-defined procedure, MyDrawWindow, that uses TEUpdate to update the text in windows of type kMyDocWindow. The TEUpdate procedure manages all necessary shifting of coordinates during window updating, so MyDrawWindow does not have to call SetOrigin as it does when it uses ScrollRect.

Listing 5-23 Redrawing a window after scrolling a TextEdit edit record

PROCEDURE MyDrawWindow (window: WindowPtr);
VAR
   myData: MyDocRecHnd;
BEGIN
   SetPort(window);
   myData := MyDocRecHnd(GetWRefCon(window));
   HLock(Handle(myData));
   WITH window^ DO
      BEGIN
         EraseRect(portRect);
         UpdateControls(window, visRgn);
         DrawGrowIcon(window);
         TEUpdate(portRect, myData^^.editRec);
      END;
   HUnLock(Handle(myData));
END;  {of MyDrawWindow}

Previous Book Contents Book Index Next

© Apple Computer, Inc.
11 JUL 1996