Improve default focus assignment in dialogs

Zach Gorman zach at archetypeauction.com
Sat Aug 14 12:13:16 CDT 2004


Corrects the behavior of initial focus assignment in dialogs. Includes tests
verifying the new behavior is the same as in Windows.

1) When WM_INITDIALOG returns FALSE, focus should be left at NULL (or
whatever it was previously) and no internal focus HWND variable should be
saved.
2) Modal dialogs should not be shown via ShowWindow until the message queue
first runs empty. This allows all initialization to complete before a
default focus is assigned.
3) When SetFocus is called on a dialog and no internal focus HWND variable
is currently saved for that dialog, the default message handler should
re-assign focus to the first visible, non-disabled, WS_TABSTOP control in
the dialog.


===================================================================
RCS file: /home/wine/wine/windows/dialog.c,v
retrieving revision 1.131
diff -u -r1.131 dialog.c
--- dialog.c    14 Jul 2004 00:49:40 -0000      1.131
+++ dialog.c    14 Aug 2004 16:16:27 -0000
@@ -655,33 +655,15 @@
 

     if (DIALOG_CreateControls32( hwnd, dlgTemplate, &template, hInst,
unicode ))
     {
-        HWND hwndPreInitFocus;
-
         /* Send initialisation messages and set focus */
 

-       dlgInfo->hwndFocus = GetNextDlgTabItem( hwnd, 0, FALSE );
-
-       hwndPreInitFocus = GetFocus();
-       if (SendMessageA( hwnd, WM_INITDIALOG, (WPARAM)dlgInfo->hwndFocus,
param ))
+        if (SendMessageA( hwnd, WM_INITDIALOG, (WPARAM)dlgInfo->hwndFocus,
param ))
         {
-            /* check where the focus is again,
-            * some controls status might have changed in WM_INITDIALOG */
+            /* By returning TRUE, app has requested a default focus
assignment */
             dlgInfo->hwndFocus = GetNextDlgTabItem( hwnd, 0, FALSE);
             if( dlgInfo->hwndFocus )
                 SetFocus( dlgInfo->hwndFocus );
         }
-        else
-        {
-            /* If the dlgproc has returned FALSE (indicating handling of
keyboard focus)
-               but the focus has not changed, set the focus where we expect
it. */
-            if ((GetFocus() == hwndPreInitFocus) &&
-                (GetWindowLongW( hwnd, GWL_STYLE ) & WS_VISIBLE))
-            {
-                dlgInfo->hwndFocus = GetNextDlgTabItem( hwnd, 0, FALSE);
-                if( dlgInfo->hwndFocus )
-                    SetFocus( dlgInfo->hwndFocus );
-            }
-        }
  
        if (template.style & WS_VISIBLE && !(GetWindowLongW( hwnd, GWL_STYLE
) & WS_VISIBLE))
        {
@@ -763,24 +745,30 @@
     MSG msg;
     INT retval;
     HWND ownerMsg = GetAncestor( owner, GA_ROOT );
-
+    BOOL bFirstEmpty;
+
     if (!(dlgInfo = DIALOG_get_info( hwnd, FALSE ))) return -1;
  
+    bFirstEmpty = TRUE;
     if (!(dlgInfo->flags & DF_END)) /* was EndDialog called in
WM_INITDIALOG ? */
     {
-        ShowWindow( hwnd, SW_SHOW );
         for (;;)
         {
-            if (!(GetWindowLongW( hwnd, GWL_STYLE ) & DS_NOIDLEMSG))
+            if (!PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ))
             {
-                if (!PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ))
+                if (bFirstEmpty)
                 {
+                    /* ShowWindow the first time the queue goes empty */
+                    ShowWindow( hwnd, SW_SHOWNORMAL );
+                    bFirstEmpty = FALSE;
+                }
+                if (!(GetWindowLongW( hwnd, GWL_STYLE ) & DS_NOIDLEMSG))
+               {
                     /* No message present -> send ENTERIDLE and wait */
                     SendMessageW( ownerMsg, WM_ENTERIDLE, MSGF_DIALOGBOX,
(LPARAM)hwnd );
-                    if (!GetMessageW( &msg, 0, 0, 0 )) break;
                 }
+                if (!GetMessageW( &msg, 0, 0, 0 )) break;
             }
-            else if (!GetMessageW( &msg, 0, 0, 0 )) break;
  
             if (!IsWindow( hwnd )) return -1;
             if (!(dlgInfo->flags & DF_END) && !IsDialogMessageW( hwnd,
&msg))



===================================================================
RCS file: /home/wine/wine/windows/defdlg.c,v
retrieving revision 1.33
diff -u -r1.33 defdlg.c
--- defdlg.c    14 Aug 2004 00:44:08 -0000      1.33
+++ defdlg.c    14 Aug 2004 16:17:29 -0000
@@ -96,13 +96,16 @@
  
     if (IsIconic( hwnd )) return;
     if (!(infoPtr = DIALOG_get_info( hwnd, FALSE ))) return;
-    if (!IsWindow( infoPtr->hwndFocus )) return;
     /* Don't set the focus back to controls if EndDialog is already
called.*/
-    if (!(infoPtr->flags & DF_END))
-    {
-        DEFDLG_SetFocus( hwnd, infoPtr->hwndFocus );
-        return;
+    if (infoPtr->flags & DF_END) return;
+    if (!IsWindow(infoPtr->hwndFocus) || infoPtr->hwndFocus == hwnd) {
+        /* If no saved focus control exists, set focus to the first
visible,
+           non-disabled, WS_TABSTOP control in the dialog */
+        infoPtr->hwndFocus = GetNextDlgTabItem( hwnd, 0, FALSE );
+       if (!IsWindow( infoPtr->hwndFocus )) return;
     }
+    DEFDLG_SetFocus( hwnd, infoPtr->hwndFocus );
+
     /* This used to set infoPtr->hwndFocus to NULL for no apparent reason,
        sometimes losing focus when receiving WM_SETFOCUS messages. */
 }



===================================================================
RCS file: /home/wine/wine/dlls/user/tests/dialog.c,v
retrieving revision 1.5
diff -u -r1.5 dialog.c
--- dialog.c    14 Aug 2004 00:44:08 -0000      1.5
+++ dialog.c    14 Aug 2004 16:26:15 -0000
@@ -46,6 +46,10 @@
 static HINSTANCE g_hinst;                          /* This application's
HINSTANCE */
 static HWND g_hwndMain, g_hwndButton1, g_hwndButton2, g_hwndButtonCancel;
 static HWND g_hwndTestDlg, g_hwndTestDlgBut1, g_hwndTestDlgBut2,
g_hwndTestDlgEdit;
+static HWND g_hwndInitialFocusT1, g_hwndInitialFocusT2,
g_hwndInitialFocusGroupBox;
+
+static LONG g_styleInitialFocusT1, g_styleInitialFocusT2;
+static BOOL g_bInitialFocusInitDlgResult;
 

 static int g_terminated;
 

@@ -456,7 +460,6 @@
     return TRUE;
 }
 

-
 static LRESULT CALLBACK main_window_procA (HWND hwnd, UINT uiMsg, WPARAM
wParam,
         LPARAM lParam)
 {
@@ -640,6 +643,7 @@
         dwVal = DefDlgProcA(g_hwndTestDlg, DM_GETDEFID, 0, 0);
         ok ( IDCANCEL == (LOWORD(dwVal)), "WM_NEXTDLGCTL changed default
button\n");
     }
+    DestroyWindow(g_hwndTestDlg);
 }
  
 static void IsDialogMessageWTest (void)
@@ -671,6 +675,134 @@
     ok (g_terminated, "ENTER did not terminate\n");
 }
  
