Getting shape to show up on a form during runtime [closed] - delphi

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I have a button and when clicked, i would like for a TMachine (aka TShape) to show up on the form. Currenty i get no errors, but it never shows up on the form.
Code for button click
procedure TfDeptLayout.bAddMachineClick(Sender: TObject);
var
machine: TMachine;
shapeAsset,
shapeShape,
shapeNumber,
shapeName: string;
begin
if not OkToAdd() then
begin
ShowMessage('Please fill out form correctly!');
Exit;
end;
ShapeAsset := Edit2.text;
ShapeShape := Combobox1.Text;
ShapeNumber := Edit3.Text;
ShapeName := Edit1.Text;
if sub = false then
begin
machine := TMachine.Create(self);
machine.Parent := Self;
machine.PlaceShape(0, FDB.GetWW(ShapeShape), FDB.GethW(ShapeShape),
'20', '20', ShapeName, ShapeNumber, ShapeAsset)
//show save button
//lockout add machine button
//let user place machine top / left.
//save all locations
//save top and left for each tmachine to database
//lockout save button
//show add machine button
end;
if sub then
ShowMessage('auto save form');
ShowMessage('congrats you added a machine');
end;
if needed i can show the TMachine unit?..
type
TMachine = class(TShape)
private
FOnMouseEnter: TNotifyEvent;
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
protected
procedure DoMouseEnter; virtual;
published
property OnMouseEnter: TNotifyEvent Read FOnMouseEnter write FOnMouseEnter;
public
mnName: string;
mnAsset: string;
mnNumber: string;
mnIsPanel: string;
mnBasicName: string;
mnLShape: string;
procedure PlaceShape(AM, sizeW, sizeH: Integer; ptop, pleft, name, order,
asset: string);
end;
implementation
uses
deptlayout;
procedure TMachine.CMMouseEnter(var Message: TMessage);
begin
DoMouseEnter;
inherited;
end;
procedure TMachine.DoMouseEnter;
begin
if Assigned(FOnMouseEnter) then
FOnMouseEnter(Self);
end;
procedure TMachine.PlaceShape(AM, sizeW, sizeH: Integer; ptop, pleft, name,
order, asset: string);
var
myLabel: TLabel;
begin
if ptop = '0' then
Top := 136
else
Top := StrToInt(ptop);
Width := sizeW;
Height := sizeH;
if pleft = '0' then
Left := MyDataModule.fDB.LastX + 2 //set left
else
Left := StrToInt(pleft);
MyDataModule.fDB.lastx := Left + sizeW;
if AM = 1 then //if in edit mode..
begin
//create label put inside the shape.
myLabel := TLabel.Create(FDeptLayout);
mylabel.Parent := FDeptLayout;
mylabel.Left := Left;
mylabel.Top := Top + 8;
mylabel.Caption := '#' + mnNumber;
end;
end;
end.

Of course it doesn't work!
The code that adds the machine is inside if not OkToAdd() then, so it will only run if not OkToAdd. BUT! Even if this is the case, you Exit before you run the code! Hence, the code will never run!
Probably you mean it to be like this:
if not OkToAdd then
begin
ShowMessage('Please fill out form correctly!');
Exit;
end; //END!!!!!!

To summarise my comments above:
Change the refer to fDeptLayout to Self, as you have done in your edit:
procedure TfDeptLayout.bAddMachineClick(Sender: TObject);
var
machine : TMachine;
shapeAsset,
shapeShape,
shapeNumber,
shapeName : string;
begin
if not OkToAdd() then
begin
showmessage('Please fill out form correctly!');
Exit;
End;
shapeAsset := edit2.text;
ShapeShape := Combobox1.Text;
ShapeNumber := Edit3.Text;
ShapeName := Edit1.Text;
if sub = false then
begin
machine := TMachine.Create(self);
machine.Parent := Self;
machine.PlaceShape(0,FDB.GetWW(ShapeShape),FDB.GethW(ShapeShape),'20','20',ShapeName,ShapeNumber,ShapeAsset)
//show save button
//lockout add machine button
//let user place machine top / left.
//save all locations
//save top and left for each tmachine to database
//lockout save button
//show add machine button
end;
if sub then
showmessage('auto save form');
showmessage('congrats you added a machine');
end;
To avoid confusion in future, delete the global form variables that the Delphi IDE creates for all but the main form, and any other autocreated forms - they are rarely if ever needed, and "pollute the namespace"

