| Windows 95 usw. (klassisch) Erklärung der Zustände | Windows XP mit Luna-Stil | („Teletubbie-Optik“) ![]() Normalzustand: Fenster hat den Eingabefokus. Vier statt drei Knöpfe |
![]() Fenster hat keinen Eingabefokus. | Verschmerzbare Fehldarstellung bei XP-Stil (Knopf nicht grau), das hat wohl irgendetwas mit den unten erwähnten undokumentierten Nachrichten zu tun
![]() Schaltfläche gedrückt – erst beim Loslassen auf derselben Schaltfläche geht's los |
![]() Hilfesuche in Aktion – jetzt nur noch auf das Dialogelement klicken | Früher wurde das mittels Umschalt+F1 gemacht
Rechts: Leichte Hervorhebung des Knopfes beim Mauszeiger-Drüberhalten („hot-tracking“) | Nur Mit Teletubbie-Optik verfügbar Um den Text „Hilfe“ kümmert sich Windows ohne Zutun |
|---|
… und so funktioniert's:
Eigentlich genauso wie gehabt, bei den nicht größenveränderlichen Fenstern.
Ein bedienungstechnischer Unterschied existiert: Nicht nur beim Drücken der Esc-Taste,
sondern auch beim Drücken der Alt-Taste wird die Hilfesuche beendet.
Beim „richtigen“ Hilfeknopf wirken nur Esc und Alt+Tab.
Es ist relativ leicht, die Routine so zu schreiben, dass beim inaktiven Fenster
der Hilfeknopf verschwindet. Das dürfte aber mehr verwirren als nützen.
Man kann den passenden Fenster-Stil WS_EX_CONTEXTHELP setzen oder nicht.
Gängige Dialog-Editoren weigern sich allerdings,
diesen Stil mit WS_MINIMIZEBOX usw. zu kombinieren.
Ich habe es so geschrieben, dass die Fensterprozedur nur wenig verändert werden muss. Also nicht so plump wie die MSDN-PSDK-Beispiele (auch: MFC) mit der Umschalt+F1-Taste, sondern etwas eleganter mit modalen Schleifen.
Zurzeit funktioniert es nur für Dialoge.
Diese bekommen – genau wie sonst auch – eine WM_HELP-Nachricht zugesandt,
auf die man wie gewohnt reagieren kann.
GetSystemMetrics(SM_CXSIZE),
diese Funktion lieferte offensichtlichen Müll.
Deshalb wurden die Größen empirisch aus Daten von SystemParametersInfo()
ermittelt.
static void GetQuestionButtonRect(HWND Wnd, LPRECT r) { NONCLIENTMETRICS ncm; ncm.cbSize=sizeof(ncm); SystemParametersInfo(SPI_GETNONCLIENTMETRICS,sizeof(ncm),&ncm,0); GetWindowRect(Wnd,r); // hier: X-Ausdehnung ermitteln r->left=r->right-r->left -GetSystemMetrics(SM_CYFRAME) -ncm.iCaptionHeight*4 +1; r->right=r->left+ncm.iCaptionHeight-2; r->top=GetSystemMetrics(SM_CXFRAME)+2; r->bottom=r->top+ncm.iCaptionHeight-4; }
GetQuestionButtonRect() liefert Fenster-Koordinaten.
static BOOL NcInQuestionRect(HWND Wnd, POINT p) { RECT r; GetWindowRect(Wnd,&r); p.x-=r.left; p.y-=r.top; GetQuestionButtonRect(Wnd,&r); return PtInRect(&r,p); } static BOOL InQuestionRect(HWND Wnd, POINT p) { ClientToScreen(Wnd,&p); return NcInQuestionRect(Wnd,p); }
DrawFrameControl(), die allerdings bei XP-Stil
einen recht „altmodischen“ Knopf produziert, der dann recht hässlich aussieht.
DrawThemeBackground().
Damit's auch unter Nicht-XP läuft, werden alle drei notwendigen
DLL-Eintrittspunkte dynamisch beschafft.
static void DrawQuestionButton(HWND Wnd, bool Pushed, bool Hot) { HDC dc=GetWindowDC(Wnd); RECT r; HTHEME th=0; HANDLE hLib; GetQuestionButtonRect(Wnd,&r); hLib=LoadLibraryA("uxtheme.dll"); if (hLib) { th=(HTHEME(_stdcall*)(HWND,LPCWSTR))GetProcAddress(hLib,"OpenThemeData")(Wnd,L"WINDOW"); } if (th) { // Teletubbie-Optik HRESULT (_stdcall*pDrawThemeBackground)(HTHEME,HDC,int,int,const RECT*,const RECT*); (FARPROC)pDrawThemeBackground=GetProcAddress(hLib,"DrawThemeBackground"); pDrawThemeBackground(th,dc,WP_HELPBUTTON,Pushed?HBS_PUSHED:Hot?HBS_HOT:HBS_NORMAL,&r,NULL); (HRESULT(_stdcall*)(HTHEME))GetProcAddress(hLib,"CloseThemeData")(th); }else{ DrawFrameControl(dc,&r,DFC_CAPTION,Pushed?DFCS_CAPTIONHELP|DFCS_PUSHED:DFCS_CAPTIONHELP); } if (hLib) FreeLibrary(hLib); ReleaseDC(Wnd,dc); }Bei den einzeiligen Aufrufen mittels
GetProcAddress() nicht schwindlig werden!
Ich erspare mir hier unnötige(?) NULL-Zeigertests.
Die Funktion GetWindowTheme() ging merkwürdigerweise nicht.
PointHelpModalLoop() kümmert sich um das gesamte
point-and-shoot (Zielen und Abdrücken), und kehrt erst zurück,
wenn der Vorgang beendet wurde (mit oder ohne Hilfe).
Es wird eine WM_HELP-Nachricht abgesetzt.
SendWmHelp() wird öfters benötigt,
beispielsweise für die Reaktionsroutine auf WM_CONTEXTMENU.
static void SendWmHelp(HWND Wnd, HWND Child, int MouseX, int MouseY) { HELPINFO hi; hi.cbSize=sizeof(hi); // HELPINFO auffüllen hi.iContextType=HELPINFO_WINDOW; hi.iCtrlId=GetDlgCtrlID(Child); hi.hItemHandle=Child; hi.dwContextId=GetWindowContextHelpId(Child); hi.MousePos.x=MouseX; hi.MousePos.y=MouseY; SendMessage(Wnd,WM_HELP,0,(LPARAM)&hi); } static void PointHelpModalLoop(HWND Wnd) { MSG Msg; bool DoExit=false; SetCursor(LoadCursor(0,IDC_HELP)); while (GetMessage(&Msg,0,0,0)) { switch (Msg.message) { case WM_KEYDOWN: switch (Msg.wParam) { case VK_TAB: case VK_ESCAPE: case VK_F1: DoExit=true; break; }break; case WM_LBUTTONDOWN: { POINT p={GET_X_LPARAM(Msg.lParam),GET_Y_LPARAM(Msg.lParam)}; HWND Child=ChildWindowFromPointEx(Wnd,p,CWP_SKIPINVISIBLE|CWP_SKIPTRANSPARENT); ClientToScreen(Wnd,&p); SendWmHelp(Wnd,Child,p.x,p.y); }nobreak; case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_SYSKEYDOWN: DoExit=true; break; } TranslateMessage(&Msg); DispatchMessage(&Msg); if (DoExit) break; } }
static void QuestionModalLoop(HWND Wnd) { MSG Msg; bool Pushed=true; SetCapture(Wnd); DrawQuestionButton(Wnd,true,false); while (GetMessage(&Msg,0,0,0)) { switch (Msg.message) { case WM_MOUSEMOVE: { POINT p={GET_X_LPARAM(Msg.lParam),GET_Y_LPARAM(Msg.lParam)}; bool InQuest=InQuestionRect(Wnd,p)!=0; if (Pushed ^ InQuest) { Pushed=InQuest; DrawQuestionButton(Wnd,Pushed,false); } }break; case WM_LBUTTONUP: { if (Pushed) { PointHelpModalLoop(Wnd); // nächste modale Schleife DrawQuestionButton(Wnd,false,false); } ReleaseCapture(); }return; // modale Schleife verlassen } DispatchMessage(&Msg); } }
WM_NC…-Nachrichten, die uns interessieren.
In den meisten Fällen müssen wir verhindern, dass (zeitlich) hinter uns
DefWindowProc gerufen wird. Das würde alle Arbeit vernichten.
Hot) für den Hot-Tracking-Effekt unter XP-Stil
ließ sich nicht so recht vermeiden.
Zeichnen bei jeder Mausbewegung flackert zu sehr.
// Filtert diverse NC-Nachrichten zur Darstellung und Behandlung des Hilfe-Fragezeichen-Knopfes // Nur für Dialoge! Hier: Nicht für normale Fenster geeignet. // Die Dialogprozedur muss TRUE zurückgeben, wenn diese Routine TRUE liefert. BOOL HandleDlgNcMessagesForHelp(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch (Msg) { case WM_NCPAINT: case WM_NCACTIVATE: { SetWindowLong(Wnd,DWL_MSGRESULT,(LONG)DefWindowProc(Wnd,Msg,wParam,lParam)); DrawQuestionButton(Wnd,false,false); }return TRUE; case WM_NCHITTEST: { // 1. Verhindert das Verschieben des Fensters beim Knopf drücken // 2. Liefert wParam==HTHELP bei WM_NCLBUTTONDOWN POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)}; if (NcInQuestionRect(Wnd,p)) return SetDlgMsgResult(Wnd,WM_NCHITTEST,HTHELP); }break; case WM_NCMOUSEMOVE: { // Hot-Tracking (nur Teletubbie-Optik) volatile static bool Hot; if (Hot!=(wParam==HTHELP)) { Hot=!Hot; DrawQuestionButton(Wnd,false,Hot); } }break; case WM_NCLBUTTONDOWN: case WM_NCLBUTTONDBLCLK: { if (wParam==HTHELP) {QuestionModalLoop(Wnd); return TRUE;} // Ereignis schlucken!! }break; case 0xAE: // Als Ergebnis bleibt der Knopf beim inaktiven Fenster hervorgehoben case 0xAF: /*MessageBeep(0);*/ return TRUE; // unbekannte XP-Nachrichten // scheinen die Titelleiste aufzuhellen (Kontrast reduzieren) } return FALSE; }
MainDlgProc() zu filtern.
Für mehrere größenveränderliche Fenster im Programm ist's aber besser so.
Weiterhin wird der Gebrauch von PointHelpModalLoop() dargestellt,
der bspw. von einem Menüpunkt oder einer Hotkey (Umschalt+F1) aufgerufen wird.
BOOL CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
if (HandleDlgNcMessagesForHelp(Wnd,Msg,wParam,lParam)) return TRUE;
switch (Msg) {
...
case WM_COMMAND: switch (LOWORD(wParam)) {
...
case 1902: { // Kontexthilfe
SetCapture(Wnd);
PointHelpModalLoop(Wnd);
ReleaseCapture();
}break;
Das Ergebnis ist dann wie oben zu sehen.
Zu beachten ist, dass SetWindowPlacement() nicht funktioniert!
Solcher Kode muss durch SetWindowPos() ersetzt werden.
// SetWindowPlacement ist Ursache für verschwindenden Fragezeichen-Knopf! // SetWindowPlacement(ghMainWnd,&wp); // Deshalb hier: SetWindowPos SetWindowPos(ghMainWnd,0, wp.rcNormalPosition.left, wp.rcNormalPosition.top, wp.rcNormalPosition.right-wp.rcNormalPosition.left, wp.rcNormalPosition.bottom-wp.rcNormalPosition.top, SWP_NOZORDER); // Fenster in Bildschirm ziehen... SendMessage(ghMainWnd,DM_REPOSITION,0,0); ...