Using Generics, TObjectList, and TComparer in Delphi to Detect Component Display Order

I haven’t really posted a lot of Delphi programming posts but recently happened upon a combination of concepts that really take the work out of determining the sort order of components on screen. Since I generally start a whole new project to work through a concept I had everything I needed for a quick post and decided to share it. This code was originally written in Delphi XE2 but still holds true in Delphi XE7.

Let’s say you have a series of TPanel or TButton components you allow the user to drag and drop in whatever order they desire. You now want to know their order from left to right to store as a preference or execute a series of tasks based on that order. One way to determine this would be to iterate all of the components for the proper type, assemble the components into a list or multidimensional array with the name and left property, then perform any number of sort routines to output the result to a new array based on min to max left values. Using Generics, a TObjectList, and TComparer will make simple work of this task.

I’m not going to get into the in-depth description of generics because the Delphi help explains it in far more depth than I’m willing to re-explain. In this example all you need to know is that we are going to use generics as a type parameter to TObjectList, thus creating a list of a specific type of object with which we can then easily interact without additional typecasting. In this case we will create an object list of buttons (TButton type) then be able to treat them as a collection of items without having to do anything overly funky like TButton(FindComponentByWhatever).SomeProperty.

We will also couple this with the use of TComparer to sort the items in the object list by a property value with little to no additional effort.

Create a form containing a TPanel with 5 buttons and purposely create them out of order. To avoid alot of code about drag and drop we’re just simulating that somebody moved the buttons around. Add a listbox and a single button labeled ‘List Buttons.’

form

When the button is clicked, a TObjectList with the type of buttons will be created. We will then loop over the TPanel controls, adding each into the button list. A loop is used to show the current control order as found via code. We then use the Sort function of the TObjectList passing a TComparer function to compare the left property of each button in the list. A final loop then shows us the actual left to right order of the buttons in the TPanel.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,   Generics.Collections,
  Generics.Defaults;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    ListBox1: TListBox;
    Button6: TButton;
    procedure Button6Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button6Click(Sender: TObject);
var
  // The <> allows us to set the Generics.Collection container of TObjectList
  // to a list of specific types. In this case, buttons
  ButtonList: TObjectList<TButton>;
  i: Integer;
begin
  // clear the list
  ListBox1.Clear;
  // create the TObjectList Button collection
  ButtonList := TObjectList<TButton>.Create(false);
  // loop over each control in the Panel
  for i := 0 to Panel1.ControlCount-1 do
  begin
    // if the control is a TButton, add it to the list.
    // It should be noted that the buttons will be added to
    // the list in their native creation order (button1, 2, 3, etc)
    if Panel1.Controls[i].ClassType = TButton then
      ButtonList.Add(TButton(Panel1.Controls[i]));
  end;

  // loop the button list and show the natural order in the listbox
  for i := 0 to ButtonList.Count-1 do
  begin
    ListBox1.Items.Add(ButtonList.Items[i].Caption);
  end;

  // now create a TComparer object for TButton/ButtonList
  // we pass the TButton then set the result by comparing
  // the Button.Left positions. L - R here will sort (when given
  // left property) left to right buttons. If R-L we get the opposite
  ButtonList.Sort(TComparer<TButton>.Construct(
   function (const L, R: TButton): integer
   begin
     result := L.Left - R.Left;
   end
  ));

  // now loop the list (it is now sorted) again and the list box will
  // show the .Left position sorted buttons
  for i := 0 to ButtonList.Count-1 do
  begin
    ListBox1.Items.Add(ButtonList.Items[i].Caption);
  end;
end;

end.

I’ve added a zip of the Delphi XE7 Project here for you to investigate on your own.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s