2011-03-21 6 views
3

TOpenDialog를 사용하여 사용자가 파일을 선택할 수 있도록하는 Delphi 응용 프로그램이 있습니다. 기본적으로 열려있는 대화 상자는 현재 모니터의 중앙에 표시되며 요즘에는 응용 프로그램의 창에서 멀어 질 수 있습니다. 대화 상자가 TOpenDialog의 소유자 컨트롤을 중심으로 표시되도록하고 싶습니다. 실패하면 응용 프로그램의 기본 창에 정착합니다. 그것은 나에게 그것을 수행하는 방법에 대한 몇 가지 힌트를 준 TJvOpenDialog에서 파생어떻게 TOpenDialog를 배치합니까?

작품의 다음 코드 종류 : 의미

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; 

"작품의 종류"대화 상자가 그것을 열 때 처음 그 소유자 양식의 중앙에 표시됩니다. 하지만 대화 상자를 닫고 창을 이동 한 다음 대화 상자를 다시 열면 SetWindowPos가 true를 반환하더라도 아무 효과가없는 것 같습니다. 대화 상자는 처음과 같은 위치에서 열립니다.

이것은 Windows XP에서 실행되는 Delphi 2007에서 대상 상자는 Windows XP에서도 실행됩니다.

+0

이 잘못된 솔루션 것 같은 느낌이 든다. 일반적인 대화를 통해 그런 식으로 파고 들지 않으려 고합니다. 필자는 Delphi의 최신 버전이 이러한 문제를 해결하기 위해 공통 대화 상자의 코드를 향상 시켰음을 알고 있습니다. 이 변경 사항이 나타난 델파이의 버전이 확실하지 않지만 문제가 될 수 있습니다. 시스템 공통 대화 상자가 올바르게 사용될 때 (그리고 VCL이 항상 그렇게하지 않은 경우) 현명한 위치에 나타나고 심지어 이전 세션의 크기와 위치를 기억합니다. –

+0

HWndOwner를 OpenDialog.Execute에 전달하고 있습니까? D2007 (심지어 이전에 추가 된 것 같습니다)에는이 문제를 해결하기 위해 부모 창 핸들을 허용하는 Execute 오버로드 된 버전이 있습니다. –

+0

이전 대화 상자의 코드를 살펴보면 기본 창 프로 시저에 메시지를 전달하기 전에 'WM_SHOWWINDOW'메시지에 대한 응답으로 배치를 완료 한 것을 볼 수 있습니다. –

답변

2

TJvOpenDialogTOpenDialog의 자손이므로 VCL이 대화 상자를 중심으로 배치 한 후에 배치 호출을 실행해야합니다. VCL은 CDN_INITDONE 알림에 대한 응답으로이 작업을 수행합니다. WM_SHOWWINDOW 메시지에 응답하는 것은 너무 일찍 수행되었으며 내 테스트에서 창 프로시 저는 WM_ENTERIDLE 메시지를 수신하지 않습니다.

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; 

또는

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; 

는 OS가 그것을두고 대화 상자를 가지고, 그것은 실제로 의미가 있습니다.

5

당신이 설명하는 동작은 OwnerHwnd의 가짜 값을 대화 상자의 Execute 메서드에 전달하여 만 재현 할 수 있습니다.

이 창 핸들은 기본 Windows 공용 컨트롤로 전달되며 실제로 대화 상자가 표시 될 때 활성 양식의 핸들로 설정하지 않으면 대화 상자에 다른 문제가 발생합니다.

예를 들어 Execute를 호출하고 Application.Handle을 전달하면 내 기본 양식의 위치와 관계없이 대화 상자가 항상 같은 창에 표시됩니다.

Execute를 호출하고 핸들을 기본 폼으로 전달하면 대화 상자가 기본 폼의 맨 위에 표시되고 오른쪽 및 아래쪽으로 약간 이동합니다. 이는 양식이 어떤 모니터에 있는지에 상관없이 사실입니다.

Delphi 2010을 사용하고 있으며 Delphi 버전에서 과부하 된 Execute 버전을 사용할 수 있는지 여부를 알지 못합니다. 사용 가능하지 않더라도 OwnerHwnd에 대해 더 합리적인 값을 전달하는 파생 클래스를 생성 할 수 있어야합니다.

본인의 문제라는 100 % 확실한 증거는 없지만이 관찰 결과는 만족스러운 해결책으로 이어질 것이라고 생각합니다.

+1

매개 변수없이 Execute를 사용하거나 현재 활성 양식 (이 경우 기본 양식이기도 함)의 창 핸들을 전달하는지 여부에 관계없이 별다른 차이가 없습니다. – dummzeuch

0

나는 성공없이 두 가지 예를 모두 시도했다 ...하지만 여기에 symple 솔루션은 다음과 같습니다

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; 
... 

이처럼 사용하여 다음 코드 (Form1의 예를 중심으로 저장 Dilaog에 대한) :

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 
... 
관련 문제