Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview. I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid.
By the 4th update to Delphi XE2 and FireMonkey, I was ready to give up but then I stumbled on a video created by Marco Cantù which introduced me a bit more to FireMonkey and the fact that all visual objects are derived from TLayout - and more importantly - are Styleable. He showed how one can change the style of any object to make changes to the base object to make it appear different - like adding a picture to a TListBoxItem.
I am now going to impart some of this knowledge to you.
TListView's replacement
As I've already mentioned, TListView does not exist as a visual component in FireMonkey - we now have TListBox. If we go ahead and create a new FireMonkey HD Application, add a TListBox onto the form, and then double-click it, we will get the Items Designer open. We can now add items, and you'll see that the Items property is a collection of TListBoxItem
Essentially, what we are going to do is change the Style of the ListBoxItem so that it includes a lot more than just one text field, and then when we add items to the listbox at run time, we're going to tell it to use our new style.
Styling the Style
With your horrible looking template on form, right click the ListBoxItem you've placed and select "Edit Custom Style..."
Automatically, a new TStyleBook object named StyleBook1 will be added to your project, and you will be taken to the Style Layout screen as shown below.
You will notice that it has added two styles for you. CheckBoxStyle and ListBoxItemstyle. The reason you have two (even though you only selected one) is because CheckBoxStyle is a style used by ListBoxItem's check, so it related styles to your stylebook. We're going to ignore the CheckBoxStyle for now (it won't ever get used), and concentrate on the ListBoxItemStyle.
If you expand ListBoxItemstyle, it will select it and show it you on screen, as well as show you the two children text: TText and check: TCheckBox.
Make the boundary of the ListBoxItemstyle a bit wider without changing the height. You can do this easily by editing the Width property of the ListBoxItemstyle and making it say 300. You now have a lot more space to work with.
I would leave the checkbox there (it's always useful to have). By default, checkboxes are not disaplayed and you can turn them on by toggling a boolean property. The first thing you want to do is to change the Alignment of the TText property so that it is alLeft. You can now change the Width of it to be whatever size you want it to end up being. For example, if I want what would have been the caption of the listitem to have a width of 100, I would set the width of the text: TText to be 100.
Now, what we do is we add more objects to the ListBoxitem. Using the Tool Palette, drag and drop a new TText object onto ListBoxItemstyle, so that it becomes a child of this object. The default height and width of a text box is 50 - and the default Alignment is alNone, so go ahead and just change the alignment to be alLeft. Voilà! it's now the same height as the bounding object, and has repositioned itself accordingly.
You'll notice that this text box seems to be a bit different than the original one. Unfortunately, we can't copy and paste inside the style designer, so what we have to do is make this new TText have the same properties as the original TText. We have to Uncheck "HitText", change HorzTextAlign to be taLeading and then change the Padding as follows - Bottom 1 - Left 3 - Right 3 - Top 1. You'll probably want to experiment a bit with the Left and Right padding, to make it look better. In the next screenshot I've used a right padding of 0, otherwise the gap between the original TText and the new one would be too great.
Let's add one more TText with a width of 50, and then also add a TImage and TProgressBar for fun. Remember, to add additional components, drag and drop them from your Tool Palette onto the ListBoxItemStyle: TLayout. Each object I add I'm going to set the alignment to alLeft, with the exception of the progress bar - that I am going to make alClient. I'm also goign to re-order it so that the original text is the left most of the 3 text items, the image is going to be even more left (this will become my Image), and the Progress Bar is going to be the right automatically because of it's alignment. The image is going to have a width of 24 pixels (which is also the same as the height). I am also going to increase the width of the component to 500 pixels. This is the end result.
You can only change the position of the object you have selected. To select an object, click on the tree view on the right.
As you can see, it's starting to look really good. What's great about this is that you can add virtually any object to your TLayout. You're also not limited to keep the height at 24 (default) - you can increase this to whatever you like!
At this point, I'm satisfied that this style will satisfy name ListItem needs. So, let's move on to the next part.
Naming the Names
Styles are referenced by a Style Name. This includes the parent style (which is currently named ListBoxItemstyle) and all the children styles. What you want to do now is select the parent style, and give it a unique Style name. You do this by editing the StyleName Property. I'm going to call mine LestersStyle.
Moving onto the children objects now - I'm going to call the "main" text style "Caption", and remove the "Text" from the Text property. I'm going to name the other two text styles "SubItem1" and "SubItem2", the image style named "Icon" and finally, the ProgressBar to be "Progress". I'm going to change the default check style to be "Check" for aethstetic reasons. Having a StyleName for each object is important! It is not necessary to change the Name property of any of these objects. If you do decide to name them, remember to use names that will be unique. This is what it looks like now in the Style Explorer.
Save this style by clicking "Apply and Close".
We're done! Now it's time to show you how to use this.
Coding
I'm going to remove the ListBoxItem1 I added using the Items Designer, since it should be blank when the application runs. I'm also going change ListBox1's alignment to AlClient so that it fills up my entire form, and lastly, I'm going to add a two buttons - button1 and button2.
Button 1 is going to be used to add a new "ListItem", and Button 2 is going to be used to Randomize the contents of all the "ListItems". I'm doing Button 2 so that you know how to reference items which already exist. Go ahead and double-click Button1 now so that you can code what it does on Click. Further explanations are in the code for your reference.
NOTE: Some of you highly skilled programmers will notice that I can save a LOT of space and code in the following block, but I've deliberately done it like this so that people with lesser understanding can follow it easier. Naturally, use whatever better methods of code you are familiar with.
procedure TForm1.Button1Click(Sender: TObject);
Const
{$IFDEF MSWINDOWS}
BitmapFile : String = 'C:\Windows\Web\Wallpaper\Windows\img0.jpg';
{$ELSE}
BitmapFile : String = '/Applications/Safari.app/Contents/Resources/SnapshotPlaceholderCompass.png';
{$ENDIF}
Var
ListBoxItem : TListBoxItem;
ItemText : TText; // if you get an error here, ensure that FMX.Objects is included in your uses above.
ItemImage : TImage;
ItemProgress: TProgressBar;
begin
// First, we create an empty ListBoxItem with no parent
ListBoxItem := TListBoxItem.Create(nil);
// now, we set a parent for this item
ListBoxItem.Parent := ListBox1;
// Associate the Style you created with the ListBoxItem - not the ListBox itself.
ListBoxItem.StyleLookup := 'LestersStyle';
// Next up, we're going to populate ItemText with the Caption of the Style using a style name lookup.
ItemText := ListBoxItem.FindStyleResource('Caption') as TText;
// We're going to set the text of the object to be whatever the caption should be.
if Assigned(ItemText) then ItemText.Text := 'Hello World!';
// Populating the two SubItems
ItemText := ListBoxItem.FindStyleResource('SubItem1') as TText;
if Assigned(ItemText) then ItemText.Text := 'SubItem1';
ItemText := ListBoxItem.FindStyleResource('SubItem2') as TText;
if Assigned(ItemText) then ItemText.Text := 'SubItem2';
// Loading up the image
ItemImage := ListBoxItem.FindStyleResource('Icon') as TImage;
if Assigned(ItemImage) then If FileExists(BitmapFile) Then ItemImage.Bitmap.LoadFromFile(BitmapFile);
// Populating the Progress Bar
ItemProgress := ListBoxItem.FindStyleResource('Progress') as TProgressBar;
if Assigned(ItemProgress) then ItemProgress.Value := Random(100);
end;
And this is what it looks like on Mac when we click Button 1.
Now, on to Button 2.
procedure TForm1.Button2Click(Sender: TObject);
Var
ListBoxItem : TListBoxItem;
ItemText : TText; // if you get an error here, ensure that FMX.Objects is included in your uses above.
ItemProgress: TProgressBar;
i : Integer;
begin
if ListBox1.Items.Count > 0 then for i := 0 to Pred(ListBox1.Items.Count) do
begin
ListBoxItem := ListBox1.ItemByIndex(i);
// Get and change the caption
ItemText := ListBoxItem.FindStyleResource('Caption') as TText;
if Assigned(ItemText) then ItemText.Text := 'Hello World! '+ IntToStr(i);
// Get and change the two Subitems
ItemText := ListBoxItem.FindStyleResource('SubItem1') as TText;
if Assigned(ItemText) then ItemText.Text := 'SubItem1 '+ IntToStr(i);
ItemText := ListBoxItem.FindStyleResource('SubItem2') as TText;
if Assigned(ItemText) then ItemText.Text := 'SubItem2 '+ IntToStr(i);
// Get and change the Progress Bar.
ItemProgress := ListBoxItem.FindStyleResource('Progress') as TProgressBar;
if Assigned(ItemProgress) then ItemProgress.Value := Random(100);
end;
end;
And this is what it looks like on PC when we click "Button2".
Where's the header?
Unfortunately, since this isn't a real TListView - it won't contain a header control, but fortunately, we can add one in! Just add a THeader to your main form, add some Headers, and resize accordingly. To make things a bit more exciting, I'm going to set the property "ShopwCheckBoxes" on ListBox1 to "True".
Here is what the final product looks like - on both Windows and Mac.
Observations
Everything I've done here has "fixed column sizes" - but this doesn't have to stay that way. It is possible to create styles dynamically at run time, so you are not limited by the number of columns you can end up having. You can also use the OnResize event for the column header to find and resize your columns in your style, dynamically. Dynamic Styles is however out of scope of this article, but I may end up writing a new article to cover this.
Further Reading
I highly recommend you read a book being written by Marco Cantù on FireMonkey if you're interested in developing cross platform applications using Delphi FireMonkey. It's currently being written, and will most likely be advertised on his web site
I would also like to take this opportunity to thank Marco Cantù for being such a great help to the Borland/Inprise/Embarcadero chain of products with his vast knowledge.
Comments (2)
Commented:
can you show me how to attach a click event on each button or image?
thank you very much!!!
Commented:
Nice tutorial.
Does this also apply to XE5?
I'm also a bit confused about how to do this in mobile apps. Especially since I don't see the edit custom style.