                 Visual Basic Solitaire: Dragging Bitmaps


                           by Michael Bertrand



  Solitaire players drag a card across the screen and drop it on
its destination. The card sticks there if appropriate but
automatically walks back to its original location if not. Visual
Basic Solitaire uses picture controls to draw a Solitaire-like
initial screen, then enables the user to drag the Ace of Hearts
to a home position. Just like in real Solitaire, the Ace sticks
or returns depending on whether it overlaps one of the home
positions when the mouse is released. Far from a complete rewrite
of Windows Solitaire, this Visual Basic program shows how to drag
cards, a key feature of most such programs.


                             Screen Regions


  First some terminology to distinguish locations in a Solitaire
window. At the upper left are the source rectangles with the
palms card: Source(0), Source(1), and Source(2); these are laid
on top of one another so the first two are mostly covered by the
last one which appears on top. A picture control array represents
the source rectangles; Source(0), for example, is a picture
control just the size of a card, positioned where this card goes,
and containing the palms image. Visual Basic uses this very
notation for control arrays, where Source is the name and 0, 1,
or 2 is the index. Then come the four non-overlapping home
rectangles in the top row towards the right containing the dots
pattern. Picture control array Home represents these rectangles:
Home(0) on the left through Home(3) at the upper right. Finally
come the seven hold rectangles in the second row: Hold(0)
containing the Ace of Hearts on the left and Hold(1) through
Hold(6) containing the palms card.


                            The Card Bitmaps


  The cards can be extracted from Solitaire as bitmaps in various
ways. Programming utilities like Borland's Workshop, shipped with
Borland C++, let you strip bitmaps from executables like Windows
Solitaire (SOL.EXE) and save them as BMP files usable in Visual
Basic. Alternatively, run Solitaire until the card appears on
screen, then press [ALT][PrintScreen] to capture the entire
Solitaire window to the clipboard as a bitmap. Then run PC
Paintbrush, maximize it, and paste. Use the scissors tool
carefully to outline the card desired, then choose Edit/CopyTo to
save the selected rectangle to a BMP file.


                      Laying Out the Initial Screen


  I analyzed a Solitaire screen in Paintbrush, using Solitaire's
initial layout as the guide for placement of the picture controls
in the Visual Basic program. The Source(0) picture control, for
example, is 11 pixels from the left edge of the window and 5
pixels down, just as in Solitaire, and so too for all the other
elements of the initial layout. Setting the Scale Mode to Pixels
(3) simplifies such comparisons, Visual Basic's default being
Twips. A Visual Basic program using bitmaps has the option of
loading them either at design time or at run time. Bitmaps loaded
at design time are stored in the FRM or FRX files and in the EXE
if one is created, while bitmaps loaded at run time must be
available as BMP files when the program runs. Design time loading
is preferable to cut down on extra files (lacking which the
program does not run properly). Each of the three bitmaps (the
palms card, the dots pattern, and the Ace of Hearts) need only be
loaded once at design time however, since it can be copied into
other picture controls at program initialization with statements
like:

           For i = 0 to 1
             Source(i).Picture = Source(2).Picture
           Next i

These lines load the bitmap into Source(0) and Source(1) assuming
it is already in Source(2), having been put there at design time.
This obviates redundant bitmap storage in the FRM/FRX and EXE
files, a considerable saving in this case amounting to 11 bitmaps
times 3574 bytes per bitmap, almost 40K.


                       Dragging the Ace of Hearts


  The card to be dragged, the Ace of Hearts, is at Hold(0). A
drag consists of a MouseDown on the Ace, a series of MouseMoves
to move the card, then a final MouseUp, all events sensed by a
Visual Basic picture control. A state variable tracks the drag,
initially set to WAITING, then changing to DRAGGING when the
MouseDown is sensed, reverting back to WAITING on MouseUp. At
MouseDown, Hold(0)'s Zorder is set to 0 to ensure that it will
move over the top of other controls rather than behind them.
Hold(0)'s MouseMove event contains the simple core drag logic:

