6String_Coder
6String_Coder

Reputation: 547

Add TSwitch to every TListView item

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,

Upvotes: 3

Views: 3319

Answers (1)

Jerry Dodge
Jerry Dodge

Reputation: 27276

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.

Screenshot

NOTE: This was written in Delphi 10 Seattle.

Your only other options I believe are to either:

  1. Instantiate a TSwitch for each item and render it using the same method as above (Very sloppy, I do not recommend)
  2. Figure out how to implement the drawing of the standard TSwitch using styles, again using the same method as above (which is probably the best option for performance and visual adaption)
  3. Resort to a 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.

Upvotes: 6

Related Questions