Wednesday, June 10, 2015

Displaying Balloon Tips with Listboxes

Stonefield Query makes it very simple to create a report: select the fields you want, set the properties of those fields as necessary, and click Preview to run the report. However, sometimes it’s hard to tell exactly what the report will look like until you do preview it: you may have grouped on one field, summed another, changed the font for a third, and so on. You have to go into the Properties dialog for each field to see how it’s formatted in the report. For example, in this report, you can’t immediately tell how each field appears in the report:

fields

One of the things we wanted to add is the ability to see the formatting for each field without having to go into the Properties dialog for each one. We decided to do that using balloon tips.

ctl32_BalloonTip is a class created by Carlos Alloatti that’s part of his ctl32 library. It provides the ability to display balloon tips in a VFP form. Balloon tips are similar to tool tips but are much more attractive, give you greater control over their appearance, and can display a lot more text. I discussed this class in detail in the July 2011 issue of FoxRockX. Here, you can see that the Freight field is summed at group breaks, displays the percentage of the total, and is formatted as a currency value. All you have to do is hover your mouse over the field in the list and the balloon tip tells you what you need to know.

balloon

Getting the balloon tip to display the settings for the field under the mouse pointer was a little tricky. The biggest issue is how to know what “line” the mouse is over in the list. There isn’t a property of the Listbox class that tells you that so you have to calculate it using the following factors:
  • The current mouse position. If you do this in the MouseMove event like I do, the X and Y coordinates are passed as parameters; it’s the Y value we need. However, the Y value is relative to the form, so we have to subtract the location of the top of the Listbox. We can’t use the Top property because the Listbox may be inside a container (in this case, it is), so we’ll use OBJTOCLIENT() to get the top of the Listbox relative to the form.
  • The height of a line. We can get that using FONTMETRIC(1) to measure the height using the Listbox’s FontName and FontSize properties. We’ll then divide the calculated mouse Y position by the height to get the line number.
  • Where the Listbox is scrolled to. If the user scrolls the list, the line number of the item under the mouse needs to be adjusted by how far down the user scrolled. Fortunately, the TopIndex property of Listbox tells us that, so we’ll add that to the value calculated above.
Here’s the code for the MouseMove method of the Listbox:

lparameters tnButton, ;
     tnShift, ;
     tnXCoord, ;
     tnYCoord
local lnHeight, ;
    lnLine, ;
    lcLine, ;
    lcField, ;
    loField
lnHeight = fontmetric(1, This.FontName, This.FontSize) + 2
lnLine   = int((tnYCoord - objtoclient(This, 1))/lnHeight) + ;
    This.TopIndex
lcLine   = transform(lnLine)
lcField  = This.List[lnLine, 2]
if This.Tag <> lcLine and not empty(lcField)
      loField = Thisform.oReport.oFieldsCollection.Item(lcField)
      if vartype(loField) = 'O'
          Thisform.HandleBalloonTip(loField)
      endif vartype(loField) = 'O'
      This.Tag = lcLine
endif This.Tag <> lcLine ...

A few things about this code:
  • There’s a little space between lines in the list, so we have to adjust the value returned by FONTMETRIC(1). I thought adding FONTMETRIC(4) (the leading) would be reasonable but that ended up being too high. I hate using a constant like “2” here but am not sure what the adjustment value can be derived from.

  • Not everyone knows this, but the List and ListItem members of Listbox can be 2-dimensional arrays. In this case, the first column of the current row contains the caption of the field displayed to the user and the second column contains the field name, which is used as an index into a collection of the fields in the report. To add a second column to List and ListItem, use code like:

    list.AddListItem(lcCaption)
    list.AddListItem(lcFieldName, list.NewItemID, 2)

  • MouseMove fires when the mouse moves even a pixel. We really only need to update the balloon tip when the user moves the mouse to a new line, so we put the line number into the Tag property (I could’ve added a specific property to a Listbox subclass but didn’t want to change the class used in the existing form) and only call the form’s HandleBalloonTip method when the line changes.

  • HandleBalloonTip does the actual work of displaying the balloon tip. I’m not going to show the code here because it’s fairly lengthy and specific to Stonefield Query. Basically, the code consists of building a string of the contents to display in the balloon tip and then displaying it using:

    This.oBalloonTip.ctlShow(6, lcText, toField.cCaption, 1)

    6 means display the balloon tip at the mouse location and 1 means use the information icon in the balloon tip.
That’s it. Not a lot of code, but it took a few minutes to work out the calculations to get the item under the mouse. Hopefully, this blog post inspires you to look at Carlo’s balloon tip control and saves you some time if you want to use it with listboxes.

No comments:

Post a Comment