Unknown why this solved it, but after trying to find the parent for Machine by putting
showmessage('Machine Parent: '+Machine.parent.name);
it was giving access errors.
Deleted
Machine.parent := self;
Compile, build. Then reaadded
Machine.parent := self;
and everything worked.

Related

How to make the same button run different code everytime it is clicked?

I am currently doing a school project, I am making a Credit Card machine. I need the 'Enter Button' to
run different code when it is clicked. The first click must get the card number from an edit ps... (I clear the edit once the card number has been retrieved), and the second click must get the pin from the same edit.
How would I do this?
procedure TfrmMainMenu.btbtnEnterClick(Sender: TObject);
var
sCvv,sPin:string;
begin
iCount2:=0;
sCardNumber:=lbledtCardInfo.Text;
if (Length(sCardNumber)<>16) AND (iCount2=0) then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
end
else
begin
Inc(iCount2);
lbledtCardInfo.clear;
lbledtCardInfo.EditLabel.Caption:='Enter Pin' ;
btbtnEnter.Enabled:=false;
end; //if
if iCount2=2 then
begin
btbtnEnter.Enabled:=true;
sPin:=lbledtCardInfo.Text;
ShowMessage(sPin);//returns a blank
end;
You could try to do everything in a single event handler. There are several different ways to handle that. However, a different solution would be to use separate event handlers for each task, and then each task can assign a new handler for the next click to perform, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnEnter.OnClick := GetCCNumber;
end;
procedure TfrmMainMenu.GetCCNumber(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnEnter.OnClick := GetCCPin;
end;
procedure TfrmMainMenu.GetCCPin(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnEnter.OnClick := GetCCNumber;
end;
A variation of this would be to create multiple buttons that overlap each other in the UI, and then you can toggle their Visible property back and forth as needed, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCNumEnterClick(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnCCNumEnter.Visible := False;
btbtnCCPinEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCPinEnterClick(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
Notice that you test iCount2 = 0 immediately after setting iCount2 := 0. Thus, that test will always be True. Furthermore, the later test iCount2 = 2 will always be False because the value starts at 0 and you only have one Inc in between.
Instead try the following.
Add two string fields FCardNumber and FPin to your form class:
private
FCardNumber: string;
FPin: string;
Also create an enumerated type TEntryStage = (esCardNumber, esPin) and add a field of this type. This will make your code look like this:
private
type
TEntryStage = (esCardNumber, esPin);
var
FCardNumber: string;
FPin: string;
FEntryStage: TEntryStage;
In Delphi, class fields (class member variables) are always initialized, so FEntryStage will be esCardNumber (=TEntryStage(0)) when the form is newly created.
Add a TLabeledEdit (I see you use those) and a TButton; name them eInput and btnNext, respectively. Let the labeled edit's caption be Card number: and the caption of the button be Next.
Now add the following OnClick handler to the button:
procedure TForm1.btnNextClick(Sender: TObject);
begin
case FEntryStage of
esCardNumber:
begin
// Save card number
FCardNumber := eInput.Text;
// Prepare for the next stage
eInput.Clear;
eInput.EditLabel.Caption := 'Pin:';
FEntryStage := esPin;
end;
esPin:
begin
// Save pin
FPin := eInput.Text;
// Just do something with the data
ShowMessageFmt('Card number: %s'#13#10'Pin: %s', [FCardNumber, FPin]);
end;
end;
end;
You might notice that you cannot trigger the Next button using Enter, which is very annoying. To fix this, do
procedure TForm1.eInputEnter(Sender: TObject);
begin
btnNext.Default := True;
end;
procedure TForm1.eInputExit(Sender: TObject);
begin
btnNext.Default := False;
end;
Much better!

Transforming online listview object - Delphi

I wonder how caught a row of a listview and transform object.
I carry an .xml file and play in a listview , after loading this file you need to double-click in a row, take all of the data line and throw in a LabelEdit , as shown in the code below .
procedure TForm1.LstbxDadosDblClick(Sender: TObject);
begin
if Assigned(TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex])) then
begin
with TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex]) do
begin
EdtPara.Text := Para;
EdtDe.Text := De;
EdtCabecalho.Text := Cabecalho;
EdtCorpo.Text := Corpo;
end;
end;
end;
TMensagem = class
private
FCorpo: String;
FCabecalho: String;
FPara: String;
FDe: String;
public
property Para : String read FPara write FPara;
property De : String read FDe write FDe;
property Cabecalho: String read FCabecalho write FCabecalho;
property Corpo : String read FCorpo write FCorpo;
end;
Many ways to edit an object where the current object can change at any time (like with a double click). Here is one of the easiest: save when the current object changes and save at the very end. Here is a quick and dirty solution.
Add a member to the form or global in the implementation section
FLastMensagem: TMensagem;
May want to initialize to nil on create or initialization (left to you). Now in the event save data when the TMensagem object changes
procedure TForm1.LstbxDadosDblClick(Sender: TObject);
var
LNewMensagem: TMensagem;
begin
LNewMensagem := TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex]));
if Assigned(LNewMensagem) then
begin
// When we switch, capture the dialog before updating it
if Assigned(FMensagem) and (LNewMensagem <> FLastMensagem) then
begin
FLastMensagem.Para := EdtPara.Text;
FLastMensagem.De := EdtDe.Text;
FLastMensagem.Cabecalho := EdtCabecalho.Text;
FLastMensagem.Corpo := EdtCorpo.Text;
end;
EdtPara.Text := LNewMensagem.Para;
EdtDe.Text := LNewMensagem.De;
EdtCabecalho.Text := LNewMensagem.Cabecalho;
EdtCorpo.Text := LNewMensagem.Corpo;
//Set the last dblclicked
FLastMensagem := LNewMensagem
end;
end;
Of course the very last edit needs to be saved, that you can do in say a form close (not sure what your full design is). For example
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(FLastMensagem) then
begin
FLastMensagem.Para := EdtPara.Text;
FLastMensagem.De := EdtDe.Text;
FLastMensagem.Cabecalho := EdtCabecalho.Text;
FLastMensagem.Corpo := EdtCorpo.Text;
end;
end;

