Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 318
dummzeuch Avatar asked Feb 24 '23 18:02

dummzeuch


2 Answers

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.

like image 184
David Heffernan Avatar answered Mar 04 '23 22:03

David Heffernan


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.

like image 28
Sertac Akyuz Avatar answered Mar 05 '23 00:03

Sertac Akyuz