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;
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
begin
UpdateLocations;
end;
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
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(Pa renPanel);
aFrame.Parent:=ParenPanel;
aFrame.SetOnClick(onMyFram eClick);
aFrame.Left:=...
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(Pa
aFrame.Parent:=ParenPanel;
aFrame.SetOnClick(onMyFram
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;
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
All you have to do is to calculate new positions of all those frames inside their client area on each resize of this area
ASKER
"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
Oh shoot, this may be the problem <smile> i will investigate
ASKER
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(Send er: 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(Sen der: TObject);
begin
UpdateUnits(pgeMain.Active Page);
end;
if the form is resized, i also update the units on the ative page
procedure TfrmMain.FormResize(Sender : TObject);
begin
UpdateUnits(pgeMain.Active Page);
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
when the program starts, i update the locations - (there is a tabsheet for each location record)
procedure TfrmMain.FormActivate(Send
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[
end;
anytime the page is changed, i update the units on the active page
procedure TfrmMain.pgeMainChange(Sen
begin
UpdateUnits(pgeMain.Active
end;
if the form is resized, i also update the units on the ative page
procedure TfrmMain.FormResize(Sender
begin
UpdateUnits(pgeMain.Active
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;
ASKER
sorry, the UpdateUnits method should have a test to see if the TabSheet is assign
if assigned(APage) then
begin
//
end;
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.
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.
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
ASKER
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.
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.