Home arrow Articles arrow Paradox Programming arrow Conditionally Colouring Records
09 September 2010
 
 
Conditionally Colouring Records PDF Print E-mail
Written by Jan Stegehuis   
05 September 2007

Preface

I have included a fully functional demo (Paradox® 9), based on the technique described below. After downloading into the folder of your choice, make that folder :WORK:, run the form and follow the instructions. The form allows to select individual records and indicate those with a different colour.

 

 

Introduction

I have many times wanted to be able to colour tableRecords in a tableFrame, based on certain conditions: like a certain value in one of the recordFields or to indicate, that the user selected them for some reason. When I started working with Paradox I tried to get this to work, but failed to let the colours stick to their own records. One scroll and everything was out of place.

The years after, I never tried again, convinced that it could not be done and/or was too complicated to be of any real use. And I certainly was not about to place any real amount of code in things like the newValue event. Much too tricky for my taste.

A short time ago however, I had to solve a problem with a form and working on that I realised that I had been wrong al that time and that it cóuld be done. And that it actually was not too complicated at all. I simply had been going about the problem the wrong way; looking at it from a wrong mindset.

 

BackGround

Somewhere in my mind I had stashed the information, that the tableFrameRecord was 1:1 the same as the tableRecord. Well, it isn't. The tableFrameRecord is a uiObject, that acts as a window for 1 tableRecord at a time. If you set a record’s colour using oPal, you set the colour of the record-uiObject; nót that of the tableRecord. The tableRecord hás no colour property.

After a scroll the record-uiObject is still the same, it only acts as a window for a different tableRecord. If you had coloured it before, then it will now probably have the wrong colour and you will have to reset/recolour it.

 

When running a form, the record-uiObject gets replicated as many times as will fit in the tableFrame, whereby each of it’s occurrences gets it’s own uiObject-name. So there are not as many record-uiObjects as there are tableRecords; there are only as many as will fit in the frame. Each record-uiObject acts as a row for the tableFrame grid.

Knowing the names of the different record-uiObjects, you can approach each of them separately and set it’s properties. These are the properties (and methods and events), that are surfaced through the object inspector. During runtime however, the record-uiObject also gives access to the properties of the underlying tableRecord. And to those of the fields of that tableRecord.

 

Panting the rows.

So if you know the (uiObject)names of the individual rows, then you can colour them at wil, based on some sort of condition. There is however an easier and more flexible way, to do it, using the First and Next properties of the tableFrame and it’s “children”. So all you need, to colour the rows of any tableFrame at any point in time, is this little piece of code:

 

proc paintRows()

 

var

      tableChild          uiObject

      loopSwitch        logical

endVar

 

loopSwitch = true

tableChild.attach(myTableFrame.first)

while loopSwitch

      if tableChild.class = "Record" then

                  switch

                  case     tableChild.myField.value meets condition

                        :     tableChild.color = red

                  otherwise

                        :     tableChild.color = white

            endSwitch

 

      endIf

      if tableChild.next = blank() then

            loopSwitch = false

      else

            tableChild.attach(tableChild.next)

      endIf

endWhile

 

endProc

 

Remarks

  • As soon as tableChild is attached to the row, it not only gives you access to the properties of the row (tableChild.color), but also to the underlying tableRecord and it’s fields (tableChild.myField.value), using normal dot-notation.
  • The first child of the tableFrame normally (but not always) is the header, so check the Class property to see if a child is a record.
  • You can use this same logic, to colour individual fields of a record; i.e. the field-uiObject that shows the value of a particular field (tableChild.myField.color = red).

 

Catching the scroll (readonly table)

The above piece of code works fine if you can call it every time a scroll took place. Paradox unfortunately does not surface a scroll-event, so you have to simulate one yourself. This is not too difficult either. The first thing that happens after a scroll, is an arrive on a new record. So all you need to do, is to catch the arrive and see if the record underneath the first row is still the same record as it was at the previous arrive-event. If it isn’t, the tableFrame scrolled it’s records. The next piece of code will do the trick.

 

The tableFrame’s Var window

var

      firstRecord                          uiObject                       ;// the first record-uiObject (row) in the frame

      firstRecordNo                      longInt                          ;// the tableRecord underneath firstRecord

endVar

 

The tableFrame’s Open event

var

      tableChild          uiObject

      loopSwitch        logical

endVar

 

method open(var eventInfo Event)

 

doDefault

