this->Hide() works only once - delphi

My environment: C++ Builder XE4
I am working on hiding/showing one application from other application, both built using XE4.
Two project
UnitShow: to show/hide UnitHide
UnitHide: to be shown/hidden from UnitShow
The code for UnitShow is as follows:
void __fastcall TFormShow::B_showClick(TObject *Sender)
{
HWND hwnd = GetDesktopWindow();
hwnd = FindWindowEx(hwnd, NULL, L"TFormHide", NULL);
ShowWindow(hwnd, SW_SHOWNORMAL);
}
The code for UnitHide is as follows:
void __fastcall TFormHide::B_hideClick(TObject *Sender)
{
this->Hide();
}
With above, I tried followings.
On UnitHide, click B_hide >> UnitHide is hidden
On UnitShow, click B_show >> UnitHide is shown
On UnitHide, click B_hide >> UnitHide is not hidden
I expected that the 2nd B_hide button push will hide UnitHide.
What is the cause of this problem?

I tried followings.
void __fastcall TFormHide::B_hideClick(TObject *Sender)
{
bool isShowing = this->Showing;
#if 1
this->Hide();
#else
ShowWindow(this->Handle, SW_HIDE);
#endif
}
When I use this->Hide(), after shown by UnitShow, the isShowing is false. With this, it seems this->Hide() does not work (after the 2nd time).
On the other hand, using ShowWindow(this->Handle, SW_HIDE); the isShowing is true always. With this, hiding after the 2nd time works.
For this->Hide(), it seems that I have to use this->Show() from other application, which currently I do not know how to do.

Use FindWindow function instead of FindWindowEx as following:
procedure TfShow.btnShowClick(Sender: TObject);
var
hWindow:Hwnd;
begin
hWindow := FindWindow('TFormHide', nil);
ShowWindow(hWindow, SW_SHOWNORMAL);
end;
Regarding the UnitHide you can call self.hide in your button
procedure TfHide.btnHideClick(Sender: TObject);
begin
self.Hide; {Hide the hidden form }
//or
ShowWindow(Self.Handle, SW_HIDE);
end;
You can do the same calls in the C++
Update
as Remy mentions in his answer
So you can use the Following:
Define custom messages in the both applications
const
Show_MESSAGE = WM_USER + 1;
Hide_MESSAGE = Show_MESSAGE + 1;
In the HideUnit Define a handler for the message
private
{ Private declarations }
procedure WMShow(var Message: TMessage); message Show_MESSAGE;
procedure WMHide(var Message: TMessage); message Hide_MESSAGE;
....
implementation
procedure TfHide.WMShow(var Message: TMessage);
begin
Self.Show;
end;
procedure TfHide.WMHide(var Message: TMessage);
begin
Self.Hide;
end;
Sending the message form the ShowUnit > Showbtn
procedure TfShow.btnShowClick(Sender: TObject);
var
hWndX : HWND;
begin
hWndX := FindWindow('TfHide',nil);
if hWndX <> 0 then begin
SendMessage(hWndX, Show_MESSAGE, 0, 0);
end
else MessageBox(0, 'Window not found', 'Msg', 0);
end;
Hiding the Form from hidden form
procedure TfHide.btnHideClick(Sender: TObject);
begin
SendMessage(Handle, Hide_MESSAGE, 0, 0);
end;

Related

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 to intercept (detect) a Paste command into a TMemo?

How to catch Paste command and change text of Clipboard before that text is pasted into a TMemo, but, after Paste, text in Clipboard must be same like before changing?
Example, Clipboard have text 'Simple Question', text that go in the TMemo is 'Симплe Qуeстиoн', and after that text in Clipboard is like before changing, 'Simple Question'.
Derive a new control that descends from 'TMemo' to intercept the WM_PASTE message:
type
TPastelessMemo = class(TMemo)
protected
procedure WMPaste(var Message: TWMPaste); message WM_PASTE;
end;
uses
clipbrd;
procedure TPastelessMemo.WMPaste(var Message: TWMPaste);
var
SaveClipboard: string;
begin
SaveClipboard := Clipboard.AsText;
Clipboard.AsText := 'Simple Question';
inherited;
Clipboard.AsText := SaveClipboard;
end;
If you would like to prohibit any paste operation at all, empty the WMPaste handler.
This is an alternative to Sertac's excellent answer, which is to override the control's WndProc:
// For detecting WM_PASTE messages on the control
OriginalMemoWindowProc: TWndMethod;
procedure NewMemoWindowProc(var Message: TMessage);
//...
// In the form's OnCreate procedure:
// Hijack the control's WindowProc in order to detect WM_PASTE messages
OriginalMemoWindowProc := myMemo.WindowProc;
myMemo.WindowProc := NewMemoWindowProc;
//...
procedure TfrmMyForm.NewMemoWindowProc(var Message: TMessage);
var
bProcessMessage: Boolean;
begin
bProcessMessage := True;
if (Message.Msg = WM_PASTE) then
begin
// Data pasted into the memo!
if (SomeCondition) then
bProcessMessage := False; // Do not process this message any further!
end;
if (bProcessMessage) then
begin
// Ensure all (valid) messages are handled!
OriginalMemoWindowProc(Message);
end;
end;