Delphi 7 - 2nd form can follow the position of the mainform

How can I make a second form can follow the position of the main form wherever the main form shifted. for example, can be seen in this GIF image:
I tried using this delphiDabbler tip, which is to stop a form moving, but did not manage to get something that worked.
In the main form you need this:
type
TMainForm = class(TForm)
protected
procedure WMWindowPosChanged(var Msg: TWMWindowPosChanged);
message WM_WINDOWPOSCHANGED;
end;
....
procedure TMainForm.WMWindowPosChanged(var Msg: TWMWindowPosChanged);
begin
inherited;
if Assigned(OtherForm) and not Application.Terminated then
begin
OtherForm.Left := Left + Width;
OtherForm.Top := Top;
end;
end;
This ensures that whenever the main form's position changes, the other form clamps to it. Note that this message can be sent before the other form is created, and after it is no longer valid. Hence the if statement.
And on the other form do this:
type
TOtherForm = class(TForm)
protected
procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;
end;
....
procedure TOtherForm.WMWindowPosChanging(var Msg: TWMWindowPosChanging);
begin
inherited;
if not Application.Terminated then
begin
Msg.WindowPos.x := MainForm.Left + MainForm.Width;
Msg.WindowPos.y := MainForm.Top;
end;
end;
This ensures that any attempts to move the other form are rejected.
Handle WM_WINDOWPOSCHANGING to move your other form(s) at the same time.
...
public
OldTop, OldLeft: Integer;
procedure WindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;
end;
...
implementation
...
procedure TForm1.WindowPosChanging(var Msg: TWMWindowPosChanging);
var
DTop, DLeft: Integer;
begin
// well and here inside of you put the relationship of like you
// want him to move.
// an example of this moving them in the same sense can be...
if (Form2 = nil) or (not Form2.Visible) then Exit;
// this line is to avoid the error of calling them when the forms
// are creating or when they are not visible...
DTop := Top - OldTop;
DLeft := Left - OldLeft;
Form2.Top := Form2.Top + DTop;
Form2.Left := Form2.Left + DLeft;
OldTop := Top;
OldLeft := Left;
inherited;
end;
Source:
http://delphi.cjcsoft.net/viewthread.php?tid=43047
(original code updated according to suggestions in comments)
Or something like this
Two forms to snap each other

How do I position a TOpenDialog

