I have a form one which I want to show a file open dialog box before the full form opens.
I already found that I can't do UI related stuff in FormShow, but it seems that I can in FormActivate (which I protect from being called a second time...)
However, if the user cancels out of the file open dialog, I want to close the form without proceeding.
But, a form close in the activate event handler generates an error that I can't change the visibility of the form.
So how does one do some UI related operation during form start up and then perhaps abort the form (or am I trying to stuff a function into the form that should be in another form?)
TIA
It would be best (i think) to show the file open dialog BEFORE you create and show the form. If you want to keep all code together you might add a public class procedure OpenForm() or something:
class procedure TForm1.OpenForm( ... );
var
O: TOpenDialog;
F: TForm1;
begin
O := TOpenDialog.Create();
try
// set O properties.
if not O.Execute then Exit
F := TForm1.Create( nil );
try
F.Filename := O.FIlename;
F.ShowModal();
finally
F.Free();
end;
finally
O.Free();
end;
end;
Set a variable as a condition of the opendialog and close the form on the formshow event if the flag is not set correctly.
procedure TForm1.FormCreate(Sender: TObject);
begin
ToClose := not OpenDialog1.Execute;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
if ToClose then Close();
end;
or even more simply
procedure TForm1.FormShow(Sender: TObject);
begin
if not OpenDialog1.Execute then Close();
end;
If you want to keep the logic conditioning the opening self-contained in the Form, you can put a TOpenDialog in your Form and use a code like this in your OnShow event:
procedure TForm2.FormShow(Sender: TObject);
begin
if OpenDialog1.Execute(Handle) then
Color := clBlue
else
PostMessage(Handle, WM_CLOSE, 0, 0); // NB: to avoid any visual glitch use AlpaBlend
end;
If you don't need this encapsulation, a better alternative can be to check the condition before trying to show the form, for instance by embedding the Form2.Show call in a function that tests all the required conditions first.
Two Ways....
1. using oncreate and onactivate
create a global flag or even 2
var
aInitialized:boolean;
Set the flag to false in the oncreate handler.
aInitialized := false; //we have not performed our special code yet.
Inside onActivate have something like this
if not aInitialized then
begin
//our one time init code. special stuff or whatever
If successful
then set aInitialized := true
else aInitialized := false
end;
And how to close it without showing anything just add your terminate to the formshow. of course you need to test for some reason to close.. :)
Procedure Tmaindlg.FormShow(Sender: TObject);
Begin
If (shareware1.Sharestatus = ssExpired) or (shareware1.Sharestatus = ssTampered) Then
application.Terminate;
End;
In your DPR you will need to add a splash screen type effect. In my case I am showing progress as the application starts. You could also just show the form and get some data.
Code from the splash.pas
Procedure tsplashform.bumpit(str: string);
Begin
label2.Caption := str;
gauge1.progress := gauge1.progress + trunc(100 / items);
update;
If gauge1.progress >= items * (trunc(100 / items)) Then Close;
End;
Program Billing;
uses
Forms,
main in 'main.pas' {maindlg},
Splash in 'splash.pas' {splashform};
{$R *.RES}
Begin
Application.Initialize;
Application.Title := 'Billing Manager';
SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update;
splash.items := 5;
SplashForm.bumpit('Loading Main...');
Application.CreateForm(Tmaindlg, maindlg);
SplashForm.bumpit('Loading Datamodule...');
Application.CreateForm(TfrmSingleWorkorder, frmSingleWorkorder);
SplashForm.bumpit('Loading SQL Builder...');
Application.CreateForm(TDm, Dm);
SplashForm.bumpit('Loading Security...');
Application.CreateForm(TSQLForm, SQLForm);
SplashForm.bumpit('Loading Reports...');
Application.CreateForm(Tpickrptdlg, pickrptdlg);
Application.Run;
End.
Related
I have these controls TDateTimePicker, TComboBox, Tedit and TButton. TButton is disabled by default. What I would like to achieve is to enable TButton when all the other controls are filled or not null.
With the following codes, all the 3 controls starting with TDateTimePicker when filled I don't have any issues it works as expected.
The error comes when I fill TComboxBox followed by TEdit, it enables the TButton even TDateTimePicker is not filled yet. Or vise versa, I will fill TEdit followed by TComboBox, it enables the TButton.
From the codes below, I expect the TButton will not enable unless all the 3 controls are filled.
I've been trying to figure out (all day) how this error come to happen.
I will appreciate anyone there help me figure this out.
procedure TfrmHolidays.EnableSaveButton;
begin
if (edtHolidayName.Text <> NullAsStringValue) and (cmbHolidayType.ItemIndex <> -1)and (dtpHolidayDate.Date <> 0) then
begin
btnHolidaySave.Enabled := True;
end
else
begin
btnHolidaySave.Enabled := False;
end;
end;
procedure TfrmHolidays.dtpHolidayDateChange(Sender: TObject);
begin
EnableSaveButton;
end;
procedure TfrmHolidays.cmbHolidayTypeChange(Sender: TObject);
begin
EnableSaveButton;
end;
procedure TfrmHolidays.edtHolidayNameChange(Sender: TObject);
begin
EnableSaveButton; // triggers enable btnHolidaySave button
end;
By the way, I have more code related to making TDateTimePicker a blank and I supposed there's no issues with that. I also tried nesting within If Statement each condition and I am still getting the error. Further, I tested each condition at a time and It works fine.
Updates:
Here's how I initialized the dtpHolidayDate.Date:
procedure TfrmHolidays.FormCreate(Sender: TObject);
begin
DateTime_SetFormat(dtpHolidayDate.Handle, ' ');
FDTMDateEmpty := True;
end;
procedure TfrmHolidays.dtpHolidayDateCloseUp(Sender: TObject);
begin
DateTime_SetFormat(dtpHolidayDate.Handle, PChar('MMM dd yyyy (ddd)'));
end;
procedure TfrmHolidays.dtpHolidayDateChange(Sender: TObject);
begin
FDTMDateEmpty := False;
EnableSaveButton; // same and updated procedure above
end;
As pointed out in the comments above, you do not have an initialised value for the TDateTimePicker.
What you want is to have it default to a sensible date, rather than set to 0 - that is not at all helpful to users.
I would introduce a Boolean flag that you set yourself once the TDateTimePicker has been set.
You could set this flag in the OnChange event handler.
So something like:
interface
protected
blMyDTFlag: Boolean;
...
implementation
function TfrmHolidays.dtpHolidayDateChange(Sender: TObject);
begin
Self.blMyDTFlag:=True;
end
procedure TfrmHolidays.EnableSaveButton;
begin
if (edtHolidayName.Text <> '') and
(cmbHolidayType.ItemIndex <> -1) and
(Self.dtMyDTFlag) then
btnHolidaySave.Enabled := True
else
btnHolidaySave.Enabled := False;
end;
Although not shown in the question, I can guess that you initialize the date by
dtpHolidayDate.Date := 0;
After this, testing the date against 0 will (most likely) fail because the time portion still contains the time that the control is created.
You can initialize by
dtpHolidayDate.DateTime := 0;
then you can test the date as you do.
Alternatively you can use SameDate to do the comparison.
uses
dateutils;
if (edtHolidayName.Text <> NullAsStringValue) and (cmbHolidayType.ItemIndex <> -1)
and (not SameDate(dtpHolidayDate.Date, 0)) then
I would like to transfer data from the statusbar of Form2 (which is on the list of available forms and used for login purposes), to the statusbar on the MainForm (which is autocreated one).
If I use :
procedure TMainForm.FormShow(Sender: TObject);
begin
AdvOfficeStatusBar1.Panels[0].Text := Form2.AdvOfficeStatusBar1.Panels[0].Text;
end;
I get an access violation error. . Why is this happening? How can I get over this?
Based on your comment I would say that the following is occurring:
The code you would be using for your login form is this:
procedure Login;
begin
with TForm2.Create(nil) do
try
Application.MainForm.Hide;
if ShowModal = mrOK then
Application.MainForm.Show
else
Application.Terminate;
finally
Free;
end;
end;
What is happening here is that there is an implicit variable for TLoginForm. This is not the same as the auto created variable which would be Form2: TForm2; which sits in the TForm2 unit. This variable is freed directly after the form closes.
To see what I mean. If you delete the variable called Form2 from your application, the only part of the code that won't compile is your line in your original post.
What you would have to do if you wanted to do this sort of thing is something like this (I have changed the name of TLoginForm to your Form2).
procedure Login;
begin
Form2 := TForm2.Create(nil);
Application.MainForm.Hide;
if Form2.ShowModal = mrOK then
Application.MainForm.Show
else
begin
Form2.Free;
Application.Terminate;
end;
end;
The you would have to free your Form2 when you close your main form. I would really not recommend doing this sort of thing though. As a quick fix, you would be far better off saving the text AdvOfficeStatusBar1.Panels[0].Text in a global variable in the OnClose event of the Form2 with something like this:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
g_MySavedVariable := AdvOfficeStatusBar1.Panels[0].Text;
end;
And then loading it in the main form with:
procedure TMainForm.FormShow(Sender: TObject);
begin
AdvOfficeStatusBar1.Panels[0].Text := g_MySavedVariable;
end;
Even this is not ideal but should get you up and running.
If you are looking for some code for a login form where you pass data between the login form and the main form then you are probably better off looking at this question in StackOverflow (delphi Login Form) along with the answers by David and Cosmin. My personal preference is for the solution by Cosmin as you don't need to mess with the DPR and there is no global variables involved.
My form1 is the mainform. I use it for login purposes and after I dont need it
I hide it. If the login is successful, on button click,form3 is called.
procedure TForm1.AdvGlowButton1Click(Sender: TObject);
begin
ABSQuery4.Active:=false;
ABSQuery4.SQL.Clear;
ABSQuery4.SQL.Add('select .....bla,bla,bla....');
ABSQuery4.Open;
if ABSQuery4.FieldByName('passsword').AsString<>''
then begin
Form3.Show;
Form1.Hide;
end else begin
cxTextedit1.Text := '';
showmessage('wrong password');
end;
end;
Now I am noticing that sometimes the event produces strange results.
Form3 is shown but Form1 remains visible also. For showing the main form
from form3 I use :
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Form1.Show;
end
Since I need the application icon for Form3, I have there :
procedure TForm3.CreateParams(var Params: TCreateParams) ;
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
Params.WndParent := 0;
end;
Can you tell me am I messing anything up or is there a better way to
make the forms behave properly?
There is a better way to do this. For a start, you should give your forms meaningful names!
Your real problem is that your main form is being hidden because you are using it as your login form. You make life needlessly difficult and complicated by doing that.
So the main piece of advice is that you should make you real main form be the Delphi main form. The Delphi main form is the first form created using Application.CreateForm. I suggest that you call Application.CreateForm exactly once, to create the main form.
This may leave you wondering how to create other forms. Well, you just create them using the standard constructor, just like any other object.
So your .dpr file code might look like this:
Application.Initialize;
LoginForm := TLoginForm.Create(nil);
try
if LoginForm.ShowModal <> mrOK then
exit;
finally
LoginForm.Free;
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
Once you make this change you will find that you don't need to take steps to force the main form onto the taskbar.
I have a DLL application that is loaded in my main application.
The DLL contains a form that is created at runtime.
The functionality is:
In main application I have a menu which whenever pressed calls a procedure from within the DLL. This procedure dynamically creates the form.
procedure doCreateForm;
var
myForm: TForm1;
begin
myForm := TForm1.Create(nil)
try
...
except
myForm.Free;
end;
end;
The closing procedures:
procedure CloseWindow(ASender: TForm1);
begin
FreeAndNil(ASender);
Application.ProcessMessages;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseWindow(Self);
end;
The problem (access violation) occurs only on the second attempt to create the form. Not first time, not 3rd, 4th, 5th and so on.
So I click on the menu, the form is created (dynamically) and closed (if condition is not satisfied during form create event). I click again on the menu, and when myForm.Create(nil) is called, AV raises. I click again on the menu, and all is ok. I click again and again and again and all is ok. Only when pressed the 2nd time, AV raises.
Is there something wrong with dynamic creation of visual form in DLL?
A more detailed explanation:
The chain is:
I create MyForm (myForm := TForm1.Create(nil))
Prior to showing the form I do some conditioning tests.
If all is ok, myForm.Show - this is working fine and I can also close myForm properly
If something is wrong:
a). I create a message form myMessageForm := TMyMessageForm.Create(nil) that contains a closing timer (the form closes after 10s). This form has action:=caFree in onClose event
b). I call myForm.Close. This form has also action:=caFree in onClose event - this form closes before myMessageForm closes (due to the timer present in myMessageForm)
Both forms are created with nil owner, but they are connected in some way (I don't know why). and the destruction of forms is not performed correctly. The next time myForm.Create(nil) or myMessageForm.Create(nil) is called, access violation occurs.
The myMessageForm should be created independently from myForm and it's destruction should not condition myForm destruction in any way.
unit1;
procedure doCreateForm;
var
myForm: TForm1;
begin
myForm := TForm1.Create(nil)
try
with myForm do
begin
if <test condition true> then Show
else
begin
ShowErrMessage('Error', 'Error message text', errType);
Close;
end;
end;
except
myForm.Free;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
unit2;
procedure ShowErrMessage(title, text: string; err: mtErrType);
var
myMessageForm: TMyMessageForm;
begin
myMessageForm := TMyMessageForm.Create(nil)
try
with myMessageForm do
begin
StepDownCounter := 10;
CloseTimer.Enable := True;
end;
except
myMessageForm.Free;
end;
end;
procedure TMyMessageForm.CloseTimerTimer(Sender: TObject);
begin
StepDownCounter := StepDownCounter - 1;
if (StepDownCounter < 1) then
begin
CloseTimer.Enabled := False;
LabelStepDownText.Visible := False;
Close;
end
else
begin
LabelStepDownText.Caption := 'Window will close in ' + IntToStr(StepDownCounter) + 's';
LabelStepDownText.Visible := True;
end;
end;
procedure TMyMessageForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
You are destroying the object that is executing the current method. All code in that object that is executed after the object is destroyed is invalid.
You should set the close action to caFree, or use Release, as has been explained. These work by posting a message to the queue to allow the object to be destroyed after the current method returns. You are subverting that with the call to ProcessMessages which pumps the queue. Remove the call to ProcessMessages.
I would like to get text width of a string before an application starts. Everything works fine until Application.MainForm canvas present. The problem is, when I try dynamically create TOrdinarium in the OnCreate event of the app. main form, "Canvas does not allow drawing" error occurs. (Application.MainForm is nil....). I tried several ways to create Canvas dynamically (one of them is written below), but it can not measure text sizes without being attached to parented control.
Is there way how to make it work somehow?
Thanx
I tried this:
TOrdinarium = class (TCustomControl)
private
function GetVirtualWidth:integer;
end;
constructor TOrdinarium.Create(AOwner:TComponent);
begin
inherited;
Width:=GetVirtualWidth;
end;
function TOrdinarium.GetVirtualWidth:integer;
var ACanvas : TControlCanvas;
begin
ACanvas := TControlCanvas.Create;
TControlCanvas(ACanvas).Control := Application.MainForm;
ACanvas.Font.Assign(Font);
result:=ACanvas.TextWidth('0');
ACanvas.Free;
end;
This works:
procedure TForm1.FormCreate(Sender: TObject);
var
c: TBitmap;
begin
c := TBitmap.Create;
try
c.Canvas.Font.Assign(self.Font);
Caption := IntToStr(c.Canvas.TextWidth('My String'));
finally
c.Free;
end;
end;
I'm not sure if this can be done, but if by "before the app starts" you mean "before the main form is displayed", you could always put your canvas-related code in the main form's OnCreate event. You'll have a valid canvas by that point.