Building a New Smartobject

Read any book on UI design and one of the first tenets that you will come across is that the purpose of a UI is to help the user to achieve whatever task it is that they're using your software to try and accomplish. In other words to guide them towards their goal rather than obstructing them and making it difficult to reach their goal. The story goes that users who are able to use a piece of software to accomplish their objectives will feel in control, confident and happy with the software, where as users who have difficulty in using software to accomplishing their objectives will feel frustrated and resentful towards the software.

My own personal experience with developing in Progress bears this axiom out. When I'm "on a roll", head down, hacking software I feel good about Progress and being a Progress developer. When I'm fighting with the ADM trying to get them to behave the way I want, then it doesn't take long at all until I'm tense and frustrated and not at all happy with Progress.

I upgraded from Win2K to WinXP a couple of months ago and one of the first things I noticed is that the Microsoft designers have been giving some thought to the design of dialog boxes. If you drag a shortcut from the desktop to the recycle bin, you now get a dialog looking something like this -

Rather than just saying "are you sure", the dialog now offers you an option to click on a hyperlink to go to a different part of the operating system if you want to perform a slightly different, but related, function - removing the application rather than removing the shortcut. This is a pretty good example of this idea of "guiding" the user through your software, the dialog is acting as a guide an reminding the user that as well as performing the task they've requested, there are other similar functions available.

Needless to say, I started thinking about how I could apply this hyperlink device to my own designs. A couple of days later I was working on the design of a screen which had to allow the user to create a record under certain conditions. The user is prompted to select a tenancy and depending upon the state of the tenancy, they may be able to proceed with the rest of the input. Or they may be in the wrong routine altogether. The first draft of the design simply prompted the user for the tenancy, validated the input and, if the validation failed, popped a dialog box telling them that they couldn't use this routine. I wasn't very happy with the dialogs ( I generally try hard to avoid using them at all ) so when I recalled the WinXP hyperlink dialogs, I did a quick re-design to the dialogs so that they would now provide the user with a hyperlink optionally taking them directly to the routine most appropriate for the status of the tenancy. The third iteration of the design went a bit further and got rid of the dialog boxes altogether and stuck a couple of lines of text and a hyperlink on the main form, so that I could provide feedback and the hyperlink without an intrusive dialog box. The final design looks something like this -

As a design, I'm really quite pleased with it. No matter which of the routines the user ends up in, once he selects a tenancy he will be at most one click away from the correct routine.

The Prototype

Now, how to do a hyperlink in Progress? It turns out that it's pretty straightforward, the trick is to use an editor with the no-box and the read-only options rather than attempting to use a fill-in. Set the colour and the font to make it look like a hyperlink and stick the following piece of trigger code on both the right-mouse-click, mouse-select-click and mouse-select-dblclick events -

editor-1:SET-SELECTION(0,0) IN FRAME {&FRAME-NAME}.

This prevents the user from being able to actually select any of the text of the hyperlink itself. Now you've got an editor that looks an behaves almost like a hyperlink with a couple of noteable exceptions. Firstly, it can still receive focus - click on the link and the cursor will appear within it - and secondly nothing happens when you click on it. Obviously the second problem can be solved by sticking some more code in the mouse-select-click event. The way I worked around the first problem was by adding another widget to the form. I added a 1 pixel by 1 pixel, flat button to the form, set the no-tab-stop property of the editor and added an apply "entry" to . statement to each of the three triggers.

This was all I actually needed to get the design implemented, but I couldn't just leave it there. I'm guessing that now I've thought of it, I'll end up using the hyperlink in a number of places and I don't want to be copying and pasting code throughout the application. The project I'm working on is an ADM2 project, so I just had to wrap it all up into a smartobject.

Building the Smartobject