I have got a Delphi application which uses TOpenDialog to let the user select a file. By default, the open dialog is displayed centered on the current monitor which nowadays can be "miles" away from the application's window. I would like the dialog to be displayed centered on the TOpenDialog's owner control, failing that, I'd settle for the application's main window.
The following code kind of works, it is derived from TJvOpenDialog which gave me some hint on how to do it:
type
TMyOpenDialog = class(TJvOpenDialog)
private
procedure SetPosition;
protected
procedure DoFolderChange; override;
procedure WndProc(var Msg: TMessage); override;
end;
procedure TMyOpenDialog.SetPosition;
begin
var
Monitor: TMonitor;
ParentControl: TWinControl;
Res: LongBool;
begin
if (Assigned(Owner)) and (Owner is TWinControl) then
ParentControl := (Owner as TWinControl)
else if Application.MainForm <> nil then
ParentControl := Application.MainForm
else begin
// this code was already in TJvOpenDialog
Monitor := Screen.Monitors[0];
Res := SetWindowPos(ParentWnd, 0,
Monitor.Left + ((Monitor.Width - Width) div 2),
Monitor.Top + ((Monitor.Height - Height) div 3),
Width, Height,
SWP_NOACTIVATE or SWP_NOZORDER);
exit; // =>
end;
// this is new
Res := SetWindowPos(GetParent(Handle), 0,
ParentControl.Left + ((ParentControl.Width - Width) div 2),
ParentControl.Top + ((ParentControl.Height - Height) div 3),
Width, Height,
SWP_NOACTIVATE or SWP_NOZORDER);
end;
procedure TMyOpenDialog.DoFolderChange
begin
inherited DoFolderChange; // call inherited first, it sets the dialog style etc.
SetPosition;
end;
procedure TMyOpenDialog.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_ENTERIDLE: begin
// This has never been called in my tests, but since TJVOpenDialog
// does it I figured there may be some fringe case which requires
// SetPosition being called from here.
inherited; // call inherited first, it sets the dialog style etc.
SetPosition;
exit;
end;
end;
inherited;
end;
"kind of works" meaning that the first time the dialog is opened, it is displayed centered on the owner form. But, if I then close the dialog, move the window and open the dialog again, SetWindowPos doesn't seem to have any effect even though it does return true. The dialog gets opened at the same position as the first time.
This is with Delphi 2007 running on Windows XP, the target box is also running Windows XP.
The behaviour you describe I can reproduce only by passing a bogus value for the OwnerHwnd to the dialog's Execute method.
This window handle is then passed on to the underlying Windows common control and in fact you will have other problems with your dialogs if you do not set it to the handle of the active form when the dialog is shown.
For example when I call Execute and pass Application.Handle, the dialog always appears on the same window, in a rather bizarre location, irrespective of where my main form is.
When I call Execute and pass the handle to my main form, the dialog appears on top of the main form, slightly shifted to the right and down. This is true no matter which monitor the form is on.
I am using Delphi 2010 and I don't know whether or not you have the overloaded version of Execute available on your version of Delphi. Even if you don't have that available, you should still be able to create a derived class that will pass a more sensible value for OwnerHwnd.
Although I don't have conclusive 100% evidence that this is your problem, I think that this observation will lead you to a satisfactory resolution.
TJvOpenDialog is a descendant of TOpenDialog, hence you should run your placement call after the VCL centers the dialog. The VCL does it in response to a CDN_INITDONE notification. Responding to a WM_SHOWWINDOW message is too early, and in my tests the window procedure never receives a WM_ENTERIDLE message.
uses
commdlg;
[...]
procedure TJvOpenDialog.DoFolderChange;
begin
inherited DoFolderChange;
// SetPosition; // shouldn't be needing this, only place the dialog once
end;
procedure TJvOpenDialog.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_NOTIFY: begin
if POFNotify(Msg.LParam)^.hdr.code = CDN_INITDONE then begin
inherited; // VCL centers the dialog here
SetPosition; // we don't like it ;)
Exit;
end;
end;
inherited;
end;
or,
procedure TJvOpenDialog.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_NOTIFY: if POFNotify(Msg.LParam)^.hdr.code = CDN_INITDONE then
Exit;
end;
inherited;
end;
to have the dialog where the OS puts it, it actually makes sense.
I tried both examples without success ... but here is a symple solution:
type
TPThread = class(TThread)
private
Title : string;
XPos,YPos : integer;
protected
procedure Execute; override;
end;
TODialogPos = class(Dialogs.TOpenDialog)
private
Pt : TPThread;
public
function Execute(X,Y : integer):boolean; reintroduce;
end;
TSDialogPos = class(Dialogs.TSaveDialog)
private
Pt : TPThread;
public
function Execute(X,Y : integer):boolean; reintroduce;
end;
implementation
procedure TPThread.Execute;
var ODhandle : THandle; dlgRect : TRect;
begin
ODhandle:= FindWindow(nil, PChar(Title));
while (ODhandle = 0) do ODhandle:= FindWindow(nil, PChar(Title));
if ODhandle <> 0 then begin
GetWindowRect(ODhandle, dlgRect);
with dlgRect do begin
XPos:=XPos-(Right-Left) div 2;
YPos:=YPos-(Bottom-Top) div 2;
MoveWindow(ODhandle, XPos, YPos,Right-Left,Bottom-Top,True);
SetWindowPos(ODhandle, HWND_TOP, XPos, YPos, 0, 0, SWP_NOSIZE);
end
end;
DoTerminate;
end;
function TODialogPos.Execute(X,Y : integer):boolean;
begin
Pt:= TPThread.Create(False);
Pt.XPos := X;
Pt.YPos := Y;
if Self.Title <> '' then
Pt.Title := Self.Title
else begin
Self.Title := 'Open';
Pt.Title := Self.Title;
end;
Result:= inherited Execute;
Pt.Free;
end;
function TSDialogPos.Execute(X,Y : integer):boolean;
begin
Pt:= TPThread.Create(False);
Pt.XPos := X;
Pt.YPos := Y;
if Self.Title <> '' then
Pt.Title := Self.Title
else begin
Self.Title := 'Save';
Pt.Title := Self.Title;
end;
Result:= inherited Execute;
Pt.Free;
end;
...
Use it like (for example center Save Dilaog in Form1) the following code:
type
TForm1 = class(TForm)
...
...
dlgSave:=TSDialogPos.Create(self);
dlgSave.Filter := 'Symbol File (*.asy)|*.asy';
dlgSave.Options:=[ofHideReadOnly,ofExtensionDifferent,ofPathMustExist,
ofCreatePrompt,ofNoTestFileCreate,ofNoNetworkButton,
ofOldStyleDialog,ofEnableIncludeNotify,ofEnableSizing];
...
with dlgSave do begin
Title :='Copy : [ *.asy ] with Attributes';
InitialDir:= DirectoryList.Directory;
FileName:='*.asy';
end;
...
with Form1 do
if dlgSave.Execute(Left+Width div 2, Top+Height div 2) then begin
// your code
end;
...
dlgSave.Free
...

