Pokaznie DialogBox nad pełnoekranowym oknem Direct3D bez utraty focusa.

0

Cześc. Wiem, że może temat nie do końca powinien być Newbie, ale tutaj raczej większość zagląda. A rozwiązanie może być banalne, bo miałem skuteczny kod, ale nie umiem go odtworzyć, a zamotany usunąłem projekt. Chociaż nnie mam pewności jak zawsze to odtworzyć, bo tamten korzystał z globalnego Hooka na WH_CBT. Jednak nie mogę go jednak użyć w docelowym kodzie, bo aplikacja będzie pracowała w Sandboxie, który nie umożliwia takich rzeczy jak zakłądanie globalnym Hooków. Ok, do rzeczy.

Interesuje mnie rozwiązanie w dowolnym języku, ktore będzie można z Waszą pomocą przeportować do Delphi i WinAPI. Najlepiej jednak żeby było to w Delphi. Chodzi o to, że mamy dllkę d3d9.dll, która jest jakby wrapperem (nie wiem czy to fachowe określenie), po prosru eksportuje funkcje, z ktorych korzysta docelowy program - konkeretnie emulator Amigi. Poza tym dllka zawiera własny kod, ktory między innymi sybclassuje WndProc niektórych okien.

Chodzi o to aby pokzać w żadanym momencie DialogBox z zasobów, ale w taki sposób aby kiedy aplikacja jest na pełnym ekranie okno głowne emulatora (o klasie PCsuxRox), w którym na pełnym ekranie wszelkie komunikaty przejmuje inne okno - dziecko (o klasie AmigaPowah) - nie straciła focusa. Czyli mój DialogBox pokazał się nad oknem gry.

Kombinowałem na rózne sposoby. Oryginalny kod WinUAE w C++, którego nie za bardzo ogarniam - potrafi pokzać GUI ustawień (stała IDD_PANEL) zawsze bez straty focusa nawet kiedy pełnoekranowe okno gry jest na wierzchu. Ponieważ kiedy gra jest w okienku dialog można pokazać modalnie i wszystko jest ok. Problem jest tylko z pełnym ekranem.

Wywoływałem DialogBox na milion sposobów, w tym zmieniając mu style okna, ale to nie pomaga. Nawet pokazanie MessageBoxa z pierwszym parametrem jako ustalony na pewno prawidlowy uchwyt okna jako pierwszy parametr pokazuje go pierwszy raz bez straty focusa, później po zamknięciu i ponownym uruchomieniu emulatora już jest źle. A robie test po naciśnięciu jednego z nieużywanych F'ów.

Oryginalny kod, który pewnie pozwala na takie rzeczy jak pokazanie dialogu bez straty focusa na pełnym ekranie - jest w kodzie WinUAE w pliku ...\od-win32\win32gui_extra.cpp. Wrzuciłem go na: http://pastebin.com/gE52vksp - jednak nie potrafię przetłumaczyć użytych struktur DLGTEMPLATEEX i DLGTEMPLATEEX_END oraz użytych funkcji.

Aktualne źródło znajduje się na: http://www.winuae.net/files/source/winuaesrc2600.zip (może się wolno pobierać - niestety taki serwer). Proszę kogoś doświadczonego o poradzenie jak można pokazać dialogbox nad pełnoekranowym oknem bez straty przez nie focusa na rzecz pokazanego modalnie dialogu. Z góry dziękuję za wszelkie porady i ewentualny kod. Googlowałem na różne sposoby, ale znajdowałem bzdety nic mi nie dające i nie na temat.

0

Wywoływałem DialogBox na milion sposobów, w tym zmieniając mu style okna, ale to nie pomaga.
DialogBox jest modalny.
Spróbuj z niemodalnym oknem dialogowym, CreateDialog.

Pamiętaj że CreateDialog wymaga dodania IsDialogMessage do pętli komunikatów, mniej więcej tak:

