0
votes

Using Delphi XE6

I am trying to create an FMX RadioGroup control using a TGroupBox and TRadioButton. I can see both my TTestRadioGroup and TTestGroupButton controls in my IDE control pallette. I can drop a TTestRadioGroup on my form and set the items property and it will create the Radio buttons. However, when I run the application and call the form with the TTestRadioGroup control with its radio buttons, on it, I get a "cant find TTestGroupButton" message.

What have I done wrong?

My first test seems to work ok, as long as I am in design. When

unit TestComponent;

interface

uses {$IFDEF MSWINDOWS}Windows, {$ENDIF}
 System.Classes, FMX.Edit, System.UITypes, System.Character, FMX.DateTimeCtrls,
 System.SysUtils, FMX.Types, System.DateUtils, System.SysConst, FMX.Controls,
 FMX.Pickers, FMX.Platform, FMX.Text, math, FMX.Consts, FMX.Forms, FMX.StdCtrls;

type

 TTestRadioGroup = class;

 TTestGroupButton = class(TRadioButton)
  private
  protected
  public
    constructor InternalCreate(RadioGroup: TTestRadioGroup);
    destructor Destroy; override;
  end;

  TTestRadioGroup = class(TGroupBox)
   private
    FReading: Boolean;
    FButtons: TList;
    FItems: TStrings;
    FItemIndex: Integer;
    FColumns: Integer;
    FUpdating: Boolean;
    FButtonMargins: Integer;
    fButtonSpacing: Integer;
    function GetButtons(Index: Integer): TRadioButton;
    procedure SetButtonMargins(Value: Integer);
    procedure SetButtonSpacing(Value: Integer);
    procedure SetColumns(Value: Integer);
    procedure SetItemIndex(Value: Integer);
    procedure SetItems(Value: TStrings);
    procedure ItemsChange(Sender: TObject);
    procedure SetButtonCount(Value: Integer);
    procedure ButtonClick(Sender: TObject);
    procedure UpdateButtons; //updates buttons list from Items list
    procedure ArrangeButtons; //rearranges buttons on Groupbox based on new properties
   protected
   public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Buttons[Index: Integer]: TRadioButton read GetButtons;
   published
    property ItemIndex: Integer read FItemIndex write SetItemIndex default -1;
    property Items: TStrings read FItems write SetItems;
    property Columns: Integer read FColumns write SetColumns default 1;
    property ButtonMargins: Integer read fButtonMargins write SetButtonMargins default 0;
    property ButtonSpacing: Integer read fButtonSpacing write SetButtonSpacing default 0;
  end;

procedure Register;

implementation

procedure Register;
begin
 RegisterComponents('Test', [TTestRadioGroup, TTestGroupButton]);
end;

{ TTestGroupButton }

constructor TTestGroupButton.InternalCreate(RadioGroup: TTestRadioGroup);
begin
  inherited Create(RadioGroup);
  RadioGroup.FButtons.Add(Self);
  Visible := False;
  Enabled := RadioGroup.Enabled;
  OnClick := RadioGroup.ButtonClick;
  Parent := RadioGroup;
end;


destructor TTestGroupButton.Destroy;
begin
  TTestRadioGroup(Owner).FButtons.Remove(Self);
  inherited Destroy;
end;

{ TTestRadioGroup }

constructor TTestRadioGroup.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FButtons := TList.Create;
  FItems := TStringList.Create;
  TStringList(FItems).OnChange := ItemsChange;
  FItemIndex := -1;
  FColumns := 1;
end;

destructor TTestRadioGroup.Destroy;
begin
  SetButtonCount(0);
  TStringList(FItems).OnChange := nil;
  FItems.Free;
  FButtons.Free;
  inherited Destroy;
end;

procedure TTestRadioGroup.ArrangeButtons;
var
 I,Y: Integer ;
begin
  if (FButtons.Count <> 0) and not FReading then
  begin
    try
    Y:= 10;
      for I := 0 to FButtons.Count - 1 do
        with TTestGroupButton(FButtons[I]) do
        begin
          Position.X:= 10;
          Position.Y:= Y;
          Y:= Y + 10;
          Visible := True;
        end;
    finally
    end;
  end;
end;

procedure TTestRadioGroup.UpdateButtons;
var
  I: Integer;
