Tech News Back IssuesIssue: 040909
Dynamic HotPICT Options
In the last issue of Omnis Tech News, we explored the basic setup and use of the HotPICT component. I hope you've been having a great time exploring how you might take advantage of these features. But there is more to this extension object than first meets the eye. Let me up the ante a bit...
There are two additional properties of this field type that I deliberately didn't mention last time. These properties open up some interesting possibilities for certain types of application once we understand what they do and how they work. Both of these properties are only available on an instantiated window (not on a remote form at this point, sorry!), so to see them, we must have Runtime Properties switched on for the Property Manager (which we do from the Property Manager's context menu, accessed by right-clicking on any "open" area on that window). Once we do, the "HotPict" tab of the Property Manager looks like this for a HotPICT field:
And to use them, we have to plan ahead a bit. Since these are runtime properties, we cannot use them while working on a window class. We can only take advantage of them on a window instance. This means that we must write methods for our window class that anticipate our use of these properties. They allow us to do some interesting things for the benefit of the users of our applications - we just have to understand how they work. Let's begin with the "easy" one...
The first of these HotPICT component properties we will examine here has appeared in recent versions of Omnis Studio. It is the $designmode property. When this property is set to kTrue (which we can do dynamically using Omnis Studio Notation), we can perform all of the same hot area creation and modification operations described in the previous article - but we can perform these actions in the window instance. And so can the users of our applications if we make the effort to train them.
In essence, this property is used to make the HotPICT field updateable at runtime without affecting the definition of the field in the window class. Changes made by the user are not saved to the class, but will persist as long as the window instance is open. This is not as limiting as it might seem, but we'll look into those details later in this article.
To switch on this property, we need to execute the following code in the window instance:
The item labeled <HotPICTref> is a notational reference to the HotPICT field in the window instance. It could be an Item reference variable or an explicit notational string. We could instead use a notational assignment:
Either one will do. This code could be put in the $construct method of the window instance (or of the field itself) if we want the field to be in design mode from the beginning - or we could include it in the $event method of some pushbutton field. There are many variations we could use.
Of course, if we just want the user to be able to switch this property on and off rather than make that action part of a more complex process, we could simply place a checkbox field on the window and assign the notational path that points to the $designmode property as the value of the $dataname property of that field. Yes, we can do just that if we wish! A value-containing property of a field is only another variable, so no reason we can't have some sort of field (entry, checkbox, etc.) represent it on screen. Here is how that might look:
However we do it, switching this property on for a HotPICT field in a window instance allows the user to create, modify and/or remove hot areas in that field. We could add further fields to this window that allow the user to assign other property values for the current hot area - with a little creativity.
Extending Design Mode
As the name suggests, the field itself now works exactly as it does on a window class - at least, with regard to selecting, creating, modifying and removing hot areas. But creating a complete interface for detecting which hot area has been selected and assigning properties to it takes a little more work than it might seem on the surface. This is because there is no event that indicates that an area has been clicked while the field is in design mode. The evAreaClicked event is not active in this mode. So we must get more creative to detect a hot area selection.
In the normal design mode (working on a window class), we change the hot area by right-clicking on it. The same is true when $designmode is switched on for a HotPICT field in a window instance. To detect this selection, we must first switch on the $rmouseevents property for the field and then put an On evRMouseDown section in the $event method for that field. In that section, we can then copy the various properties of the current hot area into instance variables for display or modification. I have noticed a few issues, however, that I would like to share here (so you don't think you're crazy if they happen to you). These have only been tested on Mac OS X 10.5.6, so they may not be noticed (or may be entirely different!) on other platforms or Mac OS X versions.
First, I have noticed that if an OK message dialog is opened while a HotPICT field is in design mode, the current hot area is put into a "dragged" state and moves as the mouse pointer moves. Clicking the mouse somewhere on the HotPICT field is the only way to release the hot area. But if the mouse pointer was not over the hot area when the dialog is closed (for example, if the OK button on the dialog is clicked by the mouse), the hot area being dragged is offset from the mouse pointer - and it is possible that it can't be replaced into its original position because the mouse would not be over the field when it is. The solution: Don't open a dialog from the $event method when in this state.
Another thing I have noticed is that either a right-click or a normal click will select a hot area when the field is in this mode. So to make sure that the proper things happen when either of these types of click are performed, we should have an On evMouseDown,evRMouseDown section in the $event method of the HotPICT field to cover the entire range of possibilities. (This also implies we should have the $mouseevents property switched on for the field as well.) We can't detect just one or the other because hot areas can still only be created or removed with a right-click, but the user might select a hot area with only a simple click.
The final thing we must do if we wish to retrieve hot area properties into instance variables for display and manipulation on our window instance is to make the Process event and continue command the first command in the On block mentioned above. If we don't do this, we will be retrieving the properties of the hot area that was current before the click occured. Remember, a $event method traps the intention to perform an action, but the action has not yet been realized (so that we can reject it if the situation demands). The Process event and continue command realizes the event so that we can deal with its occurance rather than its anticipation. That code might look like this:
Of course, if we just want the user to be able to assign property values to hot areas on some kind of setup window, we can simply create entry fields for entering those values and assign the notational paths to the properties we want to expose as the datanames of those fields as detailed above. We must still use the Process event and continue command in an On evMouseDown,evRMouseDown block in the $event method of the HotPICT field, though. That is, we must still realize the change of hot area selection. But with this technique, we only need to issue a simple Redraw command once the change has been realized to properly populate those entry fields instead of issuing multiple commands to assign values to instance variables. And any of those properties is changed as soon as the user types in a different value, rather than our having to include code in an On evAfter block in the $event method of that field to assign the property value from the instance variable value. That code would look like this:
I mention both techniques because each may be appropriate in certain situations - or each may simply appeal to to different developers.
By this time, the astute reader might be wondering, "If we can only perform these operations on a HotPICT field on a window instance, then what's the point? Sure, the user could have a great time playing around with creating hot areas, but those areas will just vanish into the ether once the window instance is closed. Is there any practical value to this feature?" Rest assured that I do have a practical use in mind, although it doesn't apply to all applications. The $designmode feature, when combined with the other property we explore in this article, opens up some interesting possibilities! We just have to combine them in the proper way...
The $arealist Property
That other useful HotPICT runtime property is $arealist. It is the main focus of this article - mainly because there is more to discuss about it. While we can't examine the contents of this property directly, we can use this property to populate an Omnis list variable. Once we do this, we will see that the list variable has been populated with the definitions of all the hot areas in a HotPICT field. Such a list allows us to gain a further understanding of how hot areas are defined (for example, how the node coordinates are specified and ordered). And we will do this shortly.
But that's just for starters! We can also modify these definitions - and even add new ones (once we understand how they are constructed) - using that list variable (or any other list variable defined with precisely the same column structure) and then reapply the list contents to the $arealist property to change the current instance of that HotPICT field. Dynamic hot areas! There are some useful techniques we can build using this property (along with the $designmode property), but let's first see how this all works.
Again, to use the $arealist property of a HotPICT field, we must first copy its contents into a list variable at some level of scope. I will use an instance variable for these examples, which allows us to view the contents of the list variable in a list display field of some type on the window instance. The most obvious thing we can do with this property is to examine the existing hot areas of a HotPICT field and learn how hot areas are defined internally. Here is a simple window with a HotPICT field and a Data Grid field with some pushbuttons that perform various actions with the $arealist information:
To begin our exploration, let's retrieve the contents of the $arealist property of our HotPICT field into a list variable named areas. This variable is used as the dataname for our Data Grid field on the lower half of our window. To do this, we put the following code into the $event method of the "Get arealist" pushbutton (followed by a Redraw command):
The list we retrieve contains eight (8) columns - which are already named for us in the target list variable! (And using a Data Grid field allows us to see those column names directly - as well as to modify the contents of each cell in the list variable.) The hot areas are shown in this list in the order in which we defined them:
This may look a little cryptic at first, but the column names can help us determine what we're looking at. Here are brief definitions of those columns:
But each of these could use a bit more explanation:
The id and name columns are pretty straightforward. They simply contain the values we have already assigned to the $currentid and $currentname properties of the hot area when it was the current one. It is assumed that we have assigned meaningful values to these items. The id values are assigned a sequential number by default and the name value defaults to "Untitled". I would also suggest that we assign unique values to each hot area in most cases as well, although there are reasonable arguments that could be made for assigning the same name and/or id value to more than one area as a means of having those areas perform the same action on a click, if that is desired. (There are other ways of doing this as well.) But we discussed all that in the previous article.
The points column is pretty easy, too - once we understand how the points (nodes) of an area are defined there . This column contains a comma-separated list of node coordinates. The list is maintained in the order in which the nodes are defined for the area - in classic follow-the-dots fashion. "But what are all those decimal numbers?", I hear you say. "Aren't pixel positions integers?" It's true: the positions of the nodes of a hot area are pixel positions relative to the upper left corner of the HotPICT field - and pixels are counted as integers. The "decimal" values we see is just a special way of combining the x and y coordinates in a way that multiple pairs of them can be listed in a comma-separated fashion with no confusion. They are given as "x.y". So a value shown as "40.40" means that both the x and y coordinates of that point equal 40. So the "decimal" values are the coordinates of a specific node - no actual decimal values are involved.
The order of the nodes for a hot area is also significant. This is given with these values as well. Drawing of a hot area begins with the first coordinate pair listed and proceeds through each successive pair. (By default the areas are drawn counterclockwise, but this can be changed by manipulating the node order if we wish.) This is important to keep in mind if we intend to dynamically add hot areas to this list. If we're not careful, we could end up with crossed lines and a strange-looking hot area - unless that is our intention. On the other hand, it is quite possible to create hot areas with holes in them - or to completely surround one hot area with another - if we are careful. Having access to these node definitions allows us to fine-tune the exact position of each node.
The cursorid column contains an integer number that specifies the cursor icon that the mouse pointer will display when over that hot area. A value of 0 means that the default icon for the window will appear. A very large number indicates that a cursor keyword (such as kcursArrow) was assigned for that area, while a smaller number (in the thousands) indicates that the icon was assigned from an icon id (with the keyword of kcursCustom assigned) - and the number is, therefore, the icon id of the cursor icon to be used over that hot area. Here is a chart that explains the cursor constant keywords (found under the Constants tab in the Catalog window in the group named "Cursors"):
The large cursorid values that indicate the use of a cursor constant might be a little bewildering since the ID values associated with each constant, which we see in the Catalog window, are quite small. But they are only mysterious until we notice the pattern: Each one is simply the ID value of that cursor constant times 16,777,216 (that's 2^24 or 16*1024*1024). It is not clear why these large values are used (perhaps to not conflict with the icon id values of other icons in the icon tables?), but that is the relationship between the values we see using the arealist and the cursor icons we assign to our hot areas.
The next three columns contain either the numeral "1" (apparently indicating "True") or the string "False". The "1" value means that the property represented by that column is switched on. The flashonclick column represents the hot area option of the same name. The flashonenter column represents the invertonenter hot area property. And the frameonenter column again represents the hot area property of the same name. So up to here we've covered all of the hot area properties discussed in the previous article. But wait - there's more...
The framecolor column is something we haven't seen before with this field type. "So what could this be?", I hear you say, curiously. This column controls a special feature that only seems to be available on the Windows platform (although I must admit, I have not tested it on any of the Linux platforms and I expect it to work there as well). Specifically, it controls the color of the frame around a hot area that appears if we have switched on the frameonenter property of that hot area and then move the mouse cursor over it. There is no property that appears in the Property Manager for this attribute, so I must assume that we are required to set it dynamically if we want to use it. (Good thing I'm writing this article...) In the following screen shot, the default frame color is used along with the "NoGo" cursor:
The default value for this column appears to be -2147483607, which is the numeric value associated with the kRed color constant. So on platforms where this additional property of a hot area has an effect, the frame that appears when the mouse passes into a hot area with its frameonenter property switched on will be red, as shown here.
But we can change this behavior by changing this columns value to a different one and then reassigning the $arealist value. We can use either other color constants or values generated using the rgb() function to specify other colors for this purpose. The rgb() function generates large non-negative values which do not conflict with the color constant values. So we can use any color we want for framing hot areas (on those platforms that support this) rather than being limited to the Omnis Studio color constants. In fact, performing such changes is exactly what the $arealist property is for...
Now For The "Dynamic" Part...
We cannot manipulate the detailed contents of the $arealist property directly, but we can replace those contents with the contents of another properly defined list. One way to do this is to copy $arealist into a list variable, make whatever changes we need to that list variable and then copy the changed list contents back into $arealist. We have seen this behavior before with the color list of the Graph2 component and the format of the Report Data Grid component. The techniques we use here are very similar to those techniques.
Combined with the $designmode property detailed at the beginning of this article, we could also create "lab" windows where the end user (or we ourselves) could configure a HotPICT field overlay for use with an imported image and then store the overlay information (the $arealist value) in a database for later retrieval and application to a HotPICT field on another window (along with the image itself - or the path to the image - so we can apply that to a picture field or background picture object underneath the HotPICT field at the same time). Not all applications could take advantage of this technique, but it's another tool in the kit that could come in handy some day.
One minor disappointment that I discovered is that this technique does not appear to work for remote form components currently. This is because access to the notation tree is not supported on the remote form instance. This technique requires having access to the $designmode and $arealist properties for both reading and setting their values, but neither of them can be accessed for either purpose on a remote form instance.
There are many possibilities for how we could take advantage of these two properties. It just takes a little imagination - along with the realization that we can only affect the instance of a HotPICT field. So we need to store our revised hot area lists somewhere for retrieval at runtime. We could cache an updated list in the $userinfo value of a field (in the class) so that we can apply any necessary corrections or fine-tuning when the field is instantiated. We could use a list field to update or fine-tune hot areas where trying to make single-pixel corrections could be difficult. We could store overlay data along with an associated picture in a database and then swap combinations of picture and overlay for kiosk systems or other uses. The list goes on...
There are other operations we could perform on our list of hot areas as well, while we have control of that information. For example, we could change the order of the lines of this list if we felt that would organize them better. Of course, reordering the lines of this list has no effect on the functioning of the field or on the id values or other properties of the hot areas, but we may still have our reasons for wanting to put the lines of this list into some specific order.
What does have an effect on the field is adding new lines to this list. We can add or remove lines as needed, which will add or remove hot areas for the field. Now that we know what criteria are required for a hot area and how those property values are defined, specified and organized, we should have no problems creating new hot areas if we know the coordinate positions of the objects on an image that we want to overlay with active areas.
When we have the hot area list configured just the way we want it, we can then reapply it to the $arealist property of our HotPICT field. Here is the line of code that will do the job:
In this statement, the areas item is a list value. It may be a list we had recently extracted from the $arealist property or one we had retrieved from some storage location. The hot areas on our HotPICT field will then be replaced by the ones defined in this list. It couldn't be simpler!
In The Next Issue...
I hope you found the subject this time to be intriguing, at least - and I hope you can find a use for it somewhere in your work.
In the next article we will explore the HWND properties of objects in Omnis Studio to see how we can use them to our advantage.
Omnis Software Ltd. Copyright of the text and images herein remains
with the respective author. No part of this newsletter may be reproduced,
transmitted, stored in a retrieval system or translated into any language
in any form by any means without the written permission of the author
or Omnis Software Ltd.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Omnis Software Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.