Slow eventhandler in TPageControl.OnChange causes weird behaviour

When I add slow code to the OnChange event of TPageControl I run into problems.
If the code is fast and doesn't take a lot of time, things are fine.
However if the code takes a long time to return +/- 0.5 to 1 second, the PageControl starts to act weird.
If the user changes a page sometimes it doesn't do anything on the first click, and a second click on the page is required to actually make the change happen.
I've kind of sort of fixed this with code like this.
(I've simplified it a bit, just to show the idea)
type TDelayProc = procedure(Sender: TObject) of object;
TForm = class(TForm)
...
private
FDelayedSender: TObject;
FDelayedEvent: TDelayProc;
procedure SetDelayedEvent(Value: TDelayProc);
property FDelayedSender: TObject read FDelayedSender write FDelayedSender;
property FDelayedEvent: TDelayProc read FDelayedEvent write SetDelayedEvent;
...
procedure TForm1.SetDelayedEvent(Value: TDelayProc);
begin
Timer1.Active:= false;
FDelayedEvent:= Value;
if Assigned(Value) then Timer1.Active:= true
else DelayedSender:= nil;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Active:= false;
if Assigned(DelayedEvent) then DelayedEvent(DelayedSender);
end;
procedure TForm1.PageControl1Change(Sender: TObject);
begin
if PageControl1.ActivePage = TSPage1 then begin
DelayedSender:= Button1;
DelayedEvent:= Button1Click;
end; {if}
end;
As you can see this is a horrible hack.
The code I'm calling is in QuickReport to prepare a report and MySQL query and such, so I don't have much control over that.
I'm think there's some Win32 messaging that I'm messing up by not returning from TPageControl.OnChange fast enough, the delay is definitely shorter than 3 seconds though.
I've tried ProcessMessages, but that just made things worse and I don't want to use a separate thread for this.
How do I fix this so I can use the OnChange event handler like normal
I'm unclear about why you're using the TTimer stuff. If it were me, I think I'd just PostMessage a custom message to my form from the OnChange event, so the OnChange handler would return immediately. That would allow the PageControl message flow to behave normally. Then in the Message handler for that custom message I would (1) show/start a progress bar form running on a 2nd thread, (2) start the activity which is taking so much time, and (3) when the time consuming activity finishes, shut down the progress bar.
Here's some code for a threaded progress bar, that I modified from something Peter Below posted years ago. It's NOT pretty, but users don't care about that as much as they care about "nothing happening" on the screen.
unit AniMg;
{ Unit for displaying animated progress bar during a lengthy process.
* Painting of progress is done in a secondary thread, so it updates even during processing
which doesn't process Windows messages (and therefore doesn't update visible windows).
* Does NOT call Application.ProcessMessages...so it doesn't alter the order in which the
application processed messages.
USAGE:
//Delays display of the progress form. When this property <> 0, caller must pepper
//his code with .UpdateVisible calls, or the form will never be displayed.
AniMgr.DelayBeforeVisible := 3000;
//If DelayBeforeVisible time has elapsed, displays the progress form & starts thread.
AniMgr.UpdateVisible;
//Displays the progress form & starts painting it in a secondary thread.
//(If DelayBeforeVisible <> 0, sets the form's caption or caption-to-be.)
AniMgr.Push('Some caption');
//To change captions without closing/opening the progress bar form...
AniMgr.Push('Another caption');
//Close the form
AniMgr.PopAll;
NOTES:
* Do NOT call DisableTaskWindows in this unit!! It's tempting to do that when the progress
form is shown, to make it function modally. However, do so at your own risk! Having
DisableTaskWindows in effect resulted in an AV when we were called from certain routines
or component's code.
AUTHOR:
* Mark Wilsdorf, Flagship Technologies, Inc., www.goflagship.com.
* Thanks to Peter Below for his original code for painting the progress bar, and his many
years of providing stellar examples and explanations to the Delphi community.
DEVELOPMENT:
* Originally put FAniform.Show/Update on a TTimer delay, so the progress form wouldn't
display just for a brief instant during quick processes. However, we had to get rid of
Application.ProcessMessages calls (which caused problems in caller), so the TTimer wouldn't
fire. Can't make the 2ndary thread do the Show/Update job either, for the same reason:
Synchronize() won't work because the main thread is occupied in other code, and without
Application.ProcessMessages calls the Synchronize(Show/Update code) doesn't get called
until the lengthy main thread code processing finishes. The only solution appears to be:
have the 2ndary thread be fully responsible for creating and showing/updating the entire
progress window, entirely via Windows API calls.
}
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, RzLabel, ExtCtrls, RzPanel;
{$I DEFINES.PAS}
type
T_AniForm = class(TForm)
RzPanel2: TRzPanel;
RzLabel1: TRzLabel;
RzPanel1: TRzPanel;
public
r : TRect;
constructor Create(AOwner: TComponent); override;
end;
//Do NOT call DisableTaskWindows in this unit!!
//We may be called from rtnes or components which attempt to update the UI, resulting
//in an AV in certain circumstances. This was the result when used with the popular
//Developer's Express component, ExpressQuantumGrid.
TAniThread = class(TThread)
private
FWnd: HWND;
FPaintRect: TRect;
FbkColor, FfgColor: TColor;
FInterval: integer;
protected
procedure Execute; override;
public
constructor Create(paintsurface : TWinControl; {Control to paint on }
paintrect : TRect; { area for animation bar }
bkColor, barcolor : TColor; { colors to use }
interval : integer); { wait in msecs between paints}
end;
TAniMgr = class(TObject)
private
FStartTime: DWord; //=Cardinal. Same as GetTickCount
FDelayBeforeVisible: cardinal;
FRefCount: integer;
FAniThread : TAniThread;
FAniForm: T_AniForm;
// procedure SetDelayBeforeVisible(Value: cardinal);
procedure StopIt;
public
procedure Push(const NewCaption: string);
procedure UpdateVisible;
//procedure Pop; Don't need a Pop menthod until we Push/Pop captions...
procedure PopAll;
//
//Delay before form shows. Takes effect w/r/t to first Push() call.
property DelayBeforeVisible: cardinal read FDelayBeforeVisible write FDelayBeforeVisible;
end;
function AniMgr: TAniMgr; //function access
implementation
{$R *.dfm}
var
_AniMgr : TAniMgr = nil; //Created privately in Initialization section
//Do NOT DisableTaskWindows in this unit!!
//We're called from some rtnes which attempt to update the UI, resulting in an AV.
//DisabledWindows: pointer = nil;
function AniMgr: TAniMgr;
begin
if not Assigned(_AniMgr) then
_AniMgr := TAniMgr.Create;
Result := _AniMgr;
end;
//---------------------------------------------------------------------------------------------
// TAniMgr
//---------------------------------------------------------------------------------------------
procedure TAniMgr.UpdateVisible;
{ Checks our form's visibility & calls form.Update if appropriate.
* This rtne implements DelayBeforeVisible handling. }
begin
//Thd may be terminating...
if Assigned( FAniThread ) and FAniThread.Terminated then
exit;
if Assigned(FAniForm) and
( (DelayBeforeVisible = 0) or (GetTickCount - FStartTime > DelayBeforeVisible) ) then begin
if not Assigned(FAniThread) then
with FAniForm do begin
Show;
//Form.Update processes our paint msgs to paint the form. Do NOT call
//Application.ProcessMessages here!! It may disrupt caller's intended message flow.
Update;
//Start painting progress bar on the form
FAniThread := TAniThread.Create(RzPanel1, r, FAniForm.color, clActiveCaption, 100);
end
else
FAniForm.Update;
end;
end;
procedure TAniMgr.Push(const NewCaption: string);
{ We don't really Push a stack of captions (though we could)...for now that's not
important; we just manage the form and RefCount. }
begin
//Thd may be terminating...
if Assigned( FAniThread ) and FAniThread.Terminated then
exit;
FRefCount := FRefCount + 1;
if FAniForm = nil then begin
FAniForm := T_AniForm.Create(nil);
//If FAniForm was nil this is the first Push() of a series, so get
//a starting tick count for DelayBeforeShowing management
FStartTime := GetTickCount;
end;
FAniForm.RzLabel1.Caption := NewCaption;
UpdateVisible;
end;
procedure TAniMgr.StopIt;
begin
if Assigned( FAniThread ) then begin
if not FAniThread.Terminated then begin
FAniThread.Terminate;
FAniThread.WaitFor;
end;
end;
FreeAndNil(FAniThread);
FreeAndNil(FAniForm);
end;
//procedure TAniMgr.Pop;
//{ We don't really Pop a stack of captions...for now that's not important; we just
// decrement the RefCount. }
//begin
// if FRefCount > 0 then
// FRefCount := FRefCount - 1;
// if (FRefCount = 0) then
// StopIt;
//end;
procedure TAniMgr.PopAll;
begin
if FRefCount > 0 then try
StopIt;
finally
FRefCount := 0;
end;
end;
//---------------------------------------------------------------------------------------------
// T_AniForm
//---------------------------------------------------------------------------------------------
constructor T_AniForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
r := RzPanel1.ClientRect;
InflateRect(r, - RzPanel1.BevelWidth, - RzPanel1.BevelWidth);
end;
//---------------------------------------------------------------------------------------------
// TAniThread
//---------------------------------------------------------------------------------------------
constructor TAniThread.Create(paintsurface : TWinControl;
paintrect : TRect; bkColor, barcolor : TColor; interval : integer); //BeforePaint: integer);
begin
inherited Create(True); //Suspended
FWnd := paintsurface.Handle;
FPaintRect := paintrect;
FbkColor := bkColor;
FfgColor := barColor;
FInterval := interval;
FreeOnterminate := False; //So we can use WaitFor & know it's dead.
Resume;
end;
procedure TAniThread.Execute;
var
image : TBitmap;
DC : HDC;
left, right : integer;
increment : integer;
imagerect : TRect;
state : (incRight, incLeft, decLeft, decRight);
begin
Image := TBitmap.Create;
try
with Image do begin
Width := FPaintRect.Right - FPaintRect.Left;
Height := FPaintRect.Bottom - FPaintRect.Top;
imagerect := Rect(0, 0, Width, Height);
end; { with }
left := 0;
right := 0;
increment := imagerect.right div 50;
//WAS... increment := imagerect.right div 50;
state := Low(State);
while not Terminated do begin
with Image.Canvas do begin
Brush.Color := FbkColor;
FillRect(imagerect);
case state of
incRight: begin
Inc(right, increment);
if right > imagerect.right then
begin
right := imagerect.right;
Inc(state);
end; { if }
end; { case incRight }
incLeft: begin
Inc(left, increment);
if left >= right then
begin
left := right;
Inc(state);
end; { if }
end; { case incLeft }
decLeft: begin
Dec(left, increment);
if left <= 0 then
begin
left := 0;
Inc(state);
end; { if }
end; { case decLeft }
decRight: begin
Dec(right, increment);
if right <= 0 then
begin
right := 0;
state := incRight;
end; { if }
end; { case decLeft }
end; { case }
Brush.Color := FfgColor;
FillRect(Rect(left, imagerect.top, right, imagerect.bottom));
end; { with }
DC := GetDC(FWnd);
if DC <> 0 then try
BitBlt(DC,
FPaintRect.Left,
FPaintRect.Top,
imagerect.right,
imagerect.bottom,
Image.Canvas.handle,
0, 0,
SRCCOPY);
finally
ReleaseDC(FWnd, DC);
end;
Sleep(FInterval);
end; { while not Terminated}
finally
Image.Free;
end;
InvalidateRect(FWnd, nil, True);
end;
initialization
finalization
if Assigned(_AniMgr) then begin
_AniMgr.PopAll;
_AniMgr.Free;
end;
end.
The only explanation I have is that your long running handler is pumping the message queue. So long as you don't pump the queue you can take as long as you like handling an event. It might look messy since you are neglecting the queue but it will work normally.
I wish there was a BeforeChange event
that gave me the new page as a
parameter [...]
There almost is. Use the OnChanging event and the IndexOfTabAt function:
// Warning: Don't use, see below!
procedure TForm1.PageControl1Changing(Sender: TObject;
var AllowChange: Boolean);
var
pnt: TPoint;
NewTabIndex: integer;
begin
if not GetCursorPos(pnt) then Exit;
pnt := PageControl1.ScreenToClient(pnt);
NewTabIndex := PageControl1.IndexOfTabAt(pnt.X, pnt.Y);
if NewTabIndex <> -1 then
ShowMessageFmt('Next up: tab with index %d.', [NewTabIndex]);
end;
But: This only works if the user clicks a tab. It does not work if the user navigates the tab control using the keyboard. Therefore, this answer is useless (other than for educational purposes).

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