loopSwitch = true

tableChild.attach(myTableFrame.first)

while loopSwitch

      switch

      case     tableChild.class = "Record"

                  :     firstRecord.attach(tableChild)

                  firstRecordNo = firstRecord.recNo

                  loopSwitch = false

      case     tableChild.next = blank()

            :     msgStop("program error", "there are no records in this tableFrame")

                  loopSwitch = false

      otherwise

            :     tableChild.attach(tableChild.next)

   endSwitch

endWhile

 

endMethod

 

The record’s Arrive event

method arrive(var eventInfo MoveEvent)

 

doDefault

If firstRecord.recNo <> firstRecordNo then

      firstRecordNo = firstRecord.recNo

      paintRows()

endif

 

endMethod

 

Remarks

  • The Paradox documentation warns for using the recNo “property” on dBase tables. So you better reserve this technique for Paradox tables only.
  • Instead of using the record’s Arrive event, you could catch the “dataArriveRecord” Action event. Contrary to most other action-events, this one does not bubble up the container hierarchy. Outside the tableFrame, you will have to catch it at the form’s prefilter level.

 

Catching the scroll (editable tables)

The above code is sufficient for readonly tables; for editable tables however, more is involved. Thére, also things like partial scrolls occur as a result of inserting and deleting records from the table. You can’t catch these, using the code above, since the record underneath the first row doesn’t change. You have to catch the Insert- and Delete events themselves and call paintRows() there. For an example on how to do that, check the code in the demo form.

 

There is one remark however, that I want to make on that subject. When Paradox inserts a new record, it completely locks the entire tableFrame. As soon as you arrive at that newly inserted blank record, there is nó way, you can do ánything with any of the other records. Not even something as simple as checking a fieldvalue.

Still you need to repaint the rows and there is only one way to do that: paint them befóre you let the insert take place. Catch the dataInsertRecord action, paint the rows beforehand and then let doDefault do it’s thing. But you can’t do that with the paintRows() method above; you need an adapted version, that is capable of colouring one-out-of-sequence (see the proc tableBox.paintRowsBeforeInsert() in the demo form).

 

Appending new records is yet another story. If you don’t need it, I would advise to set “autoAppend” for the table to “False. The demo form does support it, but I don’t know, how stable it is in exceptional cases. Appended records simply show up, they don’t have the “Inserting” property set to True and they don’t trigger a dataInsertRecord action. Not until the user types his first character in the first field. The only way you can distinguish this situation, is the fact, that the active (= the appended) record has it’s BlankRecord property set to True. This property normally means that there is no tableRecord underneath the record; a situation that normally only occurs when there are more rows in the tableFrame, then there are records in the table.

 

Paradox oddities (important!)

Paradox would not be Paradox if it would not have a thing or two, that makes something simple not really want to work the way you would expect it. That is also the case for this technique and it has to do with setting the colour of objects in connection with the keyboard focus. Something that gives problems in other area’s to.

 

Paradox seems to be moving an underwater focus around in order to change the colour of it’s uiObjects. And sometimes it seems to get confused about what the focus was, that it moved. In those cases it moves the keyboard focus away from what should be the active field or it looses track of the keyboard focus altogether. Two examples:

 

If you open the form and after the doDefault of it’s Open event do an initial colouring of the rows, you will find the keyboard focus on the first field of the last row in the tableFrame. There is no reason whatsoever why this should be the case, but it still is. So after the initial paintRows() you have to use myTableFrame.home() to send the keyboard focus back to where it belongs. It is only during the Open that this happens. During normal operation all seems to work fine.

 

When during normal operation, you do something with an object outside the tableframe, that causes a paintRows() within the tableFrame, Paradox looses track of the keyboard focus. It will still indicate the active field as having the focus, but it will not respond to keyboard commands like Up, Down, Left and Right anymore. I found this one out, when putting a button on the form, that allows the user to set his selected records back to “none”, which causes a repaint.

You have to grab the mouse and click within the tableFrame, to get the focus back to work or do something like press F2 (toggle FieldView). Of course you don’t want to burden your users with this. In the demo I use oPal to do an active.postAction(EditToggleFieldView) twice after each paintRows(), in order to get the keyboard focus back to it’s normal operational state. I haven’t found any real problems with this “workaround” yet. In more intricate forms however, it might possibly become a problem of it’s own.

 

Last Updated ( 05 September 2007 )
< Prev   Next >
 
Top! Top!