Im trying to build in a language switch into one of my programs.
Where the user selects a language and in runtime the app gets translated. I sort of got this working in a small test project. BUT only when the forms are auto created, which i dont want.
the way the forms get created is the following:
SideNote: Most of my forms are fsMDIChild forms.
ParametersForm := TParametersForm.Create(Self); //(consider this the mainform for now)
On ParametersForm I have
procedure TParametersForm.FormCreate(Sender: TObject);
begin
ResourceStringsDM.ParametersF; //(consider this the second form)
end;
this Datamodule houses the captions for ParametersForm.
procedure TResourceStringsDM.ParametersF;
begin
with ParametersForm do
begin
bsSkinLabel1.Caption := 'Execute Nieuwefacturen';
bsSkinLabel2.Caption := 'Execute Viewfacturen';
end;
end;
I have added ResourceStringsDM to the implementation uses of ParametersForm and ParametersForm to the interface uses of ResourceStringsDM.
This all above gives me an access violation cause in the procedure where it sets the captions uses ParametersForm (var name of the form i want to translate) but at that moment this var is nil. Prolly cause it isnt done creating the form yet and hasnt filled in the form var.
The only way i got this all working was by using
Application.CreateForm(TParametersForm, ParametersForm);
But I want to avoid this after reading about it, and that you should only use this on your mainform. Also it doesnt handle passing parameters very well.
Does anyone of you fine people have any hints or tips / help to get me access to the captions of form 1 from form 2?
i probably forgot tons of info you guys need. just tell me and ill add it in.
In an "MDI Application" template (file->new-other->...>, neither MDI childs are auto created nor the child form's unit contains the global form reference. There's a reason for this, more than one instance of a child form should be able to run at the same time. When you have two instances of the same child for instance, which one will the form reference hold?
Anyway, it is of course possible to not to use this design, but then if you don't use the construct you mention in the question, you're responsible for assigning the instance to the reference yourself. So either do it (which I don't recommend):
procedure TParametersForm.FormCreate(Sender: TObject);
begin
ParametersForm := Self;
ResourceStringsDM.ParametersF; //(consider this the second form)
end;
or better, pass the instance to the function in your data module so that it can work on it:
procedure TResourceStringsDM.ParametersF(ParametersForm: TParametersForm);
begin
with ParametersForm do
begin
bsSkinLabel1.Caption := 'Execute Nieuwefacturen';
...
procedure TParametersForm.FormCreate(Sender: TObject);
begin
ResourceStringsDM.ParametersF(Self); //(consider this the second form)
end;
Related
I tried this in an Access newgroup, and got over a hundred views, but not a single response, so I hope it fares better here.
I'm trying to get a grip on custom events in VBA. I've scoured endless sources on the net, studied my manuals, experimented with everything I can think of, and success is still somewhat tenuous.
Exactly what I hope to accomplish is not all that complicated, and seems like it should be ideal for custom event routines. I have main forms with comboboxes and listboxes fed from tables or queries. The user can open various dialog boxes and do things to modify the tables. When that activity is done, I would like to requery the box(es) affected by any such activity. The way I have done it in the past was to set a global 'SourceHasChanged' Boolean variable and check its status upon returning from the dialog. It works, but is a bit unwieldy, so I decided to try replacing this with custom events.
Hours of studying and endless dead-end tries and repeats have finally produced the following bits of code. They do nothing spectacular. There is a table called T. The dialog form adds records on each click of the Add button. The main form has another button that deletes all but the first record in the table. Each set of code is supposed to fire an event indicating that the listbox is to be requeried. The code in the main form does okay, but the code in the dialog refuses to activate the StalaSeZmena event. Obviously (I think, anyway), that's because the dialog from creates a new instance of the class module. But I have to have a WithEvents variable in the dialog form. If I don't, I would have to make a reference to the WithEvents variable in the main form. Requiring forms to know that much about each other is exactly back-asswards from what I thought the custom event route was going to accomplish. It would be easier and less confusing to just stay with a global status variable.
Class Module [Zmena]
Public WithEvents Udalost As HlaseniZmeny
Private Sub Udalost_StalaSeZmena()
Form_Mane.lstS.Requery
Debug.Print "Requery via class module"
End Sub
Class Module [HlaseniZmeny]
Public Event StalaSeZmena()
Public Sub OhlasitZmenu()
RaiseEvent StalaSeZmena
End Sub
Regular Module
Public chg As Zmena
Form Code [Mane]
Private WithEvents chgMane As HlaseniZmeny
Private Sub cmdCallDialog_Click()
DoCmd.OpenForm "Dia", acNormal, , , , acDialog
End Sub
Private Sub cmdShrinkT_Click()
CurrentDb.Execute "Delete * From T Where Pole1 <> 'A'"
chgMane.OhlasitZmenu
End Sub
Private Sub Form_Open(Cancel As Integer)
Me.Label1.Caption = Me.SpinButton1.Value
Set chg = New Zmena
Set chgMane = New HlaseniZmeny
Set chg.Udalost = chgMane
End Sub
Private Sub chgMane_StalaSeZmena()
lstS.Requery
Debug.Print "Requery from event on main"
End Sub
Form Code [Dia]
Private WithEvents chgDia As HlaseniZmeny
Private Sub cmdAdd_Click()
CurrentDb.Execute "Insert Into T (Pole1) Values(chr(asc('" & DMax("Pole1", "T", "Pole1") & "')+1))"
Set chgDia = New HlaseniZmeny
Set chg.Udalost = chgDia
chgDia.OhlasitZmenu
Set chgDia = Nothing
End Sub
It's functional, but feels awkward, clunky and not at all intuitive, like scratching my left ear with my right foot. The class module has a reference to the main form, which violates the principle of encapsulation, but I found no way to make the dialog form activate the event routine in the main form. I have to have a global variable to link the two WithEvents variables to, or nothing works, but this also violates encapsulation.
Is this really how these constructs are supposed to operate, or have I accidentally stumbled onto a Mad-Max version that happens to work, but isn't the proper way to build such procedures?
How can I hide a MDIChild window in Delphi?
I'm using this code in FormClose() event of my MDI children, but it doesn't seem to work:
procedure TfrmInstrument.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caNone;
ShowWindow(Handle, SW_HIDE);
frmMainForm.MDIChildClosed(Handle);
end;
My child window is minimized instead of being hidden.
There is a protected procedure in TCustomForm defined as:
procedure TCustomForm.VisibleChanging;
begin
if (FormStyle = fsMDIChild) and Visible and (Parent = nil) then
raise EInvalidOperation.Create(SMDIChildNotVisible);
end;
Override it in your MDI child windows as:
procedure TMDIChildForm.VisibleChanging;
begin
// :-P
end;
Here's a simple example
After reading Jeroen's comment, I tried another solution which works as well, but with a little flickering:
procedure TMDIChildForm.VisibleChanging;
begin
if Visible then
FormStyle := fsNormal
else
FormStyle := fsMDIChild;
end;
Maybe this works on all Windows versions.
PS: I didn't find any problem with the first solution on Windows 2k3SP2 x86 and Windows 7 Ultimate x86
You cannot hide an MDI child window. This is a Win32 limitation.
The code below is simplified from something I'm doing in a design-time .BPL for D7.
Update: Since posting this, I've found one way to do what I'm after, namely just send the form a WM_Close message, but I'd still be interested to know whether there's a more "official" way to do it, because using WM_Close seems like it has the potential for wrong-footing the IDE.
All I'm trying to do in this code that's causing me the problem is to close all files open in the IDE,
and then open a specific .Pas file that happens to have an associated .Dfm file. I don't want the form defined in the .Dfm to be open on-screen, so I'm trying to close it, without closing the .Pas file too - I just want the IDE Form designer and this form out of the way.
Eventually, I found out how to get at the form via OTA + NTA services in my .BPL's code and, naively but for want of any other obvious way to do it, I've tried calling .Close on it, by this snippet.
AForm := TForm(INTAComp.GetComponent);
AForm.Close;
However, the form does not close. I've traced into TCustomForm.Close from the CPU window
and evidently the reason it doesn't close is that its Visible property is already False. This is what evaluating Visible prior to AForm.Close returns, too.
Evaluation various other of its properties prior to AForm.Close tells me that
- its Owner is Nil BUT
- it has an apparently valid window handle // Arrghh! [sound of penny dropping... see update above]
I dare say that this is something to do with how the IDE's form designer works.
My question is simply: What do I need to do in my code to get the form to close,
as it does when I simply click its [x] button on its frame?
Btw, I've confirmed that the instance of the form I'm getting via AForm := [...] is the
instance that's on-screen, by changing on-screen instance's caption in the OI.
procedure TOTAForm.CloseAForm;
var
IServices : IOTAServices;
IActionServices : IOTAActionServices;
IModuleServices : IOTAModuleServices;
IEditorServices : IOTAEditorServices60;
IModule : IOTAModule;
i : Integer;
IEditor : IOTAEditor;
ISourceEditor : IOTASourceEditor;
IFormEditor : IOTAFormEditor;
IComponent : IOTAComponent;
INTAComp : INTAComponent;
AForm : TForm;
begin
IServices := BorlandIDEServices as IOTAServices;
IServices.QueryInterface(IOTAACtionServices, IActionServices);
if IActionServices <> Nil then begin
IServices.QueryInterface(IOTAModuleServices, IModuleServices);
IModuleServices.CloseAll;
if IActionServices.OpenFile(EditorFileName) then begin
IModule := IModuleServices.Modules[0];
ISourceEditor := Nil;
for i := 0 to IModule.ModuleFileCount - 1 do begin
IEditor := IModule.ModuleFileEditors[i];
IEditor.QueryInterface(IOTAFormEditor, IFormEditor);
if IFormEditor <> Nil then begin
IComponent := IFormEditor.GetRootComponent;
IComponent.QueryInterface(INTAComponent, INTAComp);
AForm := TForm(INTAComp.GetComponent);
AForm.Close;
end;
end;
end;
end;
end;
All it needed was:
AForm := TForm(INTAComp.GetComponent);
SendMessage(AForm.Handle, WM_Close, 0, 0);
//AForm.Close;
But I'd still be interested in knowing whether there is an official way to do this, because
my "solution" feels like it's doing an end-run around OTA and NTA services. Otoh, the user can always close the form manually, on-screen, so maybe I'm worrying about nothing
in my Delphi program i've a Login Form and it's Displayed Before the Main Form is Created , but the issue that i'm facing is that i want to Login Check to be processed in the main form , that means the Login Form will use the Main Form to check and proceed ,
please read the comment placed in :
procedure LogInButtonClick(Sender: TObject) ;
here is the TLoginForm code ( from delphi.about.com ):
unit login;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TLoginForm = class(TForm)
LogInButton: TButton;
pwdLabel: TLabel;
passwordEdit: TEdit;
procedure LogInButtonClick(Sender: TObject) ;
public
class function Execute : boolean;
end;
implementation
{$R *.dfm}
class function TLoginForm.Execute: boolean;
begin
with TLoginForm.Create(nil) do
try
Result := ShowModal = mrOk;
finally
Free;
end;
end;
procedure TLoginForm.LogInButtonClick(Sender: TObject) ;
begin
if passwordEdit.Text = 'delphi' then
{
Here how it's possible to use :
if MainForm.text=passwordEdit.Text then
ModalResult := mrOK
}
ModalResult := mrOK
else
ModalResult := mrAbort;
end;
end.
and here's the Main Program Initialization flow :
program PasswordApp;
uses
Forms,
main in 'main.pas' {MainForm},
login in 'login.pas' {LoginForm};
{$R *.res}
begin
if TLoginForm.Execute then
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm) ;
Application.Run;
end
else
begin
Application.MessageBox('You are not authorized to use the application. The password is "delphi".', 'Password Protected Delphi application') ;
end;
end.
thank you
If you need the main form to be created first, then create it first:
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);//created, but not shown
if TLoginForm.Execute then//now the login form can refer to the main form
Application.Run//this shows the main form
else
Application.MessageBox('....');
end;
That's a direct and naive answer to the question that you asked. Thinking more broadly, I would encourage you to move the login testing out of the main form. Put it somewhere that can be used by whatever higher-level code needs to. The design you are currently working towards has unhealthy coupling.
I usually do this from the OnCreate of the MainForm; Or from the OnCreate of the DataModule, if you have one. For example:
TMainForm.OnCreate(Sender: TObject);
var F: TLoginForm;
begin
F := TLoginForm.Create(Self);
try
F.ShowModal;
finally F.Free;
end;
end;
I don't like messing with the DPR file too much. This works, shows the forms in the correct order, and if the TMainForm was auto-created by Delphi then the MainForm variable is already assigned and ready to use when the OnCreate fires;
PS: Accesing the MainForm variable is actually bad design, but it's there if you want it.
Similar to David's answer, but with slightly different behaviour, I previously answered this solution which is capable of reuse in application's lifetime.
I need to create a complex form with my own components (kinda OneClick installer), and use it as the replacement of the standard InnoSetup wizard. Is it possible?
My form is placed into DLL, and this DLL will be available for InnoSetup process.
This is how I tried to do that:
Delphi DLL code
library OneClickWizard;
uses
SysUtils,
Classes,
Wizard in 'Wizard.pas' {FormWizard};
{$R *.res}
exports
CreateWizardForm,
DestroyWizardForm;
begin
end.
Delphi form
unit Wizard;
interface
type
TFormWizard = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormWizard: TFormWizard;
procedure CreateWizardForm(AppHandle: THandle); stdcall;
procedure DestroyWizardForm; stdcall;
implementation
{$R *.dfm}
procedure CreateWizardForm(AppHandle: THandle);
begin
Application.Handle := AppHandle;
FormWizard := TFormWizard.Create(Application);
FormWizard.Show;
FormWizard.Refresh;
end;
procedure DestroyWizardForm;
begin
FormWizard.Free;
end;
InnoSetup script (iss)
[Setup]
;Disable all of the default wizard pages
DisableDirPage=yes
DisableProgramGroupPage=yes
DisableReadyMemo=true
DisableReadyPage=true
DisableStartupPrompt=true
DisableWelcomePage=true
DisableFinishedPage=true
[Files]
Source:"OneClickWizard.dll"; Flags: dontcopy
[Code]
procedure CreateWizardForm(AppHandle: Cardinal);
external 'CreateWizardForm#files:OneClickWizard.dll stdcall';
procedure DestroyWizardForm;
external 'DestroyWizardForm#files:OneClickWizard.dll stdcall';
procedure InitializeWizard();
begin
CreateWizardForm(MainForm.Handle);
end;
The form appearing on the screen, but it doesn't react on my input. Seems it is out of main message cycle. How to do this correctly?
In my setup I do something similar.
InnoSetup code I pass the handle as StrToInt(ExpandConstant('{wizardhwnd}')) (my guess is that MainForm.Handle is zero)
in the DLL:
OldAppHandle := Application.Handle;
try
Application.Handle := hAppHandle; // hAppHandle the handle from InnoSetup
F := TfmZForm.Create(Application);
try
F.Caption := lpTitle;
F.ShowModal;
Result := F.ErrorCode;
finally
F.Free;
end;
finally
Application.Handle := OldAppHandle;
end;
I know precisely nothing about InnoSetup but surely you need to use ShowModal rather than Show here. Installation UI is invariably modal and what you want here is to wait until the user has finished iteracting with the form before you return to Inno. Otherwise, how would Inno know when to proceed? ShowModal runs a message loop to service the form so there will be no problems receiving input.
You would also change your DLL to remove DestroyWizardForm since the function that calls ShowModal can both create and destroy the form.
If you want to entirely replace the UI, it will probably be easier to create a stub application that presents the form then runs the normal setup in silent mode passing various command line parameters.
Either that or at the very least, using Inno's native form and wizard page functions/logic.