Balloon ToolTip Control

Document Sample
Balloon ToolTip Control Powered By Docstoc
					Balloon ToolTip Control
Predrag Bosnic

After the Multi-Line ToolTip control Predrag Bosnic created last month, most will say that
this is just a variation, and it's easy to change the Multi-Line ToolTip control to the Balloon
ToolTip control. So why a completely new article? Well, the truth is somewhere in the
middle. Here, he'll use many basic principles he covered for the Multi-Line ToolTip, but
don't forget he has to do something that's not natural for Visual FoxPro. This time, he'll
introduce Windows API functions and show how they can help when help is needed.

First of all, let me see where I can find a Balloon ToolTip control. I use MS Word for
writing, and the AutoShape/Callouts section contains a few different styles of balloons.
Figure 1 shows these callouts, and I have to say each one has something interesting about

Figure 1. MS Word callouts.

     The second application is MS Visio, but it uses the Balloon ToolTip to show the
ToolTip text. Figure 2 shows the MS Visio implementation of the Balloon ToolTip
control, and I have to say I like it; it's very elegant. In addition, the control can show a
title and has a drop-shadow.
Figure 2. MS Visio Balloon ToolTip control.

     On this occasion, I chose a simplified version of the MS Visio control. The title and
drop-shadow will be skipped for the time being. At the end of this article, I'll offer some
comments about possible improvements.
     Figure 3 shows the design I want to achieve and the basic proportion I want to keep.
It's possible to change the value of "X" inside the code. The maximum width of the
control is about 363 pixels, and that would be sufficient for about 60 characters. Figure 4
shows four possible orientations that the ToolTip can have. Which one will be used
depends on the mouse position on the screen. The default position is the position K1.
Figure 3. The basic proportion for the ToolTip control.

Figure 4. All four possible orientations for the Balloon ToolTip control.

Design requirements
Please see the requirements for the Multi-Line ToolTip in last month's issue. (Subscribers
can access the article in the "View Past Issues" area of the FoxTalk Web site.)