So, what was involved in wrapping this up into a Smart Object? Well, it took more effort than building the original prototype for sure, but a lot of that was down to it being the first time I'd done this. I'm pretty confident that the next time I come to building a new class, it'll be a lot faster an exercise. I've included a rough guide to the steps involved in the hope that you'll find it useful.

  • Planning. You need to think about what you want to end up with. There are different kinds of Smart Objects. Smart Windows, for example are template based. A copy of the template is taken each time you create a new Smart Window and saved as a new file. This would make it difficult to do something like globally change the font after you've developed a number of windows. Panels, Dynamic Browsers and the Toolbar on the other hand aren't based on a template. There is only one of each of these types of objects although an application will have multiple instances of the object. Changing some aspect of a panel throughout a system is much easier than with a template based object. On the other hand, it's more complex to develop these non-template objects as they have to be developed in a generic manner. For the hyperlink SO, it was an obvious decision to go for the non-template style.

    Another important decision to take is where you're going to place your new object in the Smart Object class heirarchy. Think carefully about this one as this decision will effect the methods and properties your new object will inherit and so will effect the Smart Links which it will be able to support. In my case I needed a visual object but had no requirements to make it support any of the existing links, so it became a sub-class of the visual class.

    You also need to think about how you're going to name your files. One of the mistakes I made was to name the super procedure for the new class hyperlink.p which meant that I had to call the actual object something other than hyperlink.w, I ended up with hlink.w.

  • Generate the Class. Once you've worked out what you want to build and what filenames you want to use, you need to use the "New ADM Class" tool in the appbuilder to generate a new class. I'd suggest that before you run this, you create a new directory somewhere in your source tree for your object and add this directory to the start of your propath. You'll also need to create src/adm2 and adm2 sub-directories.

  • Create a master. If you've selected not to have a template based smart object, then you're going to have to actually create your master object. What I did was to generate a new Smart Frame, then in the advanced procedure settings dialog, I adjusted the type of the Smart Object to "hyperlink" ( with hindsight I propably should have prefixed it with "Smart", see point about planning ). I also had to adjust the method library to use src/adm2/hyperlink.i instead of src/adm2/containr.i, before saving the new object in src/adm2.

  • Get it up an running. I think that the best bet before diving in to the properties and methods is to create a basic test window which includes you new object and simply make sure that you can get the new object running properly. Best sorting out the basics before we start customising our routine.

  • Add your new properties. There are four things you need to do in order to get a new property added to your new object.

    In your property include file, add your new properties to the xcInstanceProperties pre-processor. From hypeprop.i -

    &GLOBAL-DEFINE xcInstanceProperties {&xcInstanceProperties}~LinkLabel,LinkFont,PublishedEvent

    In the same file, but in the Main Block this time, define the &xp pre-processor definitions for the new properties. This step is actually optional depending on whether or not you want your new properties to be accessible through the {get} / {set} mechanism.

    &GLOBAL-DEFINE xpLinkLabel
    &GLOBAL-DEFINE xpLinkFont
    &GLOBAL-DEFINE xpPublishedEvent

    Add new fields to the ADMProps temp-table to support your new events. This temp-table is the mechanism that the ADM uses to track property values for different instances of the object. From the same section as the previous code snippet -

    &IF "{&ADMSuper}":U = "":U &THEN
    ghADMProps:ADD-NEW-FIELD("LinkLabel":U,"CHARACTER":U,0,"","Change the LinkLabel Property":U).
    ghADMProps:ADD-NEW-FIELD("LinkFont":U,"INTEGER":U,0,"",?).
    ghADMProps:ADD-NEW-FIELD("PublishedEvent":U,"CHARACTER":U,0,"","":U).
    &ENDIF

    Lastly, create the get / set functions in the class super procedure. You can pretty much copy these from any other super procedure and simply change the procedure / property names. That's it. If you fire up your test window now, you should be able to use the procedure viewer tool from the pro*tools pallette to ensure that your object is up and runninng and then use the "SmartInfo" button followed by the "Properties" button to view the properties of your object. If everything is hanging together properly, you'll be able to see your new properties in there with the rest. Note that if you've changed the super procedure, you may need to kill the old version if it is still running persistently.

  • Start building your new methods. This is much easier than adding properties. Simply add any new routines to the super procedure, remembering to always refer to target-procedure if you run other sub-routines. Use the procedure viewer tool to kill any old instances of the super procedure and when you run up your test window, you should be able to see your new methods

  • If you've added new properties then you'll probably eventually want to customise the instance properties dialog in order to allow the user to specify some / all of the new properties. The dialog which is run when the developer picks the instance properties menu item int the UIB is controller by the ADM-PROPERTY-DLG pre-processor. Again, from hypeprop.i

    &IF DEFINED (ADM-PROPERTY-DLG) = 0 &THEN
    &SCOP ADM-PROPERTY-DLG adm2/support/hyperd.w
    &ENDIF

    The best approach with these routines is to just started with one of the existing dialogs and modify it to support your new properties.

Simple really, the most complex bit should be writing the code for your new methods or sub-classing any of the existing methods.

Version 0.9 of my hyperlink smart object is freely available from my downloads page. The main reason for the 0.9 label is that most of the methods are actually running in the instance rather that in the super procedure. This would make it difficult to sub-class the hyperlink in order to add your own customisations, so I plan to move all these methods into the super and then release version 1.0.

 

 

What's New