Why does my radio button group selection get reset each time my window regains activation?

Raymond Chen

A customer reported (all incomplete information and red herrings preserved):

We have an issue related to two radio buttons in a window.The code programmatically checks the second buttonby sending the BM_SET­CHECK message.We observe that if the user clicks somewhere else on the screen(so that our application loses focus),and then clicks on the taskbar icon to return to our application,the first radio button spontaneously gets selected.

We watched all the messages in Spy++, and it appearsthat the radio button is receiving aWM_SET­FOCUSfollowed by aWM_SET­CHECK.

Is this by design?If not, what should I be looking for in my code thatis causing this erroneous selection change to occur?

The incomplete information is that the customerdidn’t say how they created those radio buttons.

The red herring is that the customer said thatthey had a problem with their window.This suggested that they were doing a custom windowimplementation (because if they were using the standarddialog implementation, they would have said dialog).

But from the symptoms,it’s clear that what’s most likely happening is thatthe radio button is created as aBS_AUTO­RADIO­BUTTON.And automatic radio buttons select themselvesautomatically (hence the name) when they receivefocus.

That explains the message sequenceofWM_SET­FOCUSfollowed by aWM_SET­CHECK:The automatic radio button receives focus,and in response it checks itself.

Therefore, the next level of investigationis why the first radio button is getting focuswhen the window is activated.

If the application window is a custom window,then the place to look is their window’sactivation and focus code, to see why focus isgoing to the first radio button instead of thesecond one.Perhaps it is putting focus on the first radiobutton temporarily, and then later realizes,“Oh wait, I really meant to put it on the secondradio button.”The fix would be to get rid of the temporaryfocus change and go straight to the secondradio button.

If the application window is a standard dialog,then we saw last time thatthe dialog manager restores focus to the windowthat had focus last,and that you could mimic the same behavior in yourown code.

It turns out that the customer was indeed usinga standard dialog,in which case the problem is that they put thedialog into an inconsistent state:They checked the second radio button butleft focus on the first radio button.This is a configuration that exists nowhere in nature,and therefore when the dialog manager tries torecreate it (given its lack of specialized knowledgeabout specific controls), it can’t.

The fix is to put focus on the second radio buttonas well as setting the check box.In fact, you can accomplish both by setting the focusto the second radio button(noting thatthere is a special process for setting focus in a dialog box)since you already are using automatic radio buttons.

Here’s a program that demonstrates the problem:

// scratch.rc
1 DIALOGEX 32, 32, 160, 38
STYLE DS_MODALFRAME | DS_SHELLFONT | WS_POPUP | WS_VISIBLE |
      WS_CAPTION | WS_SYSMENU
CAPTION "Test"
FONT 9, "MS Shell Dlg"
BEGIN
CONTROL "First", 100, "Button",
        WS_GROUP | WS_TABSTOP | BS_AUTORADIOBUTTON, 4,  4, 152, 13
CONTROL "Second", 101, "Button",BS_AUTORADIOBUTTON, 4, 20, 152, 13
END
// scratch.cpp
#include <windows.h>
#include <windowsx.h>
INT_PTR CALLBACK DlgProc(
    HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 switch (uMsg) {
 case WM_INITDIALOG:
  SetFocus(GetDlgItem(hdlg, 100));
  CheckRadioButton(hdlg, 100, 101, 101);
  return FALSE;
 case WM_COMMAND:
  switch (GET_WM_COMMAND_ID(wParam, lParam)) {
  case 100:
  case 101:
    CheckRadioButton(hdlg, 100, 101,
                     GET_WM_COMMAND_ID(wParam, lParam));
    break;
  case IDCANCEL: EndDialog(hdlg, 0); break;
  }
 }
 return FALSE;
}
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
 DialogBox(hinst, MAKEINTRESOURCE(1), nullptr, DlgProc);
 return 0;
}

Observe that we set focus to the first buttonbut check the second button.When the dialog regains focus, the second button willfire aWM_COMMAND because it thinks it wasclicked on,and in response the dialog box moves the selection tothe second button.

The fix here is actually pretty simple:Let the dialog manager handle the initial focus.Just delete the Set­Focus calland return TRUE,which means,“Hey, dialog manager, you do the focus thing,don’t worry about me.”

Another fix is to remove the code that updates the radiobuttons in response to the WM_COMMAND message.(I.e., get rid of the entire case 100 andcase 101 handlers.)Again, just let the dialog manager do the usual thing,and everything will work out just fine.

It’s great when you can fix a bug by deleting code.