Design and implementation
For this solution, I'll use Windows API functions. The reason is very simple; I don't see
how I can do this with the native Visual FoxPro 7 controls. In my opinion, this is a good
example of when I should use Windows API functions. When a programming language I
use can't help to solve the problem (or at least there's not an easy solution), the Windows
API should be used. Of course, the programming language has to have the ability to call
the Windows API and pass adequate parameters. Visual FoxPro isn't ideal to call the
Windows API, but, with every new release, it's getting better and better. The Visual
FoxPro 7 release brings in hWnd (windows handler) as a property of the form control.
The windows handler is used in hundreds of Windows API functions. Also, the
DECLARE statement has improved, and, if I add on top of that a couple of good articles
about using the Windows API in Visual FoxPro (Christof Lange's in the public domain
and Doug Hennig's articles in FoxTalk), a lot can be done.
   I'll create a class library wbToolTipB to be a container for my classes. This library has
two classes: wbToolTipB and ttScr2. This time, I'll first describe the wbToolTipB class.
This class is based on the Timer control and has a few user-defined properties.
 • oObjRef—Reference to the object(for instance, button, combo box, and so on)
 • ToolTipForeColor—ToolTip fore color
 • ToolTipBackColor—ToolTip back color

   The Init method contains only declarations for Windows API functions.
Declare Integer CreateRectRgn in "gdi32"
Declare Integer CreateRoundRectRgn in "gdi32"

    It's worth mentioning that this class will be on the form that uses the Balloon ToolTip
control, and using its Init method to declare Windows API functions is correct. The Init
method is executed only once, and the user must not care about declaring necessary
Windows API functions. I'll give you an explanation of using some of the API functions,
but I have to say that I don't use all of the declared functions. I suggest you read about the
following Windows API functions: CreateRectRgn, CreateRoundRectRgn,
CreateEllipticRgn, CreatePolygonRgn, FillRgn, PaintRgn, CombineRgn,
SetWindowRgn, DeleteObject, ReleaseCapture, GetDC, ReleaseDC, CreateSolidBrush,
Polygon, SelectObject, CreateFontIndirect, TextOut, SetTextColor, GetTextColor, and
    The only important method is the Timer method, and its code follows:
* Timer event
LOCAL joObject, jnLeft, jnTop, jcToolTip, joX

joX = SYS(1270)
IF TYPE('joX') <> 'O' or ISNULL(joX)
  thisform.wb_LIsToolTipActive = .f.
  this.Enabled = .f.
IF joX # this.oObjRef
  thisform.wb_LIsToolTipActive = .f.
  this.Enabled = .f.
IF thisform.wb_LIsToolTipActive = .t.
  RETURN .f.
joObject = this.oObjRef
jcToolTip = ALLTRIM(joObject.wb_cToolTipText)
IF EMPTY(jcToolTip)
  this.Enabled = .f.
jnLeft = Mcol('',3)
jnTop = Mrow('',3)
thisform.wb_oToolTip = CREATEOBJECT('ttscr2',thisform,;
thisform.wb_LIsToolTipActive = .t.
Thisform.wb_oToolTip.Visible = .T.
*** Don't kill the timer here.
*** Timer must be active during the ToolTip session!!

    Keep in mind that the timer is active only during the ToolTip session, while the
ToolTip is shown. When the Timer method is running, it investigates the position of the
mouse pointer.
    The code in the first IF command deactivates the ToolTip, and then the timer
deactivates itself if the mouse pointer isn't over an object
    The second IF command investigates the object under the mouse pointer. If it's not the
same object that's activated the ToolTip control (when the ToolTip control has been
activated, the code sets the oObjRef property and now is using it), the code deactivates it
because the user has moved the mouse pointer outside of the control that created the
    The third IF statement tests the wb_LIsToolTipActive property. If the ToolTip is
already active, it simply returns control from the Timer event code.
    The fourth IF command tests the ToolTip text. If it's empty, it returns the control from
the Timer event code. If the execution achieves this point, it means the ToolTip isn't
active, and the code must activate the ToolTip control. At this point, I scan the mouse
position, and then I call the code to create the Balloon ToolTip and set the flag:
wb_lIsToolTipActive = .T.

   Now it's time to create the class definition for Balloon ToolTip—ttScr2.
   As you can see in Figure 5, the class contains the same controls as the ttScr1 class
used for the Multi-Line ToolTip control. However, the Init method is different.
Figure 5. The wbToolTipB class inside the Class Designer.
LPARAMETERS toParentForm, tnToolTipType, ;
  tcToolTipText, tnLeft, tnTop, tnForeColor, ;

* toParentForm - reference to the Parent form
* tnToolTipType - tooltip type, 1=rectangle
* tcToolTipText - ToolTip text
* tnLeft - xCoord of the mouse pointer
* tnTop - yCoord of the mouse pointer
* tnForeColor - fore color
* tnBackColor - back color

WITH thisform
  .oParentForm = toParentForm
  .nObjCenterX = tnLeft
  .nObjCenterY = tnTop
  .comment = ALLTRIM(tcToolTipText)
  .Edit1.Value = .comment
  .ToolTipType = tnToolTipType
  IF !EMPTY(tnForeColor)
    .Edit1.ForeColor = tnForeColor
  IF !EMPTY(tnBackColor)
    .BackColor = tnBackColor

WITH thisform
  .Left = tnLeft
  .Top = tnTop

   The ToolTip form ttScr2 accepts a few parameters, sets the ToolTip color, and calls a
Skin method. After that, the ToolTip is shown. If you compare this code with Multi-Line
ToolTip code, the difference is in the Skin method.
* ttScr2, Skin method
LPARAMETERS tnWidth, tnHeight

* tnWidth - optional, ToolTip form width
* tnHeight - optional, ToolTip form height

LOCAL W, H, NoOfLines, jaDots, EH, K, strDots,;
  Delta, jnX, jnY, xMin, xMax
LOCAL yMin, yMax, nSkinRgn, jnDots, DeltaCut

*-------------- Set DEFAULTS for Width -----------
IF EMPTY(tnWidth) or tnWidth < 363
  W = 363
  W = tnWidth
  CASE thisform.ToolTipType = 1
    NoOfLines = Int(LEN(toForm.comment)/60) + 1
    EH = 21 + 15*(NoOfLines - 1)
    Delta = 10
    Delta2= 2*Delta
    thisform.Edit1.Height = EH - 4
    H = EH + Delta2
    IF NoOfLines = 1
      W = thisform.textwidth(thisform.Comment)+25
      W = W + Delta
    WITH thisform
      .width = W
      .Height = H
      .Edit1.Width = .width-6-Delta-8
      .Edit1.Left = Delta + 3
IF NoOfLines = 1
  thisform.Edit1.Alignment = 0
jnX = thisform.nObjCenterX
jnY = thisform.nObjCenterY

Xmin   =   W
Xmax   =   _screen.Width-W
Ymin   =   H
Ymax   =   _screen.Height - H
Xscr   = _screen.Width
Yscr   = _screen.Height

*               I
*          K2   I       K1
*               I
*   ------------------------------
*        K3     I       K4
*               I
*               I
DO case
  CASE jnX > Xmax and jnY < Yscr/2
    WITH thisform
      .Left = jnX - W
      .Top = jnY + Delta2
      .Edit1.Top = H - EH + 2
    thisform.edit1.Left = 3

 CASE jnX > Xscr/2 and jnX < Xmax and jnY < Ymin
   WITH thisform
     .Left = jnX - W
     .Top = jnY + Delta2
     .Edit1.Top = H - EH + 2
   thisform.edit1.Left = 3

 CASE jnX > Xmin and jnX < Xscr/2 and jnY < Ymin
   WITH thisform
     .Left = jnX
     .Top = jnY + Delta2
     .Edit1.Top = H - EH + 2
   thisform.Edit1.Width = thisform.width-6-Delta

 CASE jnX < Xmin and jnY < Yscr/2
   WITH thisform
     .Left = jnX
     .Top = jnY + Delta2
     .Edit1.Top = H - EH + 2
   thisform.Edit1.Width = thisform.width-6-Delta

 CASE jnX < Xmin and jnY > Yscr/2
   thisform.Left = jnX
   thisform.Top = jnY -H - Delta2
   thisform.Edit1.Width = thisform.width-6-Delta

 CASE jnX > Xmin and jnX < Xscr/2 and jnY > Ymax
   toForm.Left = jnX
   toForm.Top = jnY - H - Delta2
   thisform.Edit1.Width = thisform.width-6-Delta
        CASE jnX > Xscr/2 and jnX < Xmax and jnY > Ymax
   thisform.Left = jnX - W
   thisform.Top = jnY - H - Delta2
   thisform.edit1.Left = 3
   thisform.Edit1.Width = thisform.width-6-Delta

 CASE jnX > Xmax and jnY > Yscr/2
   thisform.Left = jnX - W
   thisform.Top = jnY - H - Delta2
   thisform.edit1.Left = 3
   thisform.Edit1.Width = thisform.width-6-Delta

    thisform.Left = jnX
    thisform.Top = jnY -H - Delta2
    thisform.Edit1.Width = thisform.width-6-Delta
  * Default: rectangle-Visio
  CASE thisform.ToolTipType = 1
    jnDots = 7
    DIMENSION jaDots(jnDots,2) && Region
      @jaDots, k, h, delta, eh, w, delta2 )

    WAIT WINDOW "Error in ToolTip call."