'------------------------------------------------------------
Sub Hold_MouseMove (Index As Integer, Button As Integer,
                    Shift As Integer, X As Single, Y As Single)

  Dim dx, dy     'deltas from last position

  If Index = 0 And State = DRAGGING Then
    'Calculate deltas from last position.
    dx = X - x0: dy = Y - y0
    'Reposition card according to deltas.
    Hold(0).Left = Hold(0).Left + dx
    Hold(0).Top = Hold(0).Top + dy
  End If

End Sub
'------------------------------------------------------------

Actually Hold, as a picture array, has a MouseMove event
activated as the mouse moves over any of the Hold pictures, which
of Hold's pictures being specified by incoming parameter Index.
So Index = 0 means there's a MouseMove over the Ace and State =
DRAGGING means there's a drag in progress. x0 and y0 are global
variables holding the last mouse position and incoming parameters
X and Y are the current mouse position, so the differences
('deltas' in graphics lingo) are the amounts by which
Hold(0).Left and Hold(0).Top must be moved. The result is a
moving card with the movement tied to how much the mouse has
moved: in other words, the card follows the mouse!


                         The Automatic Walk Back


  A decision ensues at MouseUp: either the Ace overlaps one of
the home rectangles, in which case it occupies the home
rectangle, or it does not, in which case the Ace walks back to
its original location along a straight line. The former case
presents no problems, the following lines setting Hold(0), the
control holding the Ace, to coincide with the i'th home
rectangle:

                Hold(0).Left = Home(i).Left
                Hold(0).Top = Home(i).Top

The automatic return, however, requires the calculation of a
series of intermediate points along the line between the MouseUp
position and the card's original location. Routine CalcPtOnLine()
calculates the intermediate points using the parametric
definition of a straight line segment between points p0 and p1 as
all points q satisfying:

                  q = p0 + t*(p1 - p0), 0.0 <= t <= 1.0

Here t is a 'real parameter' between 0.0 and 1.0. Putting t = 0.0
in the formula generates q = p0; putting t = 1.0 generates q =
p1; and likewise putting any real number between t = 0.0 and 1.0
generates a point along the line, values of t closer to 0.0
leading to a q closer to p0 and values of t closer to 1.0 leading
to a q closer to p1 (t = 0.5 generates the point exactly midway
between p0 and p1). CalcPtOnLine() applies the formula to x and y
coordinates separately and rounds the result of the floating
point calculations to the nearest pixel:

'------------------------------------------------------------
Sub CalcPtOnLine (ByVal x0%, ByVal y0%, ByVal x1%, ByVal y1%,
                  ByVal t!, X%, Y%)

  'USE:  Calc point on line determined by (x0,y0) and (x1,y1)
  'IN:   (x0,y0) = first endpoint of line
  '      (x1,y1) = second endpoint of line
  '      t = parameter between 0.0 and 1.0: t = 0.0 is (x0,y0),
  '      t = 1.0 is (x1,y1), t = 0.5 is midpoint, and so on
  'OUT:  (x,y) = calculated point on line for parameter t
  'NOTE: Add 0.5 for rounding to nearest pixel.

  X = Int(x0 + t * (x1 - x0) + .5)
  Y = Int(y0 + t * (y1 - y0) + .5)

End Sub
'------------------------------------------------------------

'ByVal' parameters to a Visual Basic routine are by value whereas
parameters not listed ByVal are by reference. With reference
parameters, calculations made in the routine are passed back to
the caller's variables -- in this case, the coordinates of the
intermediate point.


                          The Instruction Line


  Visual Basic Solitaire shows a white instruction line at the
bottom of its window with the message:

           Drag and release ace. Drag and drop over home area.

When implemented as a label, the text sometimes flickers as the
Ace is dragged. More seriously, dragging at certain speeds
results in a trail of aces, the residue clearing up eventually
when Visual Basic gets around to updating the label. Printing the
message in the form's Paint event handler or a picture control's
Paint event handler gives the same result. The solution is to
implement the instruction line much like the cards: as a picture
control containing a graphic. Type out the text in PC Paintbrush,
then save exactly the size needed as a bitmap. Then attach the
bitmap to a picture control at the bottom of the form when
designing in Visual Basic.
                                                                          
-------------------------------------------

Mike teaches Mathematics and programming at Madison Area
Technical College, Madison, WI 53704.