while (GetMessage(&msg, NULL, 0, 0))
    if (!IsWindow(hwndDialog) || !IsDialogMessage(hwndDialog, &msg)) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
0

Niestety nie działa. Nawet kiedy zamiast 0 jako parametr po zaosbie podaje uchwyt okna gry, przełącza na te tworzone okno. Robię tak (StartThread to funkcja oparta o CreateThread - zapożyczona z modułu Simple Tcp, dzięki niej możemy sobie spokjnie przekazać obiekt jako Pointer i nie uzyskujemy dziwnych rezultatów, ale tutaj i tak to zbędne):

procedure Pft(Parameter : Pointer);
var
  Msg : TMsg;
  DlgH : HWND;
begin
  DlgH := CreateDialog(HInstance, MAKEINTRESOURCE(IDD_WAITDLG), 0, @WaitDlgProc);
  ShowWindow(DlgH, SW_SHOWNORMAL);
  while GetMessage(Msg, 0, 0, 0) do
  begin
    if (not IsWindow(DlgH)) and (not IsDialogMessage(DlgH, Msg)) then
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
end;

function AmigaWindowOwnProc(AHWnd : HWND; Msg : UINT; AWParam : WParam; ALParam : LParam) : LResult; stdcall;
var
  X : integer;
  CtlObj : TControlObj;
begin
  X := IndexOfObjInArr(COArr, AHWnd);
  CtlObj := TControlObj(COArr[X]);
  case Msg of
    WM_KEYDOWN :
      begin
        if AWParam = VK_F2 then
        begin
          StartThread(@Pft);
        end;
      end;
  end;
  Result := CallWindowProc(CtlObj.OldProc, AHWnd, Msg, AWParam, ALParam);
end;

Może style okna są ważne, mój dialog wygląda w pliku *.rc w taki sposób, jak poniżej. Czy coś powinienem zmienić?

IDD_WAITDLG DIALOGEX 0, 0, 150, 50
FONT 8, "MS Sans Serif", 0, 0
STYLE WS_POPUP | WS_VISIBLE | DS_CENTER | DS_FIXEDSYS | DS_3DLOOK
BEGIN
  CONTROL "Saving game - please wait...", -1, "Button", WS_CHILDWINDOW | WS_VISIBLE | BS_GROUPBOX, 6, 4, 138, 40
  CONTROL "", IDC_WAITPB, "msctls_progress32", WS_CHILDWINDOW | WS_VISIBLE, 15, 22, 118, 9
END

Ale jak wnioskuje z opisu na MSDN inna funkcja DialogBoxIndirect ponoć nie oddaje kontroli dopóki nie będzie wykonany w jej kodzie obsługi okna EndDialog. Niestety nie znalazłem działąjącego przykładu w Delphi na tę funkcję. A kobminując z opisem kursu dla WinAPI i C++ po polsku na stronie: http://cpp0x.pl/kursy/Kurs-WinAPI-C++/Podstawy/Okna-dialogowe-cz-7/190 - nie udało mi się tworzyć kontrolek na dialogu. A nawet sam pusty dialog (tworzenie kontrolek w komentarzu) i tak przełączał focusa, próbowałem nadać styl WS_CHILD zamiast WS_POPUP, ale to nic nie dało Jeżeli masz @Azarien doświadczenie z tą funkcją i byłbyś w stanie przetłumaczyć jakiś przykład z Google na Delphi to byłbym wdzięczny.

procedure PixelsToDialogUnits(var RC : TRect);
var
  Base : LongInt;
  BaseX, BaseY : integer;
begin
  Base := GetDialogBaseUnits;
  BaseX := LOWORD(Base);
  BaseY := HIWORD(Base);
  RC.left := MulDiv(RC.left, 4, BaseX);
  RC.right := MulDiv(RC.right, 4, BaseX);
  RC.top := MulDiv(RC.top, 8, BaseY);
  RC.bottom := MulDiv(RC.bottom, 8, BaseY);
