Поиск ярлыка по заголовку

Я пытался выяснить, как искать ярлык по его Caption:

for I := ComponentCount - 1 downto 0 do
begin
  if Components[i] is TLabel then
    if Components[i].Caption = mnNumber then
    begin
      Components[i].Left := Left;
      Components[i].Top := Top + 8;
    end;
end;

Я получаю ошибку: Undeclared identifier: 'Caption',
Как я могу решить эту проблему?

5 ответов

Решение

Последняя часть информации появилась в вашем комментарии к ответу Голеза: ваши ярлыки создаются во время выполнения, поэтому есть вероятность, что они не Form как владелец. Вам нужно будет использовать Controls[] массив для просмотра всех элементов управления, которые связаны с формой, и рекурсивного просмотра всех TWinControl потомки, потому что они могут также содержать TLabel"S.

Если вы собираетесь делать это и для разных типов элементов управления, вы, вероятно, захотите внедрить своего рода помощника, чтобы не повторяться слишком часто. Посмотрите на ответ Дэвида для готового решения, которое может включать некоторые "навороты", помимо решения проблемы под рукой; Например, возможность использовать анонимные функции для управления найденными элементами управления, а также возможность использовать анонимную функцию для фильтрации элементов управления по любым критериям.

Прежде чем вы начнете использовать такое сложное решение, вы, вероятно, должны понять самое простое. Очень простая рекурсивная функция, которая просто смотрит на все TControls на все контейнеры, начиная с формы. Что-то вроде этого:

procedure TForm1.Button1Click(Sender: TObject);

  procedure RecursiveSearchForLabels(const P: TWinControl);
  var i:Integer;
  begin
    for i:=0 to P.ControlCount-1 do
      if P.Controls[i] is TWinControl then
        RecursiveSearchForLabels(TWinControl(P.Controls[i]))
      else if P.Controls[i] is TLabel then
        TLabel(P.Controls[i]).Caption := 'Test';
  end;

begin
  RecursiveSearchForLables(Self);
end;

Используя общий код Дэвида, вышеприведенное можно переписать так:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TControls.WalkControls<TLabel>(Self, nil,
    procedure(lbl: TLabel)
    begin
      lbl.Caption := 'Test';
    end
  );
end;

Перебирая Components[] это неправильный подход. Это просто дает компоненты, которые принадлежат форме. Вы пропустите любые компоненты, которые добавляются динамически и не принадлежат форме, или компоненты, которые принадлежат фреймам.

Вместо этого вы должны использовать Controls[], Тем не менее, это дает только детей первого поколения. Если существует более глубокая степень вложенности родителей / детей, вам необходимо выполнить повторную процедуру. Это больше работы. Я использую несколько помощников, чтобы сделать это легко. Я завернул их в этот блок:

unit ControlEnumerator;

interface

uses
  System.SysUtils, System.Generics.Collections, Vcl.Controls;

type
  TControls = class
  private
    type
      TEnumerator<T: TControl> = record
        FControls: TArray<T>;
        FIndex: Integer;
        procedure Initialise(WinControl: TWinControl; Predicate: TFunc<T, Boolean>);
        class function Count(WinControl: TWinControl; Predicate: TFunc<T, Boolean>): Integer; static;
        function GetCurrent: T;
        function MoveNext: Boolean;
        property Current: T read GetCurrent;
      end;
      TEnumeratorFactory<T: TControl> = record
        FWinControl: TWinControl;
        FPredicate: TFunc<T, Boolean>;
        function Count: Integer;
        function Controls: TArray<T>;
        function GetEnumerator: TEnumerator<T>;
      end;
  public
    class procedure WalkControls<T: TControl>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>; Method: TProc<T>); static;
    class function Enumerator<T: TControl>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>=nil): TEnumeratorFactory<T>; static;
    class function ChildCount<T: TControl>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>=nil): Integer; static;
  end;

implementation

{ TControls.TEnumerator<T> }

procedure TControls.TEnumerator<T>.Initialise(WinControl: TWinControl; Predicate: TFunc<T, Boolean>);
var
  List: TList<T>;
  Method: TProc<T>;
begin
  List := TObjectList<T>.Create(False);
  Try
    Method :=
      procedure(Control: T)
      begin
        List.Add(Control);
      end;
    WalkControls<T>(WinControl, Predicate, Method);
    FControls := List.ToArray;
  Finally
    List.Free;
  End;
  FIndex := -1;
end;

class function TControls.TEnumerator<T>.Count(WinControl: TWinControl; Predicate: TFunc<T, Boolean>): Integer;
var
  Count: Integer;
  Method: TProc<T>;
begin
  Method :=
    procedure(Control: T)
    begin
      inc(Count);
    end;
  Count := 0;
  WalkControls<T>(WinControl, Predicate, Method);
  Result := Count;
end;

function TControls.TEnumerator<T>.GetCurrent: T;
begin
  Result := FControls[FIndex];
end;

function TControls.TEnumerator<T>.MoveNext: Boolean;
begin
  inc(FIndex);
  Result := FIndex<Length(FControls);
end;

{ TControls.TEnumeratorFactory<T> }

function TControls.TEnumeratorFactory<T>.Count: Integer;
begin
  Result := TEnumerator<T>.Count(FWinControl, FPredicate);
end;

function TControls.TEnumeratorFactory<T>.Controls: TArray<T>;
var
  Enumerator: TEnumerator<T>;
begin
  Enumerator.Initialise(FWinControl, FPredicate);
  Result := Enumerator.FControls;
end;

function TControls.TEnumeratorFactory<T>.GetEnumerator: TEnumerator<T>;
begin
  Result.Initialise(FWinControl, FPredicate);
end;

class procedure TControls.WalkControls<T>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>; Method: TProc<T>);
var
  i: Integer;
  Control: TControl;
  Include: Boolean;
begin
  if not Assigned(WinControl) then begin
    exit;
  end;
  for i := 0 to WinControl.ControlCount-1 do begin
    Control := WinControl.Controls[i];
    if not (Control is T) then begin
      Include := False;
    end else if Assigned(Predicate) and not Predicate(Control) then begin
      Include := False;
    end else begin
      Include := True;
    end;
    if Include then begin
      Method(Control);
    end;
    if Control is TWinControl then begin
      WalkControls(TWinControl(Control), Predicate, Method);
    end;
  end;
end;

class function TControls.Enumerator<T>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>): TEnumeratorFactory<T>;
begin
  Result.FWinControl := WinControl;
  Result.FPredicate := Predicate;
end;

class function TControls.ChildCount<T>(WinControl: TWinControl; Predicate: TFunc<T, Boolean>): Integer;
begin
  Result := Enumerator<T>(WinControl, Predicate).Count;
end;

end.

Теперь вы можете решить вашу проблему следующим образом:

var
  lbl: TLabel;
....
for lbl in TControls.Enumerator<TLabel>(Form) do
  if lbl.caption=mnNumber then
  begin
    lbl.Left := Left;
    lbl.Top := Top + 8;
  end;

Или вы можете использовать предикат для проверки подписи внутри итератора:

var
  Predicate: TControlPredicate;
  lbl: TLabel;
....
Predicate := function(lbl: TLabel): Boolean
  begin
    Result := lbl.Caption='hello';
  end;
for lbl in TControls.Enumerator<TLabel>(Form, Predicate) do
begin
  lbl.Left := Left;
  lbl.Top := Top + 8;
end;

ComponentCount только для подсчета. Используйте массив Компоненты, чтобы найти фактические компоненты. Для простоты вы можете поместить метку в переменную TLabel, что также позволит вам использовать специфичные для метки свойства, которые не видны в TComponent. Вы могли бы использовать with для этого также, но я думаю, что это ухудшает читабельность.

var
  l: TLabel;


for I := ComponentCount -1 downto 0 do
begin
  if Components[i] is TLabel then // Check if it is.
  begin
    l := TLabel(Components[i]); // Typecast, to reach it's properties.
    if l.Caption = mnNumber then
    begin
      l.Left := Left;
      l.Top := Top +8;
    end;
  end;
end;

Компилятор не знает, что ваш Components[i] является TLabel.

Вы должны привести свой компонент к Tlabel следующим образом:

for I := ComponentCount - 1 downto 0 do
begin
  if Components[i] is TLabel then //here you check if it is a tlabel
    if TLabel(Components[i]).Caption = mnNumber then //and here you explicitly tell the
    begin                                            //compiler to treat components[i]
      TLabel(Components[i]).Left := Left;            //as a tlabel.
      TLabel(Components[i]).Top := Top + 8;         
    end;
end;

Это необходимо, потому что компоненты [я] не знают заголовок.

Попробуй это:

  для I:= ControlCount-1 до 0 сделать
  начать
    если Controls [i] - TLabel, тогда // Проверить, если это так.
    начать
      if (Управляет [i] как TLabel).Caption = mnNumber затем
      начать
        (Управляет [i] как TLabel).Left:= Left;
        (Управляет [i] как TLabel).Top:= Top +8;
      конец;
    конец;
  конец;
Другие вопросы по тегам