Solved

Drawing on a scrollbox and redrawing when necessary

Posted on 2010-11-11
14
648 Views
Last Modified: 2012-05-10
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;
0
Comment
Question by:Looking_4_Answers
  • 6
  • 5
  • 2
  • +1
14 Comments
 
LVL 24

Expert Comment

by:jimyX
ID: 34111449
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.
0
 

Author Comment

by:Looking_4_Answers
ID: 34111797
"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
0
 
LVL 32

Expert Comment

by:Ephraim Wangoya
ID: 34112031

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.

0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 25

Expert Comment

by:epasquier
ID: 34113605
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

0
 
LVL 25

Expert Comment

by:epasquier
ID: 34113623
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
0
 

Author Comment

by:Looking_4_Answers
ID: 34113933
"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

0
 

Author Comment

by:Looking_4_Answers
ID: 34114308
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

0
 

Author Comment

by:Looking_4_Answers
ID: 34114373
sorry, the UpdateUnits  method should have a test to see if the TabSheet is assign

 if assigned(APage) then
 begin
  //
 end;

0
 
LVL 25

Expert Comment

by:epasquier
ID: 34114450
> 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.
0
 
LVL 25

Expert Comment

by:epasquier
ID: 34114468
> 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.
0
 
LVL 25

Expert Comment

by:epasquier
ID: 34114494
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
0
 

Author Comment

by:Looking_4_Answers
ID: 34114504
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.
0
 
LVL 25

Accepted Solution

by:
epasquier earned 500 total points
ID: 34114674
really use TFrame.
You can put all kinds of controls on it, or draw in a TImage if you need special drawing.  And with the SetOnClick trick...
0
 
LVL 32

Expert Comment

by:Ephraim Wangoya
ID: 34117323
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

0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …
Are you ready to implement Active Directory best practices without reading 300+ pages? You're in luck. In this webinar hosted by Skyport Systems, you gain insight into Microsoft's latest comprehensive guide, with tips on the best and easiest way…

730 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question