This entry is specifically related to my mobile news client, iNews, which can compile for iOS and Android, however it could be applied to any platform that FireMonkey supports, e.g. Windows and OSX.
When creating a mechanism for subscribing to groups available on a news server, I needed to figure out a way of the user being able to check or uncheck groups that they wanted to subscribe/unsubscribe to. Enter the EditMode property of TListView. When EditMode is set to True, a “checkbox” becomes visible in front of each item.
Because EditMode is being set to True after the items have been loaded, I figured I’d need to iterate the items and set the Checked property of each item depending on whether a flag was set in the database for that group. I thought no problem, I’ll just set ItemIndex to the loop variable and the dataset will be pointing to the corresponding record, right? Wrong; that only happens if a user actually clicks the item; probably a good thing. So I looked for a way of “faking” the item being clicked, without success. Then I looked for what the code does when a user actually clicks the item. Eureka! TLinkObservers.SelectionChanged causes the dataset to point to the correct record. Sadly, it became clear that doing this when iterating over all items is a performance killer, especially on the device.
Fortunately with the help of Jim Tierney, I discovered I was looking at this from the wrong way. With LiveBindings, you can create a link between the dataset and the Synch binding of the TListView. This causes the TListView to be populated with the records in the dataset, and synch the record pointer with the item in the TListView without having to write any code. When the item is being populated, LiveBindings calls the OnFilledListItem event on the link (which is a TLinkListToControlField link). There, I could check the flag and set the corresponding item’s Checked property, since the record pointer is at the record in question:
procedure TfrmMain.LinkListControlToField2FilledListItem(Sender: TObject; const AEditor: IBindListEditorItem); var LItem: TListViewItem; begin if not FGroupSubscribeUpdate and FSubscribeMode and (AEditor.CurrentIndex >= 0) then begin LItem := lvGroups.Items[AEditor.CurrentIndex]; LItem.Checked := dmNews.qryGroupsSubscribed.Value = 1; end; end;
I also needed to find a way to update the database when the user actually checked/unchecked the item. The answer is to use the OnButtonChange event:
procedure TfrmMain.lvGroupsButtonChange(const Sender: TObject; const AItem: TListViewItem; const AObject: TListItemSimpleControl); var Button: TListItemGlyphButton; begin if FSubscribeMode and (AObject is TListItemGlyphButton) then begin Button := TListItemGlyphButton(AObject); if Ord(Button.Checked) <> dmNews.qryGroupsSubscribed.AsInteger then begin FGroupSubscribeUpdate := True; try dmNews.qryGroups.Edit; dmNews.qryGroupsSubscribed.AsInteger := Ord(Button.Checked); dmNews.qryGroups.Post; finally FGroupSubscribeUpdate := False; end; end; end; end;
You’ll note that I’m using a flag called FGroupSubscribeUpdate. This is because when posting the record, LiveBindings calls the OnFilledListItem event, and in this case I don’t want the Checked property being reset. I also use a similar technique for assigning a bitmap to items in the message list, to indicate that there are attachments with it:
procedure TfrmMain.LinkListControlToField3FilledListItem(Sender: TObject; const AEditor: IBindListEditorItem); var LItem: TListViewItem; begin if AEditor.CurrentIndex >= 0 then begin LItem := lvMessages.Items[AEditor.CurrentIndex]; if dmNews.MessageHasAttachments then LItem.Objects.ImageObject.Bitmap.Assign(imgAttach.Bitmap); end; end;
I hope the information above will be useful for anyone interested; it took me some time to work out what to do, however I felt I should share it.