Link to home
Start Free TrialLog in
Avatar of Looking_4_Answers
Looking_4_Answers

asked on

Drawing on a scrollbox and redrawing when necessary

Two questions:


1.) Im currently drawing TPanels on a TScrollbox (aligned alClient on my main form)  based off from records in a table.  The Tpanels will not work for me.

I need a control i can write some info on. a.) Id  b.) name, and c.) status.  It must be a control that has a tag (where i can store the "id") and it must have a onClick event.  ATPanel has all these except that it will only allow me to write in the caption. I need to write anywhere on its canvas. Need to be able to change font size , type, and color as well

2.) My controls (currently Tpanels) are drawn left to right and when it reaches the client width, it drops down and starts the drawing all over again.

I currenly have the Vertical scrollbar visible , but not the Horizontal.

How do i redraw everything once the form is resized. Example, when a user clicks the maximize on the form?

Placing the redraw in the OnResize event, works, but with some annoying flashing, like its being redrawn a number of times

procedure TfrmMain.FormResize(Sender: TObject);
begin
  UpdateLocations;
end;
Avatar of jimyX
jimyX

Not sure if I fully understand your question:

I read your previous question before you delete and prepared this as a reply (just in case it might help)
What you can do is create one of the components that have tag, onClick event and change font [size , type, color] at the run time on your panel such as:
TLabels which can be created at the run time and placed any where on your Panel or TSpeed button.

Regarding the current question you have 4 options:
Option 1: disable maximize option.
Option 2: you can rearrange your components on the form when maximized by calculating the height and width of your form then calculate that and divide equally the width of your components so they appear on the form no matter what is the size of it.
option 3: limit the maximization option between two states one medium and one large and adjust your components on the current state of the form (you will be having different components position for each state).
Option 4: just let the form be resized without affecting your components.

I suggest you go with option 2 or 3.
Avatar of Looking_4_Answers

ASKER

"I read your previous question before you delete and prepared this as a reply (just in case it might help)
What you can do is create one of the components that have tag, onClick event and change font [size , type, color] at the run time on your panel such as:
TLabels which can be created at the run time and placed any where on your Panel or TSpeed button."

Not an option, since if you then click on that component placed on your component, the onlcick event of the component you want will not work.

"Option 1: disable maximize option." - sorry, not an option

"Option 2:" I already do this with the code i show above

UpdateLocations;

which is called when the program first starts, after each new record is added in the database, etc.

I even call it on resize....but i dont think that is the place to call it. It gets flashy (or called more than once)

"Option 3"  Sorry, not an option

"Option 4" - Sorry, not an option

You can still use the TPanel and instead of changing the caption, use Canvas.TextOut to write to the canvas of the TPanel.

The other thing is that rerunning UpdatePanels, erases all the tabs. You only need to clear the panels in the active Tab and redraw them. Redraw panels in the other tabs only when the user changes to those tabs using the OnPageChange event. have a variable that indicates whether a tab needs refresh or not.

each time I had such needs as yours, I went for frames to represent those 'objects' of yours.

a frame is much like a panel, but it can be designed separately in the IDE, then created dynamically as you are with your TPanels. That is much better to visualize the result you'll have. Also, if you put buttons or other components on that frame, you don't have to set manually all events as you would with creating all objects dynamically on a panel.

On that frame, you can put whatever labels you want. you can easily circumvent the onClick problem by setting the same onClick event on all your frame's label (I generalize to all controls  that do not have an onClick event set already)

aFrame:=TMyFrame.Create(ParenPanel);
aFrame.Parent:=ParenPanel;
aFrame.SetOnClick(onMyFrameClick);
aFrame.Left:=...
procedure TMyFrame.SetOnClick(onClickEvent:TNotifyEvent);
var i:integer;
begin
 Self.onClick:=onClickEvent; // set it for the frame itself
 for i:=0 to ControlCount-1 do 
  if Not Assigned(Controls[i].onClick) // check a design event is not set allready
   Then Controls[i].onClick:=onClickEvent; // set it for all controls of the frame 
end;

Open in new window

with a frame you don't have to manage redraw manually, it will like any other set of components would.
All you have to do is to calculate new positions of all those frames inside their client area on each resize of this area
"The other thing is that rerunning UpdatePanels, erases all the tabs. You only need to clear the panels in the active Tab and redraw them. Redraw panels in the other tabs only when the user changes to those tabs using the OnPageChange event. have a variable that indicates whether a tab needs refresh or not."

Oh shoot, this may be the problem  <smile>  i will investigate

find attached , the code i use to do my drawing

when the program starts, i update the locations - (there is a tabsheet for each location record)

procedure TfrmMain.FormActivate(Sender: TObject);
begin
 //
 UpdateLocations;
end;

