Question

I have a few controls (namely, TDBChart) inside a TFlowPanel. When the user clicks on one of them, I'd like it to fill the entire flow panel's client area. But, it seems that changing the visible and align property of child controls inside a flow panel at run time doesn't have any effect. Is there a special trick to this? I found the Realign() method, but it doesn't seem to have any effect on the control's layout. Here's the code to my OnClick event:

var
  AChart: TDBChart;
  V: Boolean;
  i: Integer;
begin
  AChart := TDBChart(Sender);
  if AChart.Align = alNone then
  begin
    V := False;
    AChart.Align := alClient;
  end else begin
    V := True;
    AChart.Align := alNone;
  end;
  for i := 0 to FlowPanel1.ControlCount - 1 do
    if FlowPanel1.Controls[i] is TDBChart then
      if FlowPanel1.Controls[i] <> AChart then
        FlowPanel1.Controls[i].Visible := V;
end;

The charts are hidden or shown as expected, but ADBChart doesn't fill the entire flow panel's client area.

Was it helpful?

Solution

A FlowPanel does not care its controls' alignment settings, much like it doesn't care for their position - it is designed only to flow them.

One solution can be to derive a new class and override AlignControls, and in it, resize the control that would fill the surface accordingly. As an example:

type
  TFlowPanel = class(extctrls.TFlowPanel)
  protected
    procedure AlignControls(AControl: TControl; var Rect: TRect); override;
  end;

..

procedure TFlowPanel.AlignControls(AControl: TControl; var Rect: TRect);
var
  i, VisibleCount, VisibleControl: Integer;
begin
  VisibleCount := 0;
  VisibleControl := 0;
  for i := 0 to ControlCount - 1 do
    if Controls[i].Visible then begin
      Inc(VisibleCount);
      VisibleControl := i;
    end;

  if (VisibleCount = 1) and (Controls[VisibleControl] = AControl) and
      (AControl.Align = alClient) then begin
    // preserve 'Explicit..' settings
    AControl.ControlState := AControl.ControlState + [csAligning];
    AControl.SetBounds(1, 1, ClientWidth - 1, ClientHeight -1);
    AControl.ControlState := AControl.ControlState - [csAligning];
  end;

  inherited;
end;

Then you can set all of your charts' click event to this handler:

var
  AChart: TTDBChart;

  procedure SetVisibility(Visible: Boolean);
  var
    i: Integer;
  begin
    for i := 0 to FlowPanel1.ControlCount - 1 do
      if FlowPanel1.Controls[i] is TDBChart then
        if FlowPanel1.Controls[i] <> AChart then
          FlowPanel1.Controls[i].Visible := Visible;
  end;

begin
  AChart := TDBChart(Sender);
  if AChart.Align = alNone then
  begin
    SetVisibility(False);
    AChart.Align := alClient;
  end else begin
    AChart.Align := alNone; // set before changing visible
    SetVisibility(True);
    AChart.SetBounds(0, 0, AChart.ExplicitWidth, AChart.ExplicitHeight);
  end;
end;

I should note that this is only good for a fixed sized flowpanel.

OTHER TIPS

As by design, T(Custom)FlowPanel uses customized aligning of child controls, which is implemented in an overriden AlignControls method.

You can prevent this default behaviour by skipping it, falling back on that from its ancestor. Also, hiding all adjacent controls is not necessary. Bringing the clicked chart to front will suffice.

type
  TFlowPanel = class(Vcl.ExtCtrls.TFlowPanel)
  private
    FFlowDisabled: Boolean;
    procedure SetFlowDisabled(Value: Boolean);
  protected
    procedure AlignControls(AControl: TControl; var Rect: TRect); override;
  public
    property FlowDisabled: Boolean read FFlowDisabled write SetFlowDisabled;
  end;

...

{ TFlowPanel }

type
  TWinControlAccess = class(TWinControl);
  TAlignControls = procedure(Instance: TObject; AControl: TControl;
    var Rect: TRect);

procedure TFlowPanel.AlignControls(AControl: TControl; var Rect: TRect);
begin
  if FFlowDisabled then
    // Skip inherited in TCustomFlowPanel:
    TAlignControls(@TWinControlAccess.AlignControls)(Self, AControl, Rect)
  else
    inherited;
end;

procedure TFlowPanel.SetFlowDisabled(Value: Boolean);
begin
  if FFlowDisabled <> Value then
  begin
    FFlowDisabled := Value;
    Realign;
  end;
end;    

{ TForm1 }

procedure TForm1.DBChartClick(Sender: TObject);
const
  FlowAligns: array[Boolean] of TAlign = (alNone, alClient);
var
  Chart: TDBChart;
  Panel: TFlowPanel;
  DisableFlow: Boolean;
begin
  Chart := TDBChart(Sender);
  Panel := Chart.Parent as TFlowPanel;
  DisableFlow := not Panel.FlowDisabled;
  Chart.Align := FlowAligns[DisableFlow];
  Chart.BringToFront;
  Panel.FlowDisabled := DisableFlow;
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top