I've read FAQs, I've read Borland's docs, I've read a couple of books on
Delphi, and now that I need to sit and write code, I have a component
writer's block :)
I've written simple non-visual components before and have modified custom
visual components, but I haven't written a visual component from the ground
up. I thought a custom listbox would be a good place to start, not too
hard, and lots of freeware w/source components to learn from. I need a
listbox that supports images and checkboxes, as well as some fancy custom
drawing, so that it can do this:
http://www.lodz.pdi.net/~eristic/yyy/stylelistbox.gif
(Just using OwnerDrawFixed with an OnDrawItem event isn't enough, because I
need to handle CN_DRAWITEM in order to suppress the focus rectangle.)
I expected drawing to be the hardest part (never used canvas before), but
it was actually the easiest to figure out. The hard part is how to maintain
state objects along with the listbox's items and make sure they're always
valid and won't get accidentally clobbered by outside code. I've looked at
several free components from Torry's and DSP, and used some of them in my
apps before, but now that I'm actually *analysing* how they work, I'm
seeing some rather suspicious stuff there :)
The major problem is this: the listbox needs to maintain state of its items
(checkstate, imageindex, etc). At the same time, I want the Items.Objects
property to remain available. I would use a separate TList, but it's
impossible to keep it in sync with Items - if Items get sorted, for
example, my "state list" will be out of touch with reality.
So, the state objects must not be in a separate list; they must be stored
*in* Items.Objects. I thought of descending from TCustomListBox and storing
state in Items.Objects, as
TStateObject = class
CheckState : TCheckBoxState;
ImageIndex : integer;
Data : TObject; // for user data
end;
I figured out how to make sure these objects are created and disposed
properly whenever items are added/inserted/deleted/cleared in the list. But
this approach won't work if someone simply does
MyListBox.Items.AddStrings( SomeStrings )
because at this point my previous state objects will be dropped and
replaced with whatever objects SomeStrings has. After that, a reference
such as
TStateObject( Items.Objects[i] ).CheckState
will kill the listbox.
The only way to prevent this from happening would be to override the Items
accessors so that Items cannot be manipulated directly. I've done it:
function TmyListBox.GetItems: TStrings;
begin
raise EMyListBoxError.Create( 'Cannot access Items property directly.' );
end;
It works, but it's also counter-productive and would annoy terribly anyone
who tried to use the listbox (myself included).
Next try: descend from TWinControl and create my listbox closely following
VCL, with modifications where appropriate. But this leads to the same
problem as above, as long as I have an Items property which points to some
TStrings object under the hood. It begins to look like an imperfect OO
design: the property exposes a class (a TStrings descendant), allowing
anyone to fiddle with the class outside of the owner's (listbox's)
knowledge or control. The TStrings class, TListBoxStrings specifically,
doesn't even have any notification mechanism (like the TList has).
Last attempt: descend from TCustomControl and write my own listbox
implementation from scratch. Don't use TStrings for items, use a custom
TCollection or TList instead; give access to items in a more controlled
manner than TListBox does. Well, here the problem is that I don't know how
to :) To what extent am I controlling the listbox, and to what extent is
Windows doing its own job? Specifically, if I descend from TCustomControl,
should I still pretend I'm writing a "normal" listbox and subclass it via
CreateSubClass(Params, 'LISTBOX')? If I do that, and not have items in a
TStrings class, will Windows like it? If I don't, will my listbox get all
the LB_xxx messages it needs? What about the styles set in CreateParams -
should I use LBS_HASSTRINGS if I really don't have "strings"? And is it
really necessary to go this far just to have checkboxes in a listbox?
(I've seen the VCL implementation of TCheckListBox, and it uses a rather
tricky runaround with a class named TCheckListBoxDataWrapper which I
haven't quite figured out yet.)
At this point, I'm really stumped. The components I've seen usually take
the easy route and wedge state objects onto Items.Objects (often neglecting
to dispose of them!) but this technique seems very dangerous, it can be so
easily subverted by whoever uses the component. I've also thought of a
"virtual" mode, e.g. the DrawItem routine could get state for each item via
an event, but this will only work for image index (which is constant), bnit
for states of checkboxes, which change with user actions (i.e. the listbox
must maintain them). So, what's the right thing to do? (Except for going
back in time to my college days and choosing a different major - I studied
English, so don't laugh ;)
Thanks in advance,
.marek
--
/"\ ASCII Ribbon Campaign - Say NO to HTML in email
\ / Homepage, PGP Public Key: http://www.pdi.net/~eristic/
X No ads, no nags freeware: http://www.pdi.net/~eristic/free/
/ \
"Most of what I've learned over the years has come from
signatures." (Larry Wall)