Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

by Unknown user

Read Part 3

First, some housecleaning. Mike Hanson pointed out that I needed a couple of additional lines of code (5 and 6) in my TakeAccepted handler:

...

Those two lines of code restrict the click operation to just the icon area; without them clicking anywhere in the cell that contains the icon would workchange the icon.  

But there's another problem with the original method - I'd neglected to return a value, yet the Register function docs specifically say you must return one of three values from the function:

Level:Benign

Calls any other handlers and the ACCEPT loop, if available.

Level:Notify

Doesn't call other handlers or the ACCEPT loop. This is like executing CYCLE when processing the event in an ACCEPT loop.

Level:Fatal

Doesn't call other handlers or the ACCEPT loop. This is like executing BREAK when processing the event in an ACCEPT loop.

I always want processing to continue normally, so I'm returning Level:Benign.

I also had a suggestion from Greg for a toggle capability, so I added a ToggleAll method. In an effort to reuse as much code as possible, I had ToggleAll call the SetAll method with an equate indicating all values should be toggled:

Code Block
TrueValue                                       equate(1)
FalseValue                                      equate(2)
ToggleValue                                     equate(3)
 
...
 
CML_UI_ListCheckbox.ToggleAll                   procedure
    code
    self.SetAll(ToggleValue)

The SetAll method now looks like this:

Code Block
CML_UI_ListCheckbox.SetAll                      procedure(bool flag)
x                                                   long
    code
    loop x = 1 to records(self.ListQ)
        get(self.ListQ,x)
        if flag = ToggleValue
            self.ToggleCurrentCheckbox()
        else
            self.ListQIconField = flag
        end
        put(self.ListQ)
    end

There's a call to a new ToggleCurrentCheckbox method, which is marked private because it relies on the correct queue record being in memory, something that's under the class's control:

Code Block
CML_UI_ListCheckbox.ToggleCurrentCheckbox       procedure
    code
    self.ListQIconField = choose(self.ListQIconField=TrueValue,FalseValue,TrueValue)

This method is now called from TakeMouseClick, so the Choose logic exists in just one place. It doesn't really save any lines of code, but it does improve reuse and that's always something worth pursuing as it enhances maintainability.

And finally I added something that may only be practical in its current form for simple list boxes (and not ABC browses): the ability to press the space bar to toggle an entry and then advance to the next item. 

I added a Bool property (see Equates.clw - Bool equates to Signed which equates to Short) called ToggleAndAdvanceWithSpaceKey, and in the Initialize method I added the following code which sets SpaceKey as an Alrt attribute on the list box and then registers an event handler, similar to the way I registered a handler for icon field selection. (Oh, and I updated the handler call by adding the list box FEQ as the last parameter, so the handler is only called for the list box not for all controls.)

Code Block
    if self.ToggleAndAdvanceWithSpaceKey
        self.ListFEQ{PROP:Alrt} = SpaceKey
        register(EVENT:AlertKey,address(self.TakeSpaceKey),address(self),,self.ListFEQ)
    end

Here's the handler:

Code Block
CML_UI_ListCheckbox.TakeSpaceKey                procedure!,byte
    code
    if keycode() = SpaceKey
        get(self.ListQ,choice(self.ListFEQ))
        self.ToggleCurrentCheckbox()
        put(self.ListQ)
        if pointer(self.ListQ) < records(self.ListQ)
            select(self.ListFEQ,choice(self.ListFEQ)+1)
        end
    end
    return Level:Benign

And here's the current demo with the Toggle All button:

Image Added

As long as I don't get distracted with adding any more bells and whistles to the UI component, it's just about time to start thinking about data persistence. How can I save and load checkbox data? 

As I've said before, my usual approach with non-UI code is to start with a unit test, but in this case I think applying this class to an ABC browse will help to illustrate some of the potential issues I need to consider.

ABCTest.app

I've created a simple app with a single ABC browse (included in the source zip). 

It has these lines in the AfterGlobalIncludes global embed point:

Code Block
include('CML_UI_ListCheckbox.inc'),once
include('CML_System_Diagnostics_Logger.inc'),once

All the classes included in the ClarionMag Library follow the same convention: by default, any app that uses these classes will expect to find them in a DLL. But I'm doing testing and development, and in those circumstances I usually compile the classes along with whatever app is using them, whether it's an executable or a unit test DLL. And that means adding 

_Compile_CML_Class_Source_=>1

to the Conditional Compile Symbols in the project properties:

Image Added

This probably isn't something you'd do with your own classes; in fact there's a good chance that when you write classes you'll compile them for each app. That's how I started using classes also, but eventually I ran into problems when sharing classes between apps (usually DLLs) in a large, multi-DLL system. You can read more about the ClarionMag Library compiling approach in Look Ma! No project defines!

The ABC browse procedure has one declaration in the data embed:

Code Block
ListCheckbox        CML_UI_ListCheckbox

And one line of code at the end of the ThisWindow.Init method:

Code Block
    ListCheckbox.Initialize(Queue:Browse:1,Queue:Browse:1.Checked_Icon,?Browse:1)

But how did I know what parameters to use? I had to go looking in the generated source, which is a bit funky. This would be an excellent job for a template and would avoid potential typos. 

The first parameter, as you'll recall, is the queue that provides data to the list box. 

The second is the Long field following the field that will be used as a checkbox. So I did have to go into the list box formatter for the browse and add a local variable and configure it as having a checkbox:

Image Added

In my simple hand coded test I defined the queue myself and added the extra Long field. In the ABC browse, enabling the Icon property caused the templates to generate that extra field.

Within the browse procedure I only had to add two lines of code to get the checkbox effect. 

The first time I ran the app, however, I got an odd effect. No icons showed at all until I clicked on the icon's location:

Image Added

I found the cause in the browse's SetQueueRecord method:

Image Added

The available icons are determined by the class's Initialize method, using PROP:IconList assignments. A value of zero indicates no icon at all. 

I solved this by added

SELF.Q.Checked_Icon = 2

in the last embed so the unchecked icons display by default. 

With the modification the browse works as expected, with one proviso.

Image Added

This is a page-loaded browse, and the queue that holds the data in the list box at any one time is only the data you see on screen. So if you check a checkbox and then scroll so the checked line is off screen, when you scroll back to the line again the checkbox has been cleared. That makes total sense, since the class is only tracking the items that are currently displayed. 

You can solve this by creating a file loaded browse where all records are in the queue, but that isn't ideal for every situation. A better solution is something that can handle both file and page loaded list boxes. 

And that sets the stage for persisting data... 

Download the source

CheckboxUI2.zip