begin
  SetButtonCount(FItems.Count);
  for I := 0 to FButtons.Count - 1 do
   TRadioButton(FButtons[I]).Text := FItems[I];
  if FItemIndex >= 0 then
  begin
    FUpdating := True;
    TRadioButton(FButtons[FItemIndex]).isChecked := True;
    FUpdating := False;
  end;
  ArrangeButtons;
  Repaint;
end;

procedure TTestRadioGroup.ButtonClick(Sender: TObject);
begin
  if not FUpdating then
  begin
    FItemIndex := FButtons.IndexOf(Sender);
    Change;
    Click;
  end;
end;


procedure TTestRadioGroup.ItemsChange(Sender: TObject);
begin
  if not FReading then
  begin
    if FItemIndex >= FItems.Count then
      FItemIndex := FItems.Count - 1;
    UpdateButtons;
  end;
end;

procedure TTestRadioGroup.SetColumns(Value: Integer);
begin
  if Value < 1 then Value := 1;
  if Value > 16 then Value := 16;
  if FColumns <> Value then
  begin
    FColumns := Value;
    ArrangeButtons;
    Repaint;
  end;
end;

procedure TTestRadioGroup.SetItemIndex(Value: Integer);
begin
  if FReading then FItemIndex := Value else
  begin
    if Value < -1 then Value := -1;
    if Value >= FButtons.Count then Value := FButtons.Count - 1;
    if FItemIndex <> Value then
    begin
      if FItemIndex >= 0 then
        TRadioButton(FButtons[FItemIndex]).isChecked := False;
      FItemIndex := Value;
      if FItemIndex >= 0 then
        TRadioButton(FButtons[FItemIndex]).isChecked := True;
    end;
  end;
end;

procedure TTestRadioGroup.SetItems(Value: TStrings);
begin
  FItems.Assign(Value);
end;

procedure TTestRadioGroup.SetButtonCount(Value: Integer);
begin
  while FButtons.Count < Value do
    TTestGroupButton.InternalCreate(Self);
  while FButtons.Count > Value do
    TTestGroupButton(FButtons.Last).Free;
end;

procedure TTestRadioGroup.SetButtonMargins(Value: Integer);
begin
 if fButtonMargins <> Value then
  fButtonMargins:= Value;
  ArrangeButtons;
end;

procedure TTestRadioGroup.SetButtonSpacing(Value: Integer);
begin
 if fButtonSpacing <> Value then
  fButtonSpacing:= Value;
  ArrangeButtons;
end;

function TTestRadioGroup.GetButtons(Index: Integer): TRadioButton;
begin
  Result := TRadioButton(FButtons[Index]);
end;

end.
1
It sounds like you haven't included this unit's location in your IDE's library path.Jerry Dodge

1 Answers

1
votes

What I think the first problem is, is that when you run the program it will attempt to load the design state of the objects using a copy of the FMX file. The problem with this is that it expects TTestGroupButton to have a standard Create constructor, which effectively yours does not, so it uses TRadioButton.Create instead, which means that at run time your InternalCreate never gets called.

There is a second problem too, to do with creating buttons on the fly, and indeed it is probably this that causes your first problem.

One way to attempt to address this might be to define an additional create. like this:

 TTestGroupButton = class(TRadioButton)
  private
  protected
  public
    constructor InternalCreate(RadioGroup: TTestRadioGroup);   
    constructor Create(AOwner: TComponent); override;

    destructor Destroy; override;
  end;
...
constructor TTestGroupButton.Create(AOwner: TComponent); override;
begin
  if AOwner is TTestRadioGroup then
  begin
    InternalCreate( AOwner as TTestRadioGroup );
  end
  else
  begin
    inherited;
  end;
end;  

However it may be better to address the underlying problem which is that the buttons that you create are created on the fly both at design and at run time, so either do not create them at design time, or make sure that they are not saved at design time like this by setting the Stored value to FALSE.

constructor TTestGroupButton.InternalCreate(RadioGroup: TTestRadioGroup);
begin
  inherited Create(RadioGroup);
  RadioGroup.FButtons.Add(Self);
  Visible := False;
  Enabled := RadioGroup.Enabled;
  OnClick := RadioGroup.ButtonClick;
  Parent := RadioGroup;
  Stored := FALSE;   //////////// Make sure not saved in FMX file 
end;