strDots = this.CreateDots(@jaDots)
nSkinRgn = CreatePolygonRgn(strDots,jnDots,1)
With thisform
  Local HDC, hRgnO1, jnRez
  HDC = GetDC( .HWND )
  Local lnPrevBrush, lnBrush
  lnBrush = CreateSolidBrush(RGB( 0,0,0))
  lnPrevBrush = SelectObject(m.HDC,m.lnBrush)
  jRez = FrameRgn(HDC,nSkinRgn,lnBrush,1,1)
  SelectObject( m.HDC, m.lnPrevBrush )
  DeleteObject( m.lnBrush )
  ReleaseDC( .HWND, m.HDC )
=SetWindowRgn(thisform.HWnd, nSkinRgn, .f.)

     Basically, I'm drawing the ToolTip control on the form. First, the code calculates the
width and height of the ToolTip control and then the orientation of the ToolTip control
(K1, K2, K3, or K4). The next important line is the call of the CreatePolygonRgn
function. This function creates a region and returns a handler to it. In my case, the region
is in the shape of the ToolTip control, and this shape is defined by the DotsRectangle_v
method. The CreateDots method creates a hexadecimal interpretation of the ToolTip
coordinates. The code inside the With...EndWith structure uses a few Windows API
functions to draw the border around the ToolTip control, to paint it, and, finally, the
SetWindowRegion function will combine the form and the ToolTip region. The last part
means that the ToolTip is visible, but the rest of the form is transparent. Figure 6 shows
an example of the Balloon ToolTip control in action.

