I haven't attempted this because I wouldn't know where to begin.
Is it possible to add a FMX TSwitch into a FMX TListViewitem?
Any help/suggestions would be much appreciated.
Thanks,
You first have to keep in mind the whole design of the TListView
control. It's meant to be very lightweight for when it contains a large number of items. You may have a million items, you surely don't want a million switch controls instantiated. Therefore, it's not meant for you to embed controls in each item as a container, such as the TListBox
allows.
That being said, it's assumed that you perform minimal drawing on each individual list item to be consistent with the design of the TListView
. This requires creating virtual objects inherited from TListItemObject
to be associated with each item. These objects are what allow the existing built-in elements of any item, such as the accessory or bitmap.
Here's a very rough demo I threw together to get you started, you'd need to change the drawing how you need it to look.
Start a new FMX application, drop a TListView
, and use this unit in place of your main form's unit:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
FMX.ListView, FMX.Controls.Presentation, FMX.StdCtrls;
type
TListItemSwitch = class(TListItemSimpleControl)
private
FIsChecked: Boolean;
FOnSwitch: TNotifyEvent;
procedure SetIsChecked(const AValue: Boolean);
protected
function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean;
override;
procedure DoSwitch; virtual;
public
constructor Create(const AOwner: TListItem); override;
destructor Destroy; override;
procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates;
const SubPassNo: Integer = 0); override;
public
property IsChecked: Boolean read FIsChecked write SetIsChecked;
property OnSwitch: TNotifyEvent read FOnSwitch write FOnSwitch;
end;
TForm1 = class(TForm)
ListView1: TListView;
procedure ListView1UpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{ TListItemSwitch }
constructor TListItemSwitch.Create(const AOwner: TListItem);
begin
inherited;
end;
destructor TListItemSwitch.Destroy;
begin
inherited;
end;
procedure TListItemSwitch.DoSwitch;
begin
FIsChecked:= not FIsChecked;
if Assigned(OnSwitch) then
OnSwitch(Self);
end;
function TListItemSwitch.MouseDown(const Button: TMouseButton;
const Shift: TShiftState; const MousePos: TPointF): Boolean;
begin
if (Button = TMouseButton.mbLeft) and Enabled then begin
DoSwitch;
end;
inherited;
end;
procedure TListItemSwitch.Render(const Canvas: TCanvas;
const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates;
const SubPassNo: Integer);
var
R, R2: TRectF;
begin
inherited;
R:= Self.LocalRect;
R2:= R;
Canvas.BeginScene;
try
Canvas.Stroke.Kind:= TBrushKind.None;
Canvas.Fill.Kind:= TBrushKind.Solid;
Canvas.Fill.Color:= TAlphaColorRec.Skyblue;
Canvas.FillRect(R, 8, 8,
[TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight],
1.0, TCornerType.Round);
if IsChecked then begin
R2.Left:= R.Right - 20;
R2.Width:= 20;
end else begin
R2.Left:= R.Left;
R2.Width:= 20;
end;
Canvas.Fill.Color:= TAlphaColorRec.Black;
Canvas.FillRect(R2, 8, 8,
[TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight],
1.0, TCornerType.Round);
finally
Canvas.EndScene;
end;
end;
procedure TListItemSwitch.SetIsChecked(const AValue: Boolean);
begin
FIsChecked:= AValue;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var
I: TListViewItem;
begin
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
end;
procedure TForm1.ListView1UpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
var
S: TListItemSwitch;
begin
S:= AItem.Objects.FindObject('Switch') as TListItemSwitch;
if S = nil then begin
S:= TListItemSwitch.Create(AItem);
S.Name:= 'Switch';
S.Align:= TListItemAlign.Trailing;
S.VertAlign:= TListItemAlign.Center;
S.Width:= 50;
S.Height:= 20;
S.IsChecked:= False;
end;
end;
end.
NOTE: This was written in Delphi 10 Seattle.
Your only other options I believe are to either:
TSwitch
for each item and render it using the same method as above (Very sloppy, I do not recommend)TSwitch
using styles, again using the same method as above (which is probably the best option for performance and visual adaption)TListBox
instead, depending on how you intend to use the list (which would be very heavy on a large number of items)I went a little more in-depth about the differences between a TListView
and a TListBox
in Firemonkey in a separate question / answer.