end;

procedure Pft(Parameter : Pointer);
var
  hgbl : HGLOBAL;
  pdt : PDLGTEMPLATE;
  pdit : PDLGITEMTEMPLATE;
  pw : WORD;
  pws : PChar;
  nchar : integer;
  RcDlg : TRect;
  RCBtn : TRect;
begin
  hgbl := GlobalAlloc(GMEM_ZEROINIT, 1024);
  pdt := PDLGTEMPLATE(GlobalLock(hgbl));
  pdt.style := WS_BORDER or WS_SYSMENU or DS_MODALFRAME or WS_CAPTION;
  pdt.cdit := 0;
  RcDlg := Rect(10, 10, 200, 150);
  PixelsToDialogUnits(RcDlg);
  pdt.x := rcDlg.left;
  pdt.y := rcDlg.top;
  pdt.cx := rcDlg.right;
  pdt.cy := rcDlg.bottom;
  RcBtn := Rect(50, 100, 100, 25);
  {
    PixelsToDialogUnits(rcBtn);
    pdit.x := rcBtn.left;
    pdit.y := rcBtn.top;
    pdit.cx := rcBtn.right;
    pdit.cy := rcBtn.bottom;
    pdit.id := IDOK;
    pdit.style := WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON;
  }
  GlobalUnlock(hgbl);
  DialogBoxIndirect(HInstance, PDLGTEMPLATE(hgbl)^, AmigaWindowHandle, @DlgProc);
end;

function AmigaWindowOwnProc(AHWnd : HWND; Msg : UINT; AWParam : WParam; ALParam : LParam) : LResult; stdcall;
var
  X : integer;
  CtlObj : TControlObj;
begin
  X := IndexOfObjInArr(COArr, AHWnd);
  CtlObj := TControlObj(COArr[X]);
  case Msg of
    WM_KEYDOWN :
      begin
        if AWParam = VK_F2 then
        begin
          StartThread(@Pft);
        end;
      end;
  end;
  Result := CallWindowProc(CtlObj.OldProc, AHWnd, Msg, AWParam, ALParam);
end;

Dodam jeszcze, że taki kod jak poniżej, pokaże mi w tytule MessageBox i treści komunikatu zawsze taką samą nazwę klasy jak to okno. Ale żeby pokazało się na wierzchu muszę klinąc myszką na te okno po tym nawet jak jest one na pełnym ekranie i mam kontrolę nad klawiszami. Niestety jednak z oknem dialogowym to nie zadziała. Nie wiem czy próbować subclassować MessageBox i na wtedy pokazywać kontrolki. Trochę lamerskie. I pewnie też nie zadziała. A i nie wiem jaki komunikat wysłąć aby okno na pewno przejeło mysz. Standardowe WM_LBUTTONDOWN zakłócało pokazyanie się MessageBoxa na pierwszym planie bez straty Focusa :/

function AmigaWindowOwnProc(AHWnd : HWND; Msg : UINT; AWParam : WParam; ALParam : LParam) : LResult; stdcall;
var
  X : integer;
  CtlObj : TControlObj;
begin
  X := IndexOfObjInArr(COArr, AHWnd);
  CtlObj := TControlObj(COArr[X]);
  case Msg of
    WM_KEYDOWN :
      begin
        if AWParam = VK_F2 then
        begin
          MessageBox(AHWnd, PChar(GetControlClassName(AHWnd)), PChar(GetControlClassName(GetForegroundWindow)), MB_OK);
        end;
      end;
  end;
  Result := CallWindowProc(CtlObj.OldProc, AHWnd, Msg, AWParam, ALParam);
end;
0

spróbuj otworzyć okno przez CreateDialog i przełączyć focus na główne okno przez SetActiveWindow. (CreateDialog oddaje kontrolę od razu)

0

