2

I have a LiveBindings databound FMX.TListView with the FieldName being Stage and the FillHeaderFieldName being Production. When the app is running, I see a list of Productions using the HeaderAppearance, and within each Production, there is a list of Stages using the ItemAppearance. I've turned on SearchVisible to get the components search panel to show at the top of the list.

enter image description here

Currently, typing into the search box only filters on the Stage, and not the Production.

enter image description here

I'd like to be able to do both, and I'd like to be able to do it without making another REST call with filter parameters. I understand I would probably need to write an event handler for the OnSearchChange event, and I have this bit of code to get the search text entered:

  List := Sender as TListView;

  for I := 0 to List.Controls.Count-1 do
    if List.Controls[I].ClassType = TSearchBox then
    begin
      SearchBox := TSearchBox(List.Controls[I]);
      break;
    end;

And I think I need to set the Items.Filter property, and I used this bit of code:

  Lower := LowerCase(SearchBox.Text.Trim);

  List.Items.Filter :=
    function(X: string): Boolean
    begin
      Result:= (Lower = EmptyStr) or LowerCase(X).Contains(Lower);
    end;

One of the problems is that the ListView component is applying its filtering as soon as a character is typed, while the OnSearchChange event only fires when the searchbox loses focus.

The second problem is that, even after the event is fired and the new filter function set, nothing happens to the list.

I've confirmed that the List.Items collection in my "36" example does actually contain all 6 items - the 3 header items and the 3 detail items - so I'm unsure why the filter is not applying to the header items as it does the detail items.

Mike Torrettinni
  • 1,816
  • 2
  • 17
  • 47
SiBrit
  • 1,460
  • 12
  • 39
  • I think I have done something like what you are looking for, but I cannot find the code. I added a calculated field to the dataset that concatenates (with an improbable separator, like the pipe char) all the fields I want to filter the listview on. I then binded that field to the listview to search on. The listview actually had a custom layout with several fields in different positions, fonts, sizes, etc... but I wanted to filter on all of them. But the field used for searchin IIRC does not necessarily have to be shown. – Frazz Aug 26 '19 at 09:23
  • @Frazz. Thanks. I'll give that a go. Add a hidden field containing both the Production and Stage text, then set that as the search field... If I knew how to do that. I can only surmise that the built-in search only works on whatever is selected as the FieldName under the LiveBindings (or Item.Text when viewing the diagram for Bind Visually...) – SiBrit Aug 27 '19 at 02:34
  • Well. Not made any progress on this. Can't find any documentation on how to have a LiveBindings TListView search on any field, except the one chosen for the Item. I can't even ask Embarcadero for development support because they just send me to their Idera community forum (which I has posted to as well). I suppose I might end up having to remove Live Bindings and fill the TListView manually, but that'll be a lot slower. – SiBrit Aug 28 '19 at 20:49

2 Answers2

3

I tried this out and found a solution. Keep in mind I don't have access to Delphi 10.3 Rio. I'm using 10.1 Berlin. Also keep in mind that what I usually do is bind in the code and not visually. But for this I stuck to visual binding.

As a dataset I have used a TFDMemoryTable (mt1) with 2 data fields (fmt1Prod and fmt1Stage) and 1 calculated field (fmt1Search). I have the following handler to calculate the Search field:

Procedure TForm2.mt1CalcFields(DataSet: TDataSet);
Begin
  fmt1Search.AsString := fmt1Prod.AsString + '|' + fmt1Stage.AsString;
End;

I put some random data in the memory table OnFormCreate:

Procedure TForm2.FormCreate(Sender: TObject);
Var
  i1, i2: Integer;
  s1, s2: String;
Begin
  mt1.CreateDataSet;
  For i1   := 1 To 13 Do Begin
    s1     := 'Prod' + FormatFloat('00', i1);
    For i2 := Random(6) To Random(14) Do Begin
      s2   := 'Stage' + FormatFloat('00', i2);
      mt1.Append;
      fmt1Prod.AsString  := s1;
      fmt1Stage.AsString := s2;
      mt1.Post;
    End;
  End;
End;

I have put on Form2 a TGrid and a TListView. Both are bound to the dataset. Data and calculated fields show up properly in the TGrid (just to check).

The TListView is bound to the dataset as follows:

Synch            <-> *
ItemHeader.Text  <-  Prod
ItemHeader.Break <-  Prod
Item.Text        <-  Search
Item.Detail      <-  Stage

I did this because I cannot find a way to have the TListView searchbox work on anything but the Text of the items. Ok then... but this can be worked around though:

  • Set TListView.ItemAppeance to Custom
  • Find the TListView/ItemAppearance/Item/Text object in the structure and set Visible to False
  • Find the TListView/ItemAppearance/Item/Detail object in the structure and set Visible to True

I'm not sure all of the above is necessary, but it works. If your TListView is editable then you will probably need to fiddle with the ItemEditAppearance too.

Remember that with custom item appearance you can actually set the list view items to look just about anyway you want. You can add and remove labels, images and other things. It's not as powerful as designing a form, but you can do a lot with it. But all you really need here is to hide the search text and show the stage text somewhere in the item.

And... for more sophisticated item appearance you may have to do some code binding (non sure of this though).

Frazz
  • 2,995
  • 2
  • 19
  • 33
  • Thanks @Frazz. Won't be able to go through your answer today, but I will on Monday. I like the idea of changing to the Custom appearance for the displayed item, and hiding the actual item, which search is looking at. If it works, I'll be sure to accept your answer. – SiBrit Aug 30 '19 at 00:09
  • Perfect @Frazz. Added a new property to my storage class, added the field to the TDataGeneratorAdapter, did the Bind Visually from the TListView (setting the properties as you described above), and now it works perfectly. – SiBrit Sep 03 '19 at 03:33
  • Except on iOS, where if the entered text for the search would give no results, an "Argument out of range" error is generated within the TListView.iOS code and crashes the app. For now we have turned off the search feature for iOS devices. – SiBrit Sep 05 '19 at 20:34
  • I have no experience with iOS... but that would seem like a possible bug. If you can isolate where it is happening, you might want to open a ticket on the Embarcadero quality central. Does a vanilla iOS listview with the searchbox show the same error? – Frazz Sep 06 '19 at 08:34
  • That's what I had been trying to do. Get a simple reproducible example to QC to add to the JIRA issue I created. However, if I run my sample in the iOS simulator it is OK, but I can't deploy it to the device using via TestFlight as the sample app hadn't been provisioned. Eventually I'll get back to it, but I can't really be spending the better part of a day finding a fix or workaround for what is most likely a FireMonkey iOS problem. – SiBrit Sep 11 '19 at 01:30
0

If use bind visually and ItemAppearance (Dynamic Appearance) you can one column from data source assign to header and text item (visible = false). In this situation in header and item we have the same value and search work fine.

user_odoo
  • 2,284
  • 34
  • 55