How to create (when I want to show it) and destroy (when I want to hide it) frames on the main TForm? Frames' align = alClient.
I tried this:
The form:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, uFrame1, uFrame2;
type
TFormMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
f1: TFrame1;
f2: TFrame2;
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
f1 := TFrame1.Create(Self);
f1.Parent := Self;
end;
end.
First frame:
unit uFrame1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame1 = class(TFrame)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame2;
procedure TFrame1.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f2 := TFrame2.Create(FormMain);
FormMain.f2.Parent := FormMain;
end;
end.
Second frame:
unit uFrame2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame2 = class(TFrame)
lbl1: TLabel;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame1;
procedure TFrame2.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f1 := TFrame1.Create(FormMain);
FormMain.f1.Parent := FormMain;
end;
end.
but it crashes with access vialataions when I click button on FrameStart or Frame1 (TForm FormCreate works fine i.e. it creates and shows FrameStart).
Delphi 7.
You can't call Self.Free in those event handlers. When the event handler returns, the VCL code that executes next still uses a reference to an object that you just freed. And that's where the access violation comes from. If you had been running with FastMM in full debug mode then you would have been shown a helpful diagnostic message.
These frames will have to kill themselves in a more roundabout manner. Post a CM_RELEASE message to the frame asking it to call Free on the frame. You post the message, rather than sending it, so that all the in flight messages are processed first. You'll need to add a message handler to the frame to respond to the message.
You've got some of it.
The basic idea behind this sort of stuff.
add a private property to your mainform to hold the frame.
in the button click handler assuming you only want one at a time do
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
fMyFrame := TSomeFrame.Create(self);
fMyFrame.Parent := self;
fMyFrame.blah...
When you just want to get rid of it as opposed to replacing it
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
If you want your frame to raise another frame, repeat the above in there.
If you want the frame you raise in a frame to be a sibling, e.g. have the same Parent, then don't use Form1 var.
fMyNextFrame.Parent = self.Parent;
There's a huge number of ways you could improve on this once you get it working, it's a classic scenario for interfaces and or inheritance, but figure this bit out first.
mySomething := TMySomething.Create();
you can now do something with something.
After you called free, it's not can't, it's don't and don't let anything else either.
Don't do self.free, it's like playing with matches in barrel of petrol. It will hurt....
Related
From a form, I create and show a second form. From the second form I want to update a control on the first form. But I get access violations. I can get it to work with the form in the autocreate, but not when I create the form with the create method I get violations.
Below is an example. If I run it as it is with form 11 in autocreate it works (I update a button caption in the first form). But, if in unit 10, if I comment out form11.show;, and I uncomment the create and the show and then take Form11 out of autocreate, I get an access violation.
Question - How can I update the parent form from the showed form when I create the form with a create method.
Unit10
unit Unit10;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm10 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form10: TForm10;
implementation
uses Unit11;
{$R *.dfm}
procedure TForm10.Button1Click(Sender: TObject);
var
fForm : TForm11;
Begin
// fForm := Form11.Create(Self); //This and next line give me access violation
// fForm.Show; // with form11 out of autocreate
form11.show; //This works with form11 in the autocreate.
end;
end.
Unit11
unit Unit11;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm11 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form11: TForm11;
implementation
uses unit10;
{$R *.dfm}
procedure TForm11.Button1Click(Sender: TObject);
begin
form10.button1.caption := 'Changed';
end;
end.
This is incorrect:
fForm := Form11.Create(Self)
It should be like this:
fForm := TForm11.Create(Self)
That is, TForm11, not Form11. To create an object, you have to call the constructor via the class.
I've always just had my forms auto-creating and can't think of a reason to not do so, but here's the likely cause of your problem:
The Create method needs to be called on a class, not on a variable.
This line would probably work to create a new instance of TForm11:
fForm := TForm11.Create(Self);
Look at the image below:
As you can see I cannot send Buttons to back. This only works for labels.
So how can I send TImage to front with its transparency.
By the way I've read This related question but didn't help me. Because you cannot even click on a button after running Andreas Rejbrand's code. Not only buttons, everything (like the scrollbar in this image)
Edit:
I don't want to make the button reachable after I send that back to the image. Just want to bring TImage to front of everything.
Thanks.
One way which could you near the goal would be to use interposer classes for the TWincontrols and paint the image moved on them, after they have been painted already, using a TControlCanvas and "hooking" WM_PAINT.
The code is showing a raw draft using a semitransparent PNG image and may be enhanced.
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, dxGDIPlusClasses, ExtCtrls;
type
TButton=Class (StdCtrls.TButton)
Procedure WMPaint(var MSG:TMessage);Message WM_Paint;
End;
TEdit=Class (StdCtrls.TEdit)
Procedure WMPaint(var MSG:TMessage);Message WM_Paint;
End;
TForm2 = class(TForm)
Image1: TImage;
SpeedButton1: TSpeedButton;
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TButton }
procedure TButton.WMPaint(var MSG: TMessage);
var
cc:TControlCanvas;
begin
inherited;
CC:=TControlCanvas.Create;
CC.Control := TControl(Self);
CC.Draw(-Left,-Top,Form2.Image1.Picture.Graphic);
CC.Free;
end;
procedure TEdit.WMPaint(var MSG: TMessage);
var
cc:TControlCanvas;
begin
inherited;
CC:=TControlCanvas.Create;
CC.Control := TControl(Self);
CC.Draw(-Left,-Top,Form2.Image1.Picture.Graphic);
CC.Free;
end;
end.
Another (better) place to "hook" would be overriding PaintWindow
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, dxGDIPlusClasses, ExtCtrls;
type
TButton=Class (StdCtrls.TButton)
procedure PaintWindow(DC: HDC);override;
End;
TEdit=Class (StdCtrls.TEdit)
procedure PaintWindow(DC: HDC);override;
End;
TForm2 = class(TForm)
Image1: TImage;
SpeedButton1: TSpeedButton;
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TButton }
procedure TButton.PaintWindow(DC: HDC);
var
cc:TCanvas;
begin
inherited;
CC:=TCanvas.Create;
CC.Handle := DC;
CC.Draw(-Left,-Top,Form2.Image1.Picture.Graphic);
CC.Free;
end;
procedure TEdit.PaintWindow(DC: HDC);
var
cc:TCanvas;
begin
inherited;
CC:=TCanvas.Create;
CC.Handle := DC;
CC.Draw(-Left,-Top,Form2.Image1.Picture.Graphic);
CC.Free;
end;
end.
You do not want the Image brought to front (which by the way is impossible over a windowed control), because you want the button also reachable.
Although your question is contradicting itself, and it is not at all clear what exactly you want to achieve, I think you mean to have a transparent button over an image.
If so, then use a TSpeedButton, and set its Transparent and Flat property to True.
Here an example with the three button states: normal, hovered, pressed:
You can use the solution you linked in your question. For the controls that you want the clicks go through - disable them. Since you're putting the image on a panel, disabling both the panel and the image will let the button to be clicked.
I wonder if there is some way to create TShape controls programmatically during runtime. For example, insteed of putting 100 shapes, hide them and when the program runs, show them, 100 shapes could be created over some time (5 shapes created in 5 seconds, 10 in 10 seconds, 15 in 15 seconds, and so on).
You should not draw and animate by using controls. Instead, you should draw manually using plain GDI or some other API. For an example, see this example or this example from one of your questions.
Anyhow, a simple answer to your question: Put a TTimer on your form and set its Interval to 250, and write:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
FShapes: array of TShape;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Timer1Timer(Sender: TObject);
begin
SetLength(FShapes, Length(FShapes) + 1); // Ugly!
FShapes[high(FShapes)] := TShape.Create(Self);
FShapes[high(FShapes)].Parent := Self;
FShapes[high(FShapes)].Width := Random(100);
FShapes[high(FShapes)].Height := Random(100);
FShapes[high(FShapes)].Left := Random(Width - FShapes[high(FShapes)].Width);
FShapes[high(FShapes)].Top := Random(Height - FShapes[high(FShapes)].Height);
FShapes[high(FShapes)].Brush.Color := RGB(Random(255), Random(255), Random(255));
FShapes[high(FShapes)].Shape := TShapeType(random(ord(high(TShapeType))))
end;
end.
When a user selects a value in my TDateTimePicker I want to override the to-be-set value to the start of the week that goes with the selected value.
I tried setting it in the OnChange event, but then the originally selected value will be set right after I finished the event.
How would I go about this?
use the "ONCloseUp" event - this sample works for me (Delphi 7, WinXP)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, DateUtils, StdCtrls;
type
TForm1 = class(TForm)
dtp1: TDateTimePicker;
btn1: TButton;
edt1: TEdit;
procedure btn1Click(Sender: TObject);
procedure dtp1CloseUp(Sender: TObject);
private
{ Private declarations }
procedure SetDayToMonday();
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.SetDayToMonday;
begin
dtp1.DateTime := dtp1.DateTime - DayOfTheWeek(dtp1.DateTime) + 1;
end;
procedure TForm1.dtp1CloseUp(Sender: TObject);
begin
SetDayToMonday;
end;
end.
--reinhard :-)
Use the onUserInput event!
I would post a message to the form, define a message (WM_USER+1000+X), post it, and handle it. If you don't "pend" it like this, you could also do a PendingDateTimeTimer:TTimer that does validation slightly later (say 10msec) after the OnChange event sets PendingDateTimeTimer.Enabled := true.
Is there a way to disable the parent windows buttons? I have a "working" form that is called by a lot of forms, that I would like to disable the parent form buttons until it's finished doing it's thing. Then turn them back on.
I'd like to do something that is attached to the OnShow event and onClose event.
Thanks
-Brad
Create the form you want to call, as in:
unit fMyModalForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TfrmMyModalForm = class(TForm)
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
fCallingForm: TForm;
{ Private declarations }
public
{ Public declarations }
property CallingForm: TForm read fCallingForm write fCallingForm;
end;
(*
var
frmMyModalForm: TfrmMyModalForm;
*)
implementation
{$R *.dfm}
procedure TfrmMyModalForm.FormShow(Sender: TObject);
begin
fCallingForm.Enabled := False;
end;
procedure TfrmMyModalForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
fCallingForm.Enabled := True;
end;
end.
Then after the button where you want to call this modal form:
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
fMyModalForm;
type
TfrmMain = class(TForm)
btnCall: TButton;
btn1: TButton;
btn2: TButton;
procedure btnCallClick(Sender: TObject);
private
{ Private declarations }
f : TfrmMyModalForm;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnCallClick(Sender: TObject);
begin
if not Assigned(f)
then begin
f := TfrmMyModalForm.Create(Self);
f.CallingForm := Self;
end;
f.Show();
end;
end.
If you just want to disable all buttons you can iterate through them and in stead of disabling the CallingForm only disable the buttons on the CallingForm. See the Stack Overflow topic (and my answer) at :Cast a form dynamically EDITED: or see answer of _J_ (which basically show the topic).
I would use actions in stead of buttons though.
If the secondary window opens, does something and closes, then it would make sense to open it with ShowModal instead of Show, that way the user can't use the main form until the second form has closed.
If you want to iterate though all the buttons and disable or enable them, the code would look something like this:
var
i: Integer;
begin
for i := 0 to MainForm.ComponentCount - 1 do
if (MainForm.Components[i] is TButton) then
TButton(MainForm.Components[i]).Enabled := False;
end;
For stuf like this you will need only 1 line of code and an TActionList component.
Create an actionlist with an action and link the action to the button. An action has an OnUpdate event which lets you determine if the action (and thus the linked button) should be enabled. The OnUpdate event is triggered everytime the action should know if it must be enabled or not.