Niestety to nie pomaga. Jedyne rozwiązanie jakie działa, to wywoływanie nie poprzez wątek. Wtedy okno rodzic się zamraża i na przykład śłychać zapętlony dźwięk. Nie wiem czy da się inaczej. Kiedy robię to w wątku okno się albo przełącza albo nie pokazuje. Działający kod poniżej. Chcialbym jednak pokazać ten dialog i później móc zamknąc cały emulator w sposób nieinawzyjny (czyli nie używac na przykładExitProcess tylko wysyłać do okien WM_SYSCOMMAND i SC_CLOSE). A teraz okno, tak jak wspomniałem przestaje reagować na cokolwiek. Może jeszcze jest jakieś inne rozwiązanie tego?

procedure ShowWaitDlgTheadProc(Parameter : Pointer);
var
  Msg : TMsg;
  AHandle, DlgH : HWND;
begin
  AHandle := HWND(Parameter);
  DlgH := CreateDialog(HInstance, MAKEINTRESOURCE(IDD_WAITDLG), AHandle, @WaitDlgProc);
  SetActiveWindow(AmigaWindowHandle);
  ShowWindow(DlgH, SW_SHOW);
  while GetMessage(Msg, 0, 0, 0) do
  begin
    if (not IsWindow(DlgH)) and (not IsDialogMessage(DlgH, Msg)) then
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
end;

function AmigaWindowOwnProc(AHWnd : HWND; Msg : UINT; AWParam : WParam; ALParam : LParam) : LResult; stdcall;
var
  X : integer;
  CtlObj : TControlObj;
begin
  X := IndexOfObjInArr(COArr, AHWnd);
  CtlObj := TControlObj(COArr[X]);
  case Msg of
    WM_APP :
      begin
        PostMessage(AHwnd, WM_LBUTTONDOWN, 0, 0);
        ShowWaitDlgTheadProc(Pointer(AHWnd));
      end;
  end;
  Result := CallWindowProc(CtlObj.OldProc, AHWnd, Msg, AWParam, ALParam);
end;
0

Czy nie możesz zwyczajnie zamiast dialogu dać okienko z topmost?

0

@_13th_Dragon: próbowalem jak poniżej, wywołując samą procedurę Pft lub wywołując ją w wątku przez CreateThread (ponieważ zauważyłem, że StartThread wzięta z kodu Simple TCP źle tworzy to okno i otrzymuje się komunikat o nieznalezieniu klasy przez GetLastError. I niestety - nadal focus jest tracony "na rzecz" tworzonego okna. Może macie jeszcze jakiś pomysł na skuteczne rozwiązanie? Bardzo bym prosił. Kod poniżej. Dodam, że ta WndProc została przeklejona na szybko z innego projektu, gdzie testowałem jak tworzyć w dllkach okna główne przez CreateWindow/Ex.

const
  App_Title = 'Dialog from dll';

var
  AppClass : PChar;
  ThreadId : DWORD;

function WndProc(AHWnd : HWND; UMsg : UINT; AWParam : WPARAM; ALParam : LPARAM) : LRESULT; stdcall;
begin
  Result := 0;
  case uMsg of
    WM_LBUTTONdOWN :
      begin
        MessageBox(AHwnd, PChar(FormatC('You clicked on me :) My class name is: "%s"',
          GetControlClassName(AHWnd))), PChar(App_Title), MB_ICONINFORMATION + MB_OK);
      end;
    WM_CREATE :
      begin
      end;
    WM_QUIT :
      begin
        PostQuitMessage(0);
      end;
  else
    begin
      Result := DefWindowProc(AHWnd, UMsg, AWParam, ALParam);
    end;
  end;
end;

procedure Pft;
var
  Msg : TMsg;
  WC : TWndClass;
  WindowHandle : HWND;
begin
  AppClass := PChar(GenerateRandomString(RandomRange(4, 57)));
  WC.hinstance := hinstance;
  WC.lpfnwndproc := @WndProc;
  WC.hicon := loadicon(0, IDI_ASTERISK);
  WC.hcursor := loadcursor(0, IDC_ARROW);
  WC.hbrbackground := COLOR_WINDOW;
  WC.lpszclassname := AppClass;
  RegisterClass(WC);
  WindowHandle := CreateWindowEX(WS_EX_TOPMOST, AppClass, PChar(App_Title),
    WS_VISIBLE or WS_TILEDWINDOW, 100, 100, 400, 600, AmigaWindowHandle, 0, HInstance, nil);
  ShowWindow(WindowHandle, SW_SHOW);
  while GetMessage(Msg, 0, 0, 0) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end;
0

jak już napisałem wyżej: "przełączyć focus na główne okno przez SetActiveWindow". czy jest jakaś konkretna trudność z tym związana?

0

@Azarien: próbowałem tak i nic to nie dało, dalej traciłem focus.

@_13th_Dragon albo coś źle robię albo i tak nie działa. Próbowałem też przechwycić WM_KILLFOCUS. Jak rozumiem powinienem zwracać zero jeśli obsłuże komunikat. Możesz dać przykład jak powinienem zastosować te komunikaty w funkcji obsługi komunikatów okna WndProc jak powyżej? Z góry dziękuję. Chyba, przez te ostatnio kodzenie po nocach nie ogarniam niektorych oczywistości i się motam. Musicie mi wybaczyć :)