Figure 6. Form controls using the Balloon ToolTip control.
   It's important to note that the form must call the Skin method every time it's painted.
The Paint method contains the following code:

The wbToolTipB library
This library contains two classes: ttScr2 and wbToolTipB. The latter class must be added
to the form. All controls on the form have to support the Balloon ToolTip.

The ttScr2 class
Figure 7 shows the ttScr2 class with it methods and properties; they're described in Table
1 and Table 2.

Figure 7. The ttScr2 class with its methods and properties.

Table 1. The ttScr2 methods.

Name                  Type      Description
Skin                  Private   Used internally. It calculates, draws, and repaints a
                                ToolTip control.
CreateDots            Private   Used internally. It creates a dot array to draw a polygon.
DecToHex              Private   Performs conversion: Decimal to Hex.
DotsRectangle_v       Private   Defines seven significant points to draw a defined
                                ToolTip shape.

Table 2. The ttScr2 properties.

Name             Type     Description
nObjCenterX      Int      Used internally. Mouse pointer position, X coord.
nObjCenterY      Int      Used internally. Mouse pointer position, Y coord.
oParentForm      Int      Reference to the parent form object.
The wbToolTipB class
Figure 8 shows the wbToolTip class with it methods and properties; the properties are
described in Table 3.

Figure 8. The wbToolTip class with its methods and properties.

Table 3. The wbToolTip properties.

Name                  Type    Description
nObjRef               Int     Used internally. Reference to the object on the form.
ToolTipBackColor      Int     ToolTip back color.
ToolTipForeColor      Int     ToolTip fore color.

Apart from improving the current code, think about the following:
 • A ToolTip with rounded corners
 • A ToolTip with a title and possibly an icon (see Figure 2)
 • A cloud-style ToolTip control

The Balloon ToolTip control is a very attractive control, and it can find a place in all
applications. This solution isn't heavy, and the speed is satisfactory. Most probably, you
can see many similarities between the Multi-Line ToolTip and Balloon ToolTip controls.
If you're thinking about putting both classes together in one class, I advise you not to.
You'll probably use only one type of ToolTip control, and the rest of the code is an
unnecessary overload. Next time, I'll show you how to build a very specific ToolTip
control—a ComboBox Item ToolTip control.

Download 07BOSNIC.ZIP
Predrag Bosnic is a senior developer for London's Westwood Forster Ltd. mbosnic@westwood-

Shared By: