delphidelphi-5

Components not being found by FindComponent()


I have created a login form which holds buttons corresponding to users' names held in an Access file. The buttons are created in the OnCreate event because I don't want to have to create them each time the screen is shown.

The buttons display as expected, and I have created LogOn and LogOff procedures, which both work as I expected.

The next step was to only display the buttons for users who are currently logged on to the system. To do this, I have created the following code in the OnActivate event:

procedure TUserInForm.FormActivate(Sender: TObject);
var
   btn : TLBButton;
begin
   With UserQuery do begin;
      first;
      while (not eof) do begin
         BtnName := FieldByName('UserName').AsString;
         Btn := TLBButton(FindComponent(BtnName));
         if (Btn <> nil) then
            if (FieldByName('LoggedIn').AsBoolean = True) then Btn.Visible := True else Btn.Visible := False;
         next;
      end;
   end;
end;

However, it doesn't find any of the buttons - they are all nil. The code throws an Access Violation exception if I remove the nil check. But, at no point in the code do I destroy the buttons or the form itself. The buttons exist, because I can see them on the form. The BtnName variable is global within the unit. I've checked that the BtnName variable is populated correctly from the table.

I've used code similar to this to find components before, with no problems. In fact, I "stole" the code shown above from another procedure (with the obvious changes) in which it works fine. The logs show no errors.

Can anyone suggest some approach to fixing this problem? It's very frustrating!


Solution

  • FindComponent() searches the owned-components-list of the component that it is called on. I’m assuming your OnCreate handler creates the buttons with the Form as their Owner. But the with block will cause FindComponent() to be called on the UserQuery component instead of the Form. That would explain why the buttons are not being found.

    So, you can either:

    procedure TUserInForm.FormActivate(Sender: TObject);
    var
      Btn : TLBButton;
      BtnName : string;
    begin
      with UserQuery do begin
        First;
        while (not Eof) do begin
          BtnName := FieldByName('UserName').AsString;
          Btn := TLBButton(Self.FindComponent(BtnName));
          if (Btn <> nil) then
            Btn.Visible := FieldByName('LoggedIn').AsBoolean;
          Next;
        end;
      end;
    end;
    
    procedure TUserInForm.FormActivate(Sender: TObject);
    var
      Btn : TLBButton;
      BtnName : string;
    begin
      UserQuery.First;
      while (not UserQuery.Eof) do begin
        BtnName := UserQuery.FieldByName('UserName').AsString;
        Btn := TLBButton(FindComponent(BtnName));
        if (Btn <> nil) then
          Btn.Visible := UserQuery.FieldByName('LoggedIn').AsBoolean;
        UserQuery.Next;
      end;
    end;
    
    procedure TUserInForm.FormActivate(Sender: TObject);
    var
      Btn : TLBButton;
      BtnName : string;
    begin
      with UserQuery do begin
        First;
        while (not Eof) do begin
          BtnName := FieldByName('UserName').AsString;
          Btn := FindButton(BtnName);
          if (Btn <> nil) then
            Btn.Visible := FieldByName('LoggedIn').AsBoolean;
          Next;
        end;
      end;
    end;
    
    function TUserInForm.FindButton(const BtnName: string): TLBButton;
    begin
      Result := TLBButton(FindComponent(BtnName));
    end;
    

    Now, that being said, it would be a better design to add the created buttons to a list that you manage for yourself, rather than a list that the VCL manages on your behalf. Then you will always know exactly where to find the buttons. For example:

    type
      TUserInForm = class(TForm)
        UserQuery: TQuery;
        ...
        procedure FormActivate(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        ...
      private
        Buttons: TList;
        function FindButton(const BtnName: string): TLBButton;
        ...
      end;
    
    ...
    
    procedure TUserInForm.FormCreate(Sender: TObject);
    var
      Btn : TLBButton;
    begin
      Buttons := TList.Create;
      ...
      Btn := TLBButton.Create(Self);
      Btn.Name := ... 
      Buttons.Add(Btn);
      ...
    end;
    
    procedure TUserInForm.FormDestroy(Sender: TObject);
    begin
      Buttons.Free;
    end;
    
    procedure TUserInForm.FormActivate(Sender: TObject);
    var
      Btn : TLBButton;
      BtnName : string;
    begin
      UserQuery.First;
      while (not UserQuery.Eof) do begin
        BtnName := UserQuery.FieldByName('UserName').AsString;
        Btn := FindButton(BtnName);
        if (Btn <> nil) then begin
          Btn.Visible := UserQuery.FieldByName('LoggedIn').AsBoolean;
        end;
        UserQuery.Next;
      end;
    end;
    
    function TUserInForm.FindButton(const BtnName: string): TLBButton;
    var
     i: integer;
    begin
      for i := 0 to Buttons.Count-1 do begin
        Result := TLBButton(Buttons[i]);
        if Result.Name = BtnName then Exit;
      end;
      Result := nil;
    end;