0

A gdyby zamiast ShowWindow użyć SetWindowPos i tam pokombinować z parametrami?

0

@kAzek: a jak proponujesz użyć SetWindowPos? Z drugim parametrem jako HWND_TOPMOST i ostatni SWP_NOMOVE or SWP_NOSIZE? Bo próbowałem tak jak poniżej, jak również korzystając z WS_EX_TOPMOST jako pierwszy parametr CreateWindowEX i okno się nie pokazuje w ogóle, ale i tak okno rodzic (AmigaWindowHandle) traci niestety tak samo focus "na rzecz" tego niewidocznego okna. Czyli nadal jest źle.

  WindowHandle := CreateWindowEX(0, AppClass, PChar(App_Title),
    WS_VISIBLE or WS_TILEDWINDOW, 100, 100, 400, 600, AmigaWindowHandle, 0, HInstance, nil);
  SetWindowPos(WindowHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
0

Tak myślałem o czymś takim

SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW or SWP_NOSIZE or
     SWP_NOMOVE or SWP_NOACTIVATE);

ewentualnie w połączeniu z :

SetWindowPos(hAmigaWnd, hDlg, 0, 0, 0, 0, SWP_SHOWWINDOW or SWP_NOSIZE or
     SWP_NOMOVE);

ale jak mówisz że dialog się nie pokaże to kiepska sprawa.

0

No niestety, taki kod jak poniżej znowu działa tak, żę przywraca okno głowne gry, ale wtedy dialog wykonuje się pod nim i znowu go nie widać niestety. Mozę macie jeszcze jakieś pomysły, a możę nadal coś robię nie tak? :/

procedure ShowWaitDlgTheadProc(Parameter : Pointer);
var
  Msg : TMsg;
  AHandle, DlgH : HWND;
begin
  AHandle := HWND(Parameter);
  DlgH := CreateDialog(HInstance, MAKEINTRESOURCE(IDD_WAITDLG), AHandle, @WaitDlgProc);
  SetWindowPos(AmigaWindowHandle, DlgH, 0, 0, 0, 0, SWP_SHOWWINDOW or SWP_NOSIZE or SWP_NOMOVE);
  ShowWindow(DlgH, SW_SHOW);
  while GetMessage(Msg, 0, 0, 0) do
  begin
    if (not IsWindow(DlgH)) and (not IsDialogMessage(DlgH, Msg)) then
    begin
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1