+
+static LRESULT CALLBACK delayFocusDlgWinProc (HWND hDlg, UINT uiMsg, WPARAM
wParam,
+        LPARAM lParam)
+{
+    switch (uiMsg)
+    {
+    case WM_INITDIALOG:
+        g_hwndMain = hDlg;
+       g_hwndInitialFocusGroupBox = GetDlgItem(hDlg,100);
+       g_hwndButton1 = GetDlgItem(hDlg,200);
+       g_hwndButton2 = GetDlgItem(hDlg,201);
+       g_hwndButtonCancel = GetDlgItem(hDlg,IDCANCEL);
+       g_styleInitialFocusT1 = GetWindowLong(g_hwndInitialFocusGroupBox,
GWL_STYLE);
+
+       /* Initially check the second radio button */
+       SendMessage(g_hwndButton1, BM_SETCHECK, BST_UNCHECKED, 0);
+       SendMessage(g_hwndButton2, BM_SETCHECK, BST_CHECKED  , 0);
+       /* Continue testing after dialog initialization */
+       PostMessage(hDlg, WM_USER, 0, 0);
+       return g_bInitialFocusInitDlgResult;
+
+    case WM_COMMAND:
+        if (LOWORD(wParam) == IDCANCEL)
+       {
+           EndDialog(hDlg, LOWORD(wParam));
+           return TRUE;
+       }
+       return FALSE;
+
+    case WM_USER:
+       g_styleInitialFocusT2 = GetWindowLong(hDlg, GWL_STYLE);
+        g_hwndInitialFocusT1 = GetFocus();
+       SetFocus(hDlg);
+        g_hwndInitialFocusT2 = GetFocus();
+       PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0);
+       return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* Helper for InitialFocusTest */
+static const char * GetHwndString(HWND hw)
+{
+  if (hw == NULL)
+    return "a null handle";
+  if (hw == g_hwndMain)
+    return "the dialog handle";
+  if (hw == g_hwndInitialFocusGroupBox)
+    return "the group box control";
+  if (hw == g_hwndButton1)
+    return "the first button";
+  if (hw == g_hwndButton2)
+    return "the second button";
+  if (hw == g_hwndButtonCancel)
+    return "the cancel button";
+
+  return "unknown handle";
+}
+
+static void InitialFocusTest (void)
+{
+    /* Test 1:
+     * This test intentionally returns FALSE in response to WM_INITDIALOG
+     * without setting focus to a control. This is not allowed according to
+     * MSDN, but it is exactly what MFC's CFormView does.
+     *
+     * Since the WM_INITDIALOG handler returns FALSE without setting the
focus,
+     * the focus should initially be NULL. Later, when we manually set
focus to
+     * the dialog, the default handler should set focus to the first
control that
+     * is "visible, not disabled, and has the WS_TABSTOP style" (MSDN).
Because the
+     * second radio button has been checked, it should be the first control
+     * that meets these criteria and should receive the focus.
+     */
+
+    g_bInitialFocusInitDlgResult = FALSE;
+    g_hwndInitialFocusT1 = (HWND) -1;
+    g_hwndInitialFocusT2 = (HWND) -1;
+    g_styleInitialFocusT1 = -1;
+    g_styleInitialFocusT2 = -1;
+
+    DialogBoxA(g_hinst, "RADIO_TEST_DIALOG", NULL,
(DLGPROC)delayFocusDlgWinProc);
+
+    ok (((g_styleInitialFocusT1 & WS_TABSTOP) == 0),
+       "Error in wrc - Detected WS_TABSTOP as default style for
GROUPBOX\n");
+
+    ok (((g_styleInitialFocusT2 & WS_VISIBLE) == 0),
+       "Modal dialogs should not be shown until the message queue first
goes empty\n");
+
+    /*todo_wine*/ ok ((g_hwndInitialFocusT1 == NULL),
+                  "Error in initial focus when WM_INITDIALOG returned
FALSE: "
+                  "Expected NULL focus, got %s (%p).\n",
+                  GetHwndString(g_hwndInitialFocusT1),
g_hwndInitialFocusT1);
+
+    /*todo_wine*/ ok ((g_hwndInitialFocusT2 == g_hwndButton2),
+                  "Error after first SetFocus() when WM_INITDIALOG returned
FALSE: "
+                     "Expected the second button (%p), got %s (%p).\n",
+                  g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
+                 g_hwndInitialFocusT2);
+
+    /* Test 2:
+     * This is the same as above, except WM_INITDIALOG is made to return
TRUE.
+     * This should cause the focus to go to the second radio button right
away
+     * and stay there (until the user indicates otherwise).
+     */
+
+    g_bInitialFocusInitDlgResult = TRUE;
+    g_hwndInitialFocusT1 = (HWND) -1;
+    g_hwndInitialFocusT2 = (HWND) -1;
+    g_styleInitialFocusT1 = -1;
+    g_styleInitialFocusT2 = -1;
+
+    DialogBoxA(g_hinst, "RADIO_TEST_DIALOG", NULL,
(DLGPROC)delayFocusDlgWinProc);
+
+    ok ((g_hwndInitialFocusT1 == g_hwndButton2),
+       "Error in initial focus when WM_INITDIALOG returned TRUE: "
+       "Expected the second button (%p), got %s (%p).\n",
+       g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
+       g_hwndInitialFocusT2);
+
+    ok ((g_hwndInitialFocusT2 == g_hwndButton2),
+       "Error after first SetFocus() when WM_INITDIALOG returned TRUE: "
+       "Expected the second button (%p), got %s (%p).\n",
+       g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
+       g_hwndInitialFocusT2);
+}
+
+
 START_TEST(dialog)
 {
     g_hinst = GetModuleHandleA (0);
@@ -680,4 +812,5 @@
     GetNextDlgItemTest();
     IsDialogMessageWTest();
     WM_NEXTDLGCTLTest();
+    InitialFocusTest();
 }


===================================================================
RCS file: /home/wine/wine/dlls/user/tests/resource.rc,v
retrieving revision 1.3
diff -u -r1.3 resource.rc
--- resource.rc 14 Jul 2004 00:52:55 -0000      1.3
+++ resource.rc 14 Aug 2004 16:28:05 -0000
@@ -32,6 +32,20 @@
   DEFPUSHBUTTON "OK",     IDOK,4,4,50,14, WS_TABSTOP | WS_GROUP
 END
  
+RADIO_TEST_DIALOG DIALOGEX 0, 0, 160, 80
+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
+CAPTION "Radio Button Test Dialog"
+FONT 8, "MS Sans Serif"
+BEGIN
+  GROUPBOX "Static",      100,6,5,92,70
+  CONTROL  "Radio1",      200,"Button",BS_AUTORADIOBUTTON |
+                          WS_GROUP | WS_TABSTOP,17,27,39,10
+  CONTROL  "Radio2",      201,"Button",BS_AUTORADIOBUTTON,17,40,39,10
+  PUSHBUTTON "Cancel",    IDCANCEL,109,20,50,14, WS_TABSTOP | WS_GROUP
+END
+
+
+
 CLASS_TEST_DIALOG DIALOG DISCARDABLE  0, 0, 91, 28
 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "CreateDialogParams Test"






More information about the wine-patches mailing list