Good day
I am trying to build an application which requires me to display multiple images in groups with selectable sizes. The images consist of a bitmap and a text field. My idea is to put the image groups in a TListview (Which I hope can display horizontally) and then add these listview groups into a TFlowlayout to manage the screen layout.
However, I simply do not get it right to create a TListview item programmatically to display the image. I have tried to create a TListItemImage as well as simply adding a TListViewItem but neither worked in that I could see anything on the screen.
I am including my test code (Note it pulls images from a folder for testing). The commented out sections will probably indicate some of the experiments that I tried.
I will probably also struggle to add the TListviews to the TFlowlayout. Some advice will be much appreciated. The idea is that the application will run on both Android mobile as well as desktops.
function TForm1.BuildGrpObj(GroupSize, CurrPicIdx: integer): boolean;
var
aPicObj : TListViewitem;
k: Integer;
aPicObjImg: TListItemImage;
// aPicObjImg: TListViewItem;
FName: string;
begin
for k := 0 to GroupSize-1 do
begin
aPicObj := Listview1.Items.Add;
aPicObj.Text := 'Picture: ' + inttostr(CurrPicIdx + k);
aPicObjImg := TListItemImage.Create(aPicObj);
// aPicObjImg := Listview1.Items.Add;
FName := LList[CurrPicIdx + k];
// aPicObjImg.Bitmap := TBitmap.Create;
aPicObjImg.Bitmap.LoadFromFile(FName);
aPicObjImg.Align := TListItemAlign.Center;
aPicObjImg.VertAlign := TListItemAlign.Center;
aPicObjImg.PlaceOffset.X := 0;
aPicObjImg.PlaceOffset.Y := 0;
aPicObjImg.Width := 40;
aPicObjImg.Height := 40;
aPicObjImg.invalidate;
end;
result := true;
end;
Related
Is there a way to access the different items configured to a FMX TListview's ListViewItem component below the ListView rows are filled?
I have a TListView set to DynamicAppearance and have it set up as shown in the image below.
Based on a field in a database, I need to make some of the Items under columns 1 to 9 invisible. I have been able to make this work using the code below within the UpdateObjects event, but was wondering if there is any way I can accomplish the same thing when the database is open, instead of when each ListViewItem is updated.
procedure TfrmHistoryRuntime.ListView1UpdateObjects(const Sender: TObject; const AItem: TListViewItem);
begin
Drawable := TListItemText(AItem.View.FindDrawable('DateTime'));
if assigned(Drawable) then
Drawable.Height := 52;
AItem.Height := 54;
for I := 1 to 9 do
begin
Drawable := TListItemText(AItem.View.FindDrawable('Device' + IntToStr(I)));
if assigned(Drawable) then
Drawable.Visible := IsBitSet(fGroups, I);
Drawable := TListItemText(AItem.View.FindDrawable('Real' + IntToStr(I)));
if assigned(Drawable) then
Drawable.Visible := IsBitSet(fGroups, I);
end;
end;
I did figure out I can access the items using code similar to below, but don't know if there is a way to set the index value using the Item name and not a number (which might change)?
ListView1.ItemAppearanceObjects.ItemObjects.Objects[0].Height := 52;
ListView1.ItemAppearance.ItemHeight := 54;
ListView1.ItemAppearanceObjects.ItemObjects.Objects[2].Visible := false;
ListView1.ItemAppearanceObjects.ItemObjects.Objects[12].Visible := false;
Using Delphi XE8 I'm currently testing functionality with Firemonkey TListViews.
One thing I'm trying to do is to load a field of all records from a TFDMemtable component into a Listview Item, specifically into the DetailObject of the ListView Item.
For example, I have 3 records in a table (db field is called 'Name'):
Record 1 = Name 1
Record 2 = Name 2
Record 3 = Name 3
There is only 1 DetailObject property per ListView Item so my question is, would I be able to add all of the fields (Name 1, Name 2, Name 3) into that one DetailObject?
Below is what I've attempted so far but no luck. Not 100% sure what I need to do.
procedure MainForm.BuildList;
var LItem : TListViewItem;
begin
ListView1.BeginUpdate;
try
ListView1.CLearItems;
LItem := ListView1.Items.Add;
LItem.Objects.DetailObject.Visible := True;
with memtable do
begin
while not eof do
begin
LItem.Detail := FieldByName('Name').AsString;
end;
end;
finally
ListView1.EndUpdate;
end;
end;
I'm sorry if this isn't clear enough, please let me know.
Any help would be great.
I think I should warn you that before seeing your q, I'd never done anything with FMX ListViews and Master/Detail datasets. The Following is a little rough around the edges, and the layout isn't ideal, but it shows one way to populate a ListView from Master + Detail datasets. I have no idea whether there are better ways. Personally, I would see if I could use Live Bindings to do the job.
procedure TMasterDetailForm.BuildList;
var
LItem : TListViewItem;
DetailItem : TListViewItem;
ListItemText : TListItemText;
DetailIndex : Integer;
begin
ListView1.BeginUpdate;
ListView1.ItemAppearanceObjects.ItemEditObjects.Text.TextVertAlign := TTextAlign.Leading; // The default
// seems to be `Center`, whereas we want the Master field name to be at the top of the item
try
ListView1.Items.Clear; //Items;
Master.First;
while not Master.eof do begin
LItem := ListView1.Items.Add;
LItem.Text := Master.FieldByName('Name').AsString;
LItem.Height := 25;
Detail.First;
DetailIndex := 0;
while not Detail.Eof do begin
Inc(DetailIndex);
ListItemText := TListItemText.Create(LItem);
ListItemText.PlaceOffset.X := 100;
ListItemText.PlaceOffset.Y := 25 * (DetailIndex - 1);
ListItemText.TextAlign := TTextAlign.Leading;
ListItemText.Name := 'Name' + IntToStr(DetailIndex); //Detail.FieldByName('Name').AsString;
LItem.Data['Name' + IntToStr(DetailIndex)] := Detail.FieldByName('Name').AsString;
Detail.Next;
end;
LItem.Height := LItem.Height * (1 + DetailIndex);
Master.Next;
end;
finally
ListView1.EndUpdate;
end;
end;
TListItemText is one of a number of "drawable" FMX objects that can be added to do the TListViewItem. They seem to need unique names so that they can be accessed via the Names property.
FWIW, I used 2 TClientDataSets as the Master and Detail in my code.
Also FWIW, for FMX newbies like me, populating an FMX TreeView is a lot more like what you'd do in a VCL project:
procedure TMasterDetailForm.BuildTree;
var
PNode,
ChildNode : TTreeViewItem;
begin
TreeView1.BeginUpdate;
try
TreeView1.Clear;
Master.First;
while not Master.eof do begin
PNode := TTreeViewItem.Create(TreeView1);
TreeView1.AddObject(PNode);
PNode.Text := Master.FieldByName('Name').AsString;
Detail.First;
while not Detail.Eof do begin
ChildNode := TTreeViewItem.Create(TreeView1);
ChildNode.Text := Detail.FieldByName('Name').AsString;
PNode.AddObject(ChildNode);
Detail.Next;
end;
Master.Next;
end;
finally
TreeView1.EndUpdate;
end;
end;
Btw, in your code you should have been calling
memtable.Next;
in your while not eof loop, and memtable.First immediately before the loop.
I am dynamicaly (at run time) adding controls into a TScrollBox using myScrollBox.AddObject
Now I need to remove all the controls I added to put new ones.
I tryed myScrollBox.Controls.Clear but after I call that function, any control I add are not showing up.
(Warning: I'm new to delphi and Firemonkey)
Update 1
Here is how I add my objects (this is just a test function)
procedure TMainForm.TaskDetailsAdd;
var
btn1 : TButton;
intI : Integer;
count: Integer;
begin
scbTaskVariables.BeginUpdate;
count := 0;
for intI := 0 to 100 do
begin
btn1 := TButton.Create(self);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.Parent := scbTaskVariables;
btn1.OnClick := Button1Click;
btn1.Tag := intI * 10;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
scbTaskVariables.AddObject(btn1);
count := scbTaskVariables.ControlsCount;
end;
scbTaskVariables.EndUpdate;
end;
The funny thing is that if I place a break point on count := scbTaskVariables.ControlsCount
I can see that ControlsCount goes from 0 to 1 for the first control and then it stays to 1 for the others.
Update 2
I submitted QC#125440.
The inverse of AddObject is RemoveObject. Call ScrollBox.RemoveObject(aChildObject) for each child object that you wish to remove.
The alternative is to set the Parent property of the child object. Set it to ScrollBox to add it. Set it to nil to remove it. This is interchangeable with AddObject and RemoveObject. You can do it either way.
However, when you attempt to do this, just as your said, attempts to add new controls fail if you have removed controls earlier. This would appear to be a bug. Please submit a QC report.
I tested on XE6.
Try with:
myScrollBox.Content.DeleteChildren;
I have added this as an Answer but as there are bugs in FMX it should be considered as a workaround at this stage.
I spent some time on your problem about deleting your buttons, but also to tried to find out more about the bug. David was very quick to spot this and shows his experience.
Two of my findings were that (1) the AddObect() does not appear to work with the buttons, for some reason, they are not being seen as "Objects" but as "Components". (2) Also I found that creating btn1 with the "scrollBox" as its owner helped to achieve an adequate result.
I used 1 x TScrollbox, 2 x TButton and 4 x TLabel. The buttons left with their default name and the TScrollBox with Your default name. So you can just copy and paste. btn1 is made a private variable along with it's procedures.
procedure TMainForm.TaskDetailsAdd;
var
intI : Integer;
begin
label1.Text := IntToStr(scbTaskVariables.ComponentCount);
// Initial count = 1, Probably the scroll box.
if scbTaskVariables.ComponentCount >1 then
TaskDetailsDel; // Don't create Buttons with same Name if already exists.
scbTaskVariables.BeginUpdate;
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton.Create(scbTaskVariables);
btn1.Parent := scbTaskVariables;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
btn1.Tag := intI * 10;
btn1.TabOrder := 10 + intI;
btn1.Name := 'MyBtn' + IntToStr(intI);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.OnClick := Button1Click;
if btn1.IsChild(scbTaskVariables) = true then
Label2.Text := 'True'
else // All this, proves buttons not seen as children.
Label2.Text := 'False';
scbTaskVariables.AddObject(btn1);
// AddObject() taken out as button is not seen as "FmxObject"
end;
scbTaskVariables.EndUpdate;
Label3.Text := IntToStr(scbTaskVariables.ComponentCount);
// Count now all created (includes ScrollBox).
Label4.Text := IntToStr(scbTaskVariables.ControlsCount);
end;
The "TaskDetailsDel" procedure was was quite easy once I had determined that I was really dealing with "Components"
procedure TMainForm.TaskDetailsDel;
var
intI : Integer;
count: Integer;
begin
label1.Text := '';
label2.Text := '';
label3.Text := '';
label4.Text := '';
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton(scbTaskVariables.FindComponent('MyBtn' + IntToStr(intI)));
btn1.Parent := Nil;
FreeAndNil(btn1);
end;
Count := scbTaskVariables.ComponentCount;
Label1.Text := IntToStr(Count);
end;
Using the FindComponent line did the trick.
Press F1 and type the links into the URL Box; I found these interesting, especially seeing how TButton is derived in the VCL and FMX.
ms-help://embarcadero.rs_xe3/libraries/Vcl.StdCtrls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Controls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TStyledControl.html
ms-help://embarcadero.rs_xe3/rad/Objects,_Components,_and_Controls.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TFmxObject.AddObject.html
The TScrollBox has by default 1 component that is of type TScrollContent that is responsible of the display of the other components. So if we delete him then nothing will be shown ever.
I created this little function to RemoveAllComponents inside the TScrollBox (Expect the TScrollContent):
procedure RemoveAllComponentsScrollBox(ScrollBox : TScrollBox);
var i : integer; Obj : TFmxObject;
begin
for I := ScrollBox.ComponentCount-1 downto 0 do
begin
if ((ScrollBox.Components[i] is TFmxObject) and not (ScrollBox.Components[i] is TScrollContent)) then
begin
Obj:=TFmxObject(ScrollBox.Components[i]);
Obj.Parent:=nil;
FreeAndNil(Obj);
end;
end;
end;
This method can be improved by recursivity
I am creating a component at runtime but i am having an issue because when I create 2 of these components, I would change the value of the properties on one of them but it seems to also change it on the other.
How can I create components at runtime so they are seperate components and not instances of eachother?
Ok so this is the code I am using to create the component.
Cell[CellCount]:= TBattery.Create(nil);
Cell[CellCount].Top := Random(500);
Cell[CellCount].Left := Random(500);
Cell[CellCount].Parent := Self;
Cell[CellCount].ID := CellCount;
CellCount := CellCount + 1;
I am using the GDI graphics to draw lines between multiple instances of TBattery. The problem I am having is; if I create two components then add a third, when I move the third one the lines get drawn to that one instead of sticking to the second component.
I uploaded my source files, I'm sure a lot of it won't make sense and my implementation may be bad but any help is appreciated! Thanks in advance
http://pastebin.com/8WUkT1rw
http://pastebin.com/BpASvc7N
They are both part of a electrical circuit simulator for my school project if that helps understanding what the code is for :s
A simple runtime component creation is...
first create a unit
second create a procedure
ex
procedure Label_Comp(Location: TWinControl; Text: String; Label_Left,Label_Top,Numofcomp: Integer; NameOwn: string; Label_Autosize,Label_FontBold,Label_Trans: Boolean);
add in var
var
MyLabel: TsLabel;
and then the procedure code
procedure Label_Comp(Location: TWinControl; Text: String;
Label_Left,Label_Top,Numofcomp: Integer; NameOwn: string;
Label_Autosize,Label_FontBold,Label_Trans: Boolean);
begin
MyLabel := TLabel.Create(main);
MyLabel.Name := 'Label' + NameOwn + IntToStr(Numofcomp);
MyLabel.Parent := Location;
MyLabel.Caption := Text;
MyLabel.Left := Label_Left;
MyLabel.Top := Label_Top;
MyLabel.Font.Name := 'Tahoma';
MyLabel.Font.Size := 8;
MyLabel.Font.Color := clWindowText;
MyLabel.AutoSize := Label_Autosize;
if Label_FontBold = True then
MyLabel.Font.Style := MyLabel.Font.Style + [fsBold];
MyLabel.Transparent := Label_Trans;
MyLabel.Visible := True;
end;
and call it from your program as you want
ex
for i := 0 to 10 do
Label_Comp(main.panelBattery,'Exit',90,24,i,'',True,True,True);
The problem here is to remember the i statement...
I hope i help...
Creating TQReport elements at run time.
Well, at least trying...
I don't know what headings or data shall appear on this report. I get a TList of TStrings representing the data rows and columns. I plant the 'Create' directives in the band print event for the group and the OnNeedData event for main data row bands.
But nothing appears. Must I make the labels at design time? Do not want.
To get you started, this works:
// uses QuickRpt, qrpBaseCtrls, QRCtrls, QRPrntr;
procedure TForm1.Button1Click(Sender: TObject);
var QR: TQuickRep;
QB: TQRBand;
QL: TQRLabel;
begin
QR := TQuickRep.Create(Self);
try
QR.PrintIfEmpty := True;
QB := TQRBand.Create(Self);
QB.Parent := QR;
QB.BandType := rbTitle;
QL := TQRLabel.Create(Self);
QL.Parent := QB;
QL.Left := 10;
QL.Top := 10;
QL.AutoSize := True;
QL.Caption := 'This works';
QR.Preview;
finally QR.Free;
end;
end;