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:


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.


  1. The change was because the old bookmark pointer was stored as PChar, and the PChar meaning was changed to, and I think this is enough to understand the move from PChar to TBytes.

    You can also declare, if it is not there yet
    PBookmark = ^TBookmark;

    save the bookmark pointer, and use PBookmark(Pointer)^ to restore de bookmark.

    • The switch from PChar to TBytes still doesn’t make sense – PChar is an unmanaged type, whereas TBytes is a managed type. If the plan was to keep the size of the pointer elements the same, PByte should have been used.

  2. Steven said

    The reason they made this change was that bookmark was actually a PChar, which in D2009 has changed meaning because of the unicode changes..

  3. warren said

    The use of an unmanaged Pointer type is ‘declasse’, bad style.

    The use of a managed type is much more consistent with the design style changes of the VCL, away from untyped, unmanaged pointers, and towards reference counted or memory-managed types.

    Essentially TBytes is, as you point out, a string type, equivalent to AnsiString, with byte size data elements.

    I for one appreciate the change, and consider any untyped or pointer related features in the VCL to be outmoded, dangerous, unsafe, and just plain ugly.

    The new way is much better.


  4. teo said

    Simplest way that works is keeping a list of record numbers. Easy as that.

    But when you think that implementing 3 events of virtualtreeview is more effective than setting 1 property of a dbgrid, I can imagine that you did not think of this.

    • Record numbers are not implemented on all datasets, so your plan isn’t exactly a winner. Take a look at if you want to see what the default implementation does. Also, cached datasets that load data as and when you request it, almost never supports RecNo.

      But all that is beside the point. If you don’t like the Virtual TreeView use case, try getting multi-select to work on a DBGrid. That requires the use of bookmarks, and your code will most certainly break as a result.

      • Fabricio said

        I’ve always used bookmarks with…. TBookmark! Never look under the hood even with TDbGrid.

        And always avoided to use it outside the same method – and only when is safe assume the record buffer is the same, which is not the case when Refresh method or even some other few methods alter the buffer and invalidating the bookmark.

        Never trusted beyond this on bookmarks. 😉

  5. Tengo el fuente de DBGridPro y no trabaja en delphi 2010 por el cambio en tbookmarkstr esto debe ser informado a los usuarios para que no pasen tanto trabajo al migrar sus fuentes a una nueva version es posible que sea mucho mejor pero demanda mucho trabajo a la hora de migrar en hora buena todos los cambios para mejorar

RSS feed for comments on this post · TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: