Best programming practises: the ListBox

if you have some tips to share then this is the place - please create new threads for other controls and classes!

When I started out with REALbasic / Xojo I used the ListBox both to store and display data. It worked but the code was cumbersome and hard to maintain and expand as everything tended to be hardwired like this:

ListBoxEmplyees(0, 0).cell = TextFieldName.text
ListBoxEmplyees(0, 1).cell = TextFieldFirstName.text
ListBoxEmplyees(0, 2).cell = TextFieldStreet.text
ListBoxEmplyees(0, 0).cell = TextFieldCity.text

Nowadays I donā€™t pass strings around anymore, I use objects like this:

// create new Employee object
dim Employee as new cEmployee

// set properties of Employee object
Employee.name = TextFieldName.text
Employee.FirstName = TextFieldFirstName.text
Employee.Street = TextFieldStreet.text
Employee.City = TextFieldCity.text

and then I simply pass around those objects:

// store Employee object in array
Window1.ListOfEmplyees.append Employee

// store Employee object in ListBox RowTag
// first create a new row in the ListBox
ListBoxEmplyees.addRow ""
ListBoxEmplyees.RowTag(ListBoxEmplyees.LastIndex) = Employee

Note that there is only ONE object, and that it is only a pointer to that object that is stored in the ListOfEmplyees and a RowTag of the ListBox

Now I want my objects to be able to draw themselves, so they need to have a Draw method. I could simply add a Draw method to the object itself, but a better way is to define an interface CanDrawItself with two methods DrawAsList and DrawAsBusinessCard, and pass a graphics object to them for them to draw themselves on:

Public Sub DrawAsList(g as Graphics)
  // actual drawing code
End Sub

Public Sub DrawAsBusinessCard(g as Graphics)
  // actual drawing code
End Sub

Of course you can have many more drawing methods.

The advantages of using an interface are manyfold. For one thing - and very unlike a subclass - you can add it to many different objects! So all you have to do now in the Listbox CellBackgroundPaint event is to pass the ListBox cellā€™s graphic parameter g to the draw method of the object in the RowTag like this (in pseudo-code ā€¦ note the Casting):

if PopupDrawSelection.text = "List" then
  ObjectType( me.RowTag( row ) ).DrawAsList( g )
end if

if PopupDrawSelection.text = "Business Card" then
  ObjectType( me.RowTag( row ) ).DrawAsBusinessCard( g )
end if

and it will draw on the graphics of the cell.

Um. One moment? ObjectType? How do I know what kind of object is in the RowTag???

Thatā€™s the beauty of it: it doesnā€™t matter what type the object is because any object that implements the interface can be called as being of that type like this:

If row < Me.ListCount Then
  CanDrawItself( me.RowTag( row ) ).Draw( g )
end if

With this is becomes easy to make a ListBox like this (note that this ListBox uses just one column):

Note that there are THREE different types of rows: one is based on a class cHEADER, one on a class cDEPARTMENT, and one on a class cEMPLOYEE. Each of them implements the CanDrawItself interface and draws itself in a one column listbox according to the code in its draw method.

A longer version of this will be an article in the forthcoming issue of xDev magazine, and the code will be on GitHub afterwards ā€¦

7 Likes

Excellent tip @MarkusWinter. Iā€™m a big fan of using interfaces as a form of abstraction but I had never thought of using them for rendering ListBox items.

If you have xDev then you should read Marc Zeedarā€™s SuperDraw article in xDev 12.5 - it revolutionised my approach to graphic programming.

1 Like

One should also note that the kind of listbox handling Markus proposed will also make your listbox perform much faster in case you have a long list to add. The string handling that is usually done with AddRows is often a time-consuming task, and if you donā€™t employ some chunked loading, can cause the user to wait or even see the spinning ball of possible GUI death.

With custom CellTextPaint events, you reduce that task to only the visible cells that have to be redrawn. (And of course adding empty strings and a tag object which is much faster.) Can create an enormous performance gain when you handle lots of data.

1 Like

And with a bit of extra work you can even delegate the event handling to each row and have different handling per row depending on what kind of tag it has attached to it

Could you explain that a bit more? After conquering the ā€œobserver patternā€ the ā€œdelegatesā€ are next on my list - I still donā€™t really get them. Iā€™m sure it will be a simple thing like with the ā€œobserver patternā€, but until I get it Iā€™m never sure how to properly use it.

That sounds interesting @npalardy ā€¦

Its really just an extension of Alex Restrepoā€™s custom listbox drawing code but I also pass on the events like keydown, mouse move, paint etc so not only can a cell have custom drawing it can also have custom handling of these events

Every rowtag that I add is a subclass of RowTagDelegate and the base class has methods for click keydown etc
The only one that is truly a pain to try & intercept is the in cell editing of text as you canā€™t delegate that because you cannot intercept the beginning of the edit :frowning:
I think I put in a feature request to let this be possible but I have very low confidence it will ever get implemented.

I really need to take a look Alexā€™s class a little deeper. That guy was a smart cookie. Loads of his controls are really well engineered. What ever happened to him?

1 Like

He moved on
He is /w as a grad student and Iā€™m not sure if he wrote some of this as a thesis or just for fun
Never got to know him that well to be honest
But he did have a lot of fun code

Ahh. Jetzt hats gschnackelt!

Do you mean subclass the ListBox, add the events to each RowTagā€™s object, and raise those events from the corresponding ListBox events?

You can get the same effect by using introspection and a select case statement depending on the type of object (less elegant though).

yes
I dont use introspection for this

From an OO perspective select case to determine what to do is a good place to have a generic super and specialized subclasses so polymorphism just does its thing as you expect :slight_smile:

You can download the source code from the xDev Mag website:

http://www.xdevmag.com/browse/18.4/archive18.4.zip

You might also want to check out the 18.2 issue in which I demonstrate how to merge rows and columns (though I left it to the reader to make the solution general). Source code link:

http://www.xdevmag.com/browse/18.2/archive18.2.zip

I think I know how to do that! :wink:

-karen

1 Like

Certainly better than I do. Thatā€˜s also one reason why I didnā€˜t make the solution more general (the other one being that I prefer the interface method).

Just a little preview on the new version Iā€™m working on for xDev - still a work in progress but itā€™s still nicely object-oriented and letā€™s you flip between hierarchical and not hierarchical:

Iā€™ll post the code when it is done.

3 Likes

Excellent Tutorial @MarkusWinter thank you sir.

Did you ever put this on GitHub? Couldnā€™t find it.

I wonder if after the shit storm Markusā€™s article stirred p he just said to hell with it ?