How to switch the 'current' directory of a TOpenDialog in an OnTypeChange handler? (is it possible at all?)

Depending on the chosen filter, I'd like the OpenDialog to 'look' in different directries.
Something like:
procedure TForm1.FileOpen1OpenDialogTypeChange(Sender: TObject);
// This does not work as intended...
var
Dialog: TOpenDialog;
FilterIndex: Integer;
FilterExt: string;
Path: string;
begin { TForm1.actFileOpenOpenDialogTypeChange }
Dialog := Sender as TOpenDialog;
FilterIndex := Dialog.FilterIndex;
FilterExt := ExtFromFilter(Dialog.Filter, FilterIndex);
GetIniPathForExtension(FilterExt, Path);
if DirectoryExists(Path) and
(Path <> IncludeTrailingPathDelimiter(Dialog.InitialDir)) then
begin
// those two statements don't have the desired effect
// but illustrate what is meant to happen:
Dialog.FileName := Path + '*' + FilterExt;
Dialog.InitialDir := Path;
end;
end; { TForm1.actFileOpenOpenDialogTypeChange }
I can't find any way to let the dialog update itself to the new directory.
I've tried calling OpenDialog.Execute, but that starts another OpenDialog without closing the current one...
Some time ago I have looked after exactly that sort of thing, but couldn't find a solution either. Nowadays I'm glad not to implement it anyway for the following reason:
Imagine a user executes the open dialog. He knows where to find the required file and navigates to that folder. Now he can't see the file and realizes that the filter is set wrong. He changes the filter and naturally expects the folder to stay the same.
Try and make some observations: in most of the cases a user first selects the folder and after that the file type.
While the below is not exactly elegant, tested with 2K, XP, Vista and 7, it seems to work. The idea is to use the dialog's behavior that, when a valid directory is entered into the file name box, if 'Open' button is pressed, the dialog switches to that folder.
It does not work with 'Vista style' dialogs, I don't have any acquaintance with the Common Item Dialog. So the UseLatestCommonDialogs must be set to false before showing a dialog. Also note that the OnTypeChange event is not fired when the dialog is initially launched, one can set the FilterIndex and InitialDir before showing the dialog.
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure OpenDialog1TypeChange(Sender: TObject);
procedure OpenDialog1FolderChange(Sender: TObject);
private
FDlgCleanUp: Boolean;
FDlgFocusCtrl: HWnd;
FSaveDlgFName: array [0..255] of Char;
public
end;
[...]
uses
CommDlg, Dlgs;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
ShowMessage(OpenDialog1.FileName);
end;
type
TFileExt = (feText = 1, feRichText, feDocument);
const
FileExts: array [TFileExt] of string = ('txt', 'rtf', 'doc');
FileExtDesc: array [TFileExt] of string =
('text (*.txt)', 'rich text (*.rtf)', 'document (*.doc)');
procedure TForm1.FormCreate(Sender: TObject);
var
fe: TFileExt;
begin
OpenDialog1.Options := OpenDialog1.Options - [ofOldStyleDialog];
NewStyleControls := True;
UseLatestCommonDialogs := False;
OpenDialog1.Filter := '';
for fe := Low(FileExts) to High(FileExts) do
OpenDialog1.Filter := OpenDialog1.Filter +
FileExtDesc[fe] + '|*.' + FileExts[fe] + '|';
end;
function GetIniPathForExtension(const Ext: string): string;
begin
// Get corresponding path from an ini file....
Result := ExtractFilePath(Application.ExeName) + Ext;
end;
procedure TForm1.OpenDialog1TypeChange(Sender: TObject);
var
Dialog: TOpenDialog;
Dlg: HWnd;
Path: string;
begin
Dialog := Sender as TOpenDialog;
Dlg := GetParent(Dialog.Handle);
Path := GetIniPathForExtension(FileExts[TFileExt(Dialog.FilterIndex)]);
ForceDirectories(Path);
// remember what's in file name, have to put it back later
GetDlgItemText(Dlg, cmb13, #FSaveDlgFName, 256);
SendMessage(GetDlgItem(Dlg, cmb13), WM_SETREDRAW, 0, 0); // reduce flicker
FDlgFocusCtrl := GetFocus;
// set file name to new folder
SendMessage(Dlg, CDM_SETCONTROLTEXT, cmb13, Longint(PChar(Path)));
// weird OS: windows - the below is only necessary for XP. 2K, Vista and 7
// clicks fine without it, XP does not!
windows.SetFocus(GetDlgItem(Dlg, IDOK));
// do not cleanup here, with Vista and 7 folder change seems to happen
// asynchronously - it might occur later than setting the file name and that
// clears/reverts the edit box.
FDlgCleanUp := True;
// click 'Open' to change to folder
SendMessage(GetDlgItem(Dlg, IDOK), BM_CLICK, IDOK, 0);
end;
procedure TForm1.OpenDialog1FolderChange(Sender: TObject);
var
Dlg: HWnd;
begin
// set the file name and focus back
if FDlgCleanup then begin // do not intervene if we didn't cause the change
Dlg := GetParent((Sender as TOpenDialog).Handle);
SendMessage(GetDlgItem(Dlg, cmb13), WM_SETREDRAW, 1, 0);
SetDlgItemText(Dlg, cmb13, #FSaveDlgFName);
windows.SetFocus(FDlgFocusCtrl);
end;
FDlgCleanup := False;
end;
One possibility:
var
ShowAfterClose: boolean = false;
MemFilterIndex: integer;
procedure TForm1.Import1Click(Sender: TObject);
begin
//...
with OpenDialogImport do
repeat
if Execute then
begin
ReadImportedFile(FileName); //Do action
exit;
end else begin
if not ShowAfterClose then //Check ShowAfterClose
exit;
ShowAfterClose := false; //Set ShowAfterClose false
FilterIndex := MemFilterIndex; //Copy MemFilterIndex
end;
until false;
//...
end;
procedure TForm1.OpenDialogImportTypeChange(Sender: TObject);
begin
PostMessage(TOpenDialog(Sender).handle,
WM_KEYDOWN, VK_ESCAPE , 0); //Cancel dialog
TOpenDialog(Sender).InitialDir := 'C:\'; //Set new directory
MemFilterIndex := TOpenDialog(Sender).FilterIndex; //Remember filter index
ShowAfterClose := True; //ShowAfterClose = True
end;
I'll agree with everyone else to date... it's VERY BAD user interface design to change things without asking the user, and/or against the user's wishes.

Resources