The last thing I do in the UpdateLocations call is get the activepage and update the units on that page

 if pgeMain.PageCount > 0 then
 begin
  pgeMain.ActivePageIndex:= 0;
  UpdateUnits(pgeMain.Pages[0]);
 end;

anytime the page is changed, i update the units on the active page

procedure TfrmMain.pgeMainChange(Sender: TObject);
begin
 UpdateUnits(pgeMain.ActivePage);
end;

if the form is resized, i also update the units on the ative page

procedure TfrmMain.FormResize(Sender: TObject);
begin
 UpdateUnits(pgeMain.ActivePage);
end;


There are a number of issues here.

1.) I still get the flashing while drawing

2.) there is an issue with cleanup (freeing some something), cause i get access violations when i close the project
function TfrmMain.getScrollBox(APage: TTabSheet): TScrollBox;
var
 I: Integer;
begin
 result:= nil;
 for I:= 0 to APage.ControlCount-1 do
  if APage.Controls[I] is TScrollBox then
  begin
   result:= TScrollBox(APage.Controls[I]);
   Exit;
  end;
end;

procedure TfrmMain.RemoveLocations;
var
 I: Integer;
begin
 for I:= pgeMain.PageCount-1 downto 0 do
  pgeMain.Pages[I].Destroy;
end;

//procedure TfrmMain.UpdateUnits(ATabSheet: TTabSheet);
procedure TfrmMain.UpdateUnits(APage: TTabSheet);
var
 AUnit: TPanel;
 X, Y, W, H, S: Integer;
 P: Integer;
 AScrollBox: TScrollBox;
begin
 AScrollbox:= getScrollBox(APage);
 X:= 10; Y:= 10; W:= 75; H:= 75; S:= 5;
 frmDataMod.qryUnits.Active:= False;
 try
  frmDataMod.qryUnits.Parameters.ParamByName('locname').Value:= APage.Caption;
  frmDataMod.qryUnits.Active:= True;
  if frmDataMod.qryUnits.RecordCount > 0 then
  begin
   while not frmDataMod.qryUnits.EOF do
   begin
    AUnit:= TPanel.Create(AScrollBox);
    AUnit.Parent:= AScrollBox;
    AUnit.BringToFront;
    AUnit.ParentColor:= False;
    Aunit.ParentBackground:= False;
    AUnit.BevelOuter:= bvRaised;
    AUnit.BevelInner:= bvLowered;
    AUnit.Color:= clGreen;
    AUnit.Font.Color:= clYellow;
    AUnit.Font.Style:= [fsBold];
    AUnit.Left:= X;
    AUnit.Top:= Y;
    AUnit.Width:= W;
    AUnit.Height:= H;
    AUnit.Caption:= frmDataMod.qryUnits.fieldByName('unit_number').AsString + ' - ' + frmDataMod.qryUnits.fieldByName('unit_type').AsString;
    frmDataMod.qryUnits.Next;
     P:= X + W + S;
    if (P + W ) < AScrollBox.ClientWidth then
     X:= P //X = X + Width + space
    else
    begin
     X:= 10;
     Y:= Y + H + S; //Y = Y + Hieght + Space
    end;
   end;
  end;//if
 except
 begin
  ShowMessage('Error retrieving units to display on location pages!');
 end;
 end;//try
end;

procedure TfrmMain.UpdateLocations;
var
 Locations: TStringList;
 I: Integer;
 APage: TTabSheet;
 AScrollBox: TScrollBox;
begin
 RemoveLocations;
 Locations:= TStringList.Create;
 frmDataMod.GetLocations(Locations, False);
 for I:= 0 to Locations.Count-1 do
 begin
  APage:=  TTabSheet.Create(pgeMain);
  APage.PageControl:= pgeMain;
  APage.Parent:= pgeMain;
  APage.Caption:= Locations[I];
  AScrollBox:= TScrollBox.Create(APage);
  AScrollBox.Parent:= APage;
  AScrollBox.Align:= alClient;
  AScrollBox.VertScrollBar.Visible:= True;
 end;
 if pgeMain.PageCount > 0 then
 begin
  pgeMain.ActivePageIndex:= 0;
  UpdateUnits(pgeMain.Pages[0]);
 end;
 Locations.Free;
end;

Open in new window

sorry, the UpdateUnits  method should have a test to see if the TabSheet is assign

 if assigned(APage) then
 begin
  //
 end;

> I still get the flashing while drawing
That is because you erase everything and recreate every component of your page each time.

You don't need to do that, your Update function should only create new ones since last update and delete those that are no longer there. You can do that by setting the Tag property of the panel (or frame) to a unique ID of the object it represent.

I could make you a sample application creating pages of frames inside a scrollbox that would redraw smartly, but monday only.
> there is an issue with cleanup (freeing some something), cause i get access violations when i close the project
a simpler/smarter update method would probably fix that as well.
that really is too bad I don't have time to do it now and will be out the next 3 days, such a sample application would need only one hour... You can count on me monday if you don't solve it by then
also, for question #1,  the TPanel does not have a exposed Canvas property and therefore i can't draw on the tpanel. I could hack the Tpanel and create a version that exposes the canvas, but then i would have to overide the Paint method.....this will not work for me.
ASKER CERTIFIED SOLUTION
Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Use double buffered and set a variable to know when to refresh, try this code. no flickering and you have full control
unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TUnitPanel = class(TPanel)
  private
    FDescriptionLabel: TLabel;
    FID: Integer;
    FStatus: string;
    FUnitNumberLabel: TLabel;
    FUnitNumber: string;
    FUnitTypeLabel: TLabel;
    FUnitType: string;
    procedure SetUnitNumber(const Value: string);
    function GetDescription: string;
    procedure SetDescription(const Value: string);
    procedure SetUnitType(const Value: string);
  public
    constructor Create(AOwner: TComponent); override;
    property Description: string read GetDescription write SetDescription;
    property ID: Integer read FID write FID;
    property Status: string read FStatus write FStatus;
    property UnitNumber: string read FUnitNumber write SetUnitNumber;
    property UnitType: string read FUnitType write SetUnitType;
  end;

  TfrmMain = class(TForm)
    pgeMain: TPageControl;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure pgeMainChange(Sender: TObject);
  private
    FNeedResize: array of Boolean;
    FRedrawList: TList;
    FScrollWidth: Integer;
    function GetScrollBox(APage: TTabSheet): TScrollBox;
    procedure RemoveLocations;
    function ResizePending(AIndex: Integer): Boolean;
    procedure SetNeedsResize(AExcludeIndex: Integer);
    procedure UpdateLocations;
    procedure UpdateUnits(APage: TTabSheet);
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

{ TUnitPanel }

constructor TUnitPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  CAption := '';
  Width := 75;
  Height := 75;
  Color := clGreen;
  BevelOuter:= bvRaised;
  BevelInner:= bvLowered;
  Font.Color:= clYellow;
  Font.Style:= [fsBold];

  FUnitNumberLabel := TLabel.Create(Self);
  FUnitNumberLabel.Parent := Self;
  FUnitNumberLabel.Top := 5;
  FUnitNumberLabel.Left := 2;

  FUnitTypeLabel := TLabel.Create(Self);
  FUnitTypeLabel.Parent := Self;
  FUnitTypeLabel.Top := 20;
  FUnitTypeLabel.Left := 2;

  FDescriptionLabel := TLabel.Create(Self);
  FDescriptionLabel.Parent := Self;
  FDescriptionLabel.Top := 35;
  FDescriptionLabel.Left := 2;
end;

function TUnitPanel.GetDescription: string;
begin
  Result := FDescriptionLabel.Caption;
end;

procedure TUnitPanel.SetDescription(const Value: string);
begin
  FDescriptionLabel.Caption := Value;
  Invalidate;  //really no need for this
end;

procedure TUnitPanel.SetUnitNumber(const Value: string);
begin
  if FUnitNumber <> Value then
  begin
    FUnitNumber := Value;
    FUnitNumberLabel.Caption := Value;
  end;
end;

procedure TUnitPanel.SetUnitType(const Value: string);
begin
  if FUnitType <> Value then
  begin
    FUnitType := Value;
    FUnitTypeLabel.Caption := Value;
  end;
end;

{TfrmMain}

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FRedrawList := TList.Create;
  FScrollWidth := GetSystemMetrics(SM_CXVSCROLL);
  DoubleBuffered := True;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FRedrawList);
end;

procedure TfrmMain.FormResize(Sender: TObject);
var
  I: Integer;
  ScrollBox: TScrollBox;
  AUnit: TUnitPanel;
  P, X, Y, S: Integer;
begin
  if pgeMain.PageCount = 0 then
    exit;
  FRedrawList.Clear;
  ScrollBox := getScrollBox(pgeMain.ActivePage);
  if Assigned(ScrollBox) then
  begin
    for I := ScrollBox.ControlCount - 1 downto 0 do
      if ScrollBox.Controls[I] is TPanel then
      begin
        ScrollBox.Controls[I].Visible := False;
        FRedrawList.Add(ScrollBox.Controls[I]);
      end;

    X:= 10; Y:= 10; S:= 5;  I := 0;
    for I := FRedrawList.Count - 1 downto 0 do
    begin
      AUnit := TUnitPanel(FRedrawList[I]);
      AUnit.Left:= X;
      AUnit.Top:= Y;
      AUnit.Visible := True;
      P:= X + AUnit.Width + S;
      if (P + AUnit.Width + FScrollWidth ) < ScrollBox.ClientWidth then
       X:= P //X = X + Width + space
      else
      begin
       X:= 10;
       Y:= Y + AUnit.Height + S; //Y = Y + Hieght + Space
      end;
    end;

    SetNeedsResize(pgeMain.ActivePageIndex);
  end;
end;

function TfrmMain.getScrollBox(APage: TTabSheet): TScrollBox;
var
 I: Integer;
begin
 result:= nil;
 for I:= 0 to APage.ControlCount-1 do
  if APage.Controls[I] is TScrollBox then
  begin
   result:= TScrollBox(APage.Controls[I]);
   Exit;
  end;
end;

procedure TfrmMain.pgeMainChange(Sender: TObject);
var
  ScrollBox: TScrollBox;
begin
  ScrollBox := GetScrollBox(pgeMain.ActivePage);
  if Assigned(ScrollBox) then
  begin
    if (ScrollBox.ControlCount <= 0) then
      UpdateUnits(pgeMain.ActivePage)
    else if ResizePending(pgeMain.ActivePageIndex) then
      FormResize(Self);
  end;
end;

procedure TfrmMain.RemoveLocations;
var
 I: Integer;
begin
 for I:= pgeMain.PageCount-1 downto 0 do
    pgeMain.Pages[I].Destroy;
end;

function TfrmMain.ResizePending(AIndex: Integer): Boolean;
begin
  Result := (AIndex >= 0) and (AIndex < Length(FNeedResize)) and FNeedResize[AIndex];
end;

procedure TfrmMain.SetNeedsResize(AExcludeIndex: Integer);
var
  I: Integer;
begin
  for I := 0 to Length(FNeedResize) - 1 do
    FNeedResize[I] := I <> AExcludeIndex;
end;

procedure TfrmMain.UpdateUnits(APage: TTabSheet);
var
 AUnit: TUnitPanel;
 X, Y, S: Integer;
 P: Integer;
 AScrollBox: TScrollBox;
begin
 AScrollbox:= getScrollBox(APage);
 if not Assigned(AScrollbox) then
  Exit;

 X:= 10; Y:= 10; S:= 5;
 frmDataMod.qryUnits.Active:= False;
 try
  frmDataMod.qryUnits.Parameters.ParamByName('locname').Value:= APage.Caption;
  frmDataMod.qryUnits.Active:= True;
  if frmDataMod.qryUnits.RecordCount > 0 then
  begin
   while not frmDataMod.qryUnits.EOF do
   begin
    AUnit:= TUnitPanel.Create(AScrollBox);
    AUnit.Parent:= AScrollBox;
    AUnit.BringToFront;
    AUnit.ParentColor:= False;
    Aunit.ParentBackground:= False;
    AUnit.BevelOuter:= bvRaised;
    AUnit.BevelInner:= bvLowered;
    AUnit.Color:= clGreen;
    AUnit.Font.Color:= clYellow;
    AUnit.Font.Style:= [fsBold];
    AUnit.Left:= X;
    AUnit.Top:= Y;
    AUnit.UnitNumber := frmDataMod.qryUnits.fieldByName('unit_number').AsString;
    AUnit.UnitType := frmDataMod.qryUnits.fieldByName('unit_type').AsString;
    frmDataMod.qryUnits.Next;
     P:= X + AUnit.Width + S;
    if (P + AUnit.Width + FScrollWidth) < AScrollBox.ClientWidth then
     X:= P //X = X + Width + space
    else
    begin
     X:= 10;
     Y:= Y + AUnit.Height + S; //Y = Y + Hieght + Space
    end;
   end;
  end;//if
 except
 begin
  ShowMessage('Error retrieving units to display on location pages!');
 end;
 end;//try
end;

procedure TfrmMain.UpdateLocations;
var
 I: Integer;
 Locations: TStringList;
 APage: TTabSheet;
 AScrollBox: TScrollBox;
begin
 //RemoveLocations; no need for remove locations
 Locations:= TStringList.Create;
 try
   frmDataMod.GetLocations(Locations, False);
   for I:= 0 to Locations.Count-1 do
   begin
    APage:=  TTabSheet.Create(pgeMain);
    APage.PageControl:= pgeMain;
    APage.Parent:= pgeMain;
    APage.Caption:= Locations[I];
    AScrollBox:= TScrollBox.Create(APage);
    AScrollBox.Parent:= APage;
    AScrollBox.Align:= alClient;
    AScrollBox.VertScrollBar.Visible:= True;
   end;
   if pgeMain.PageCount > 0 then
   begin
    pgeMain.ActivePageIndex:= 0;
    UpdateUnits(pgeMain.Pages[0]);
   end;
   SetLength(FNeedResize, Locations.Count);
 finally
   FreeAndNil(Locations);
 end;
end;

end.

Open in new window