Posts Tagged bookmarks

Delphi’s broken bookmarks

Delphi 2009’s blue chip features were Unicode support, generics and anonymous methods. Most developers expected Unicode support to be the one to introduce the bulk of the migration headaches. But as with most Delphi versions, Delphi 2009 also saw a number of changes that didn’t quite make it into the marketing material.

Perhaps I’ll get into some of the beneficial ones in a later post – possibly just in time to follow it up with a Delphi 2010 version. But for now I’d like to look at one of the more perplexing ones.

Delphi’s TDataSet class has for the longest times supported bookmarks. If you still waste your time with TDBGrid, you’ll use these whenever you allow the user to multi-select.

Myself, I use these extensively with TVirtualStringTree – one free control worth a serious look if you don’t use it already – to display TDataSet records as rows in a grid.

So on Delphi 2007 and before, you used bookmarks like so:

Bookmark := CDS.GetBookmark;

and a little later:

CDS.GotoBookmark(Bookmark);

where CDS is a TDataSet – in my case usually a TClientDataSet. And Bookmark is a Pointer.

On Delphi 2009 this doesn’t quite work as expected. You see, in Delphi 2007 GetBookmark returned a Pointer and you would declare Bookmark as a Pointer.

In Delphi 2009, GetBookmark returns a TBookmark, which is an alias for TBytes. And TBytes is a dynamic array of Byte. If it hasn’t sunk in yet, read it again.

That’s right. In Delphi 2007, GetBookmark returned a regular, unmanaged pointer. In Delphi 2009, it returns a managed dynamic array pointer.

So what? Well, in the past you would get the bookmark and do with it as you please until you were done with it. And you could still do the same, providing that you declare my Bookmark variable from above as TBookmark.

If you don’t, the bookmark gets freed at the end of the method that you called GetBookmark from. Which makes it impossible to return to the bookmarked record from another record.

In case you don’t know Virtual Treeview, here’s the rundown: You add nodes and attach data to those nodes. Instead of storing strings, the tree would store only the data you give it. And the text displayed in the grid gets picked up in event handlers and not stored anywhere.

So if you simply used to store the pointer to the bookmark (like me) you’d suddenly keep getting annoying “Record not found” exceptions.

I can think of two possible solutions. The first is obvious – declare your bookmark as TBookmark. But that doesn’t work in my Virtual Treeview scenario and there are possibly many other people who stored bookmarks as untyped pointers in TList or something similar. In that case, you should simply cast when you do the assignment:

TBookmark(Bookmark) := CDS.GetBookmark;

or in Virtual Treeview:

Bookmark := nil;// Required so your assignment doesn't attempt to free a non-existent array
TBookmark(Bookmark) := CDS.GetBookmark;
Tree.AddChild(<strong>nil</strong>, Bookmark);

It’s a simple fix, but one that you will need to make at every place where you store a bookmark. Either that, or changing the bookmark declaration.

I can’t even begin to guess what possessed our friends at Embarcadero to do this. In fact, I don’t quite see what they intended to fix by making the change. If the idea was to replace the untyped pointer, then PByte – an unmanaged type – would be a far better alternative to TBytes.

But it’s an undocumented caveat. Or it was – until now.

Advertisements

Comments (8)