How do I trigger an EN_UPDATE notification for all of my edit controls when the user’s locale information changes?


A customer had a dialog box with a bunch of edit controls. Some of these edit controls contained floating-point values, and the code parses them by calling a locale-sensitive parser, so that users in the United States (say) can use the period as the decimal marker, whereas users in Germany can use the comma. The user might change the locale settings from US-style to Germany-style, and the customer wants to handle this change by updating the text in all of the edit controls to match the new locale settings.

The customer decided that the way to do this was to handle the WM_SETTING­CHANGE message, and the customer concluded that they could get everything to work if only they could cajole every edit control into generating a EN_UPDATE notification when this happened. The existing handling for this message would finish the work. (I don't quite see how, but that's what they said.)

The literal answer to the question is that you can just send the EN_UPDATE notification yourself.

void SendFakeEnUpdateNotification(HWND hwndEdit)
{
  FORWARD_WM_COMMAND(
    GetParent(hwndEdit), GetDlgCtrlID(hwndEdit), hwndEdit,
    EN_UPDATE, SendMessage);
}

You can find all the edit controls by enumerating them.

void GenerateFakeEnUpdateNotificationsForChildWindows(HWND hdlg)
{
  EnumChildWindows(hdlg, [](HWND hwnd, LPARAM lParam)
  {
    auto hdlg = reinterpret_cast<HWND>(lParam);
    wchar_t className[10];
    if (GetClassName(hwnd, className, 10) == 4 &&
        CompareStringOrdinal(className, -1,
                             L"edit", -1, TRUE) == CSTR_EQUAL) {
      FORWARD_WM_COMMAND(
        hdlg, GetDlgCtrlID(hwndEdit), hwndEdit,
        EN_UPDATE, SendMessage);
    }
    return TRUE;
  }, reinterpret_cast<LPARAM>(hdlg));
}

This enumerates all the child windows, picks out the edit controls, and generates a fake EN_UPDATE notification on their behalf.

But you can do better.

For example, instead of sending a fake EN_UPDATE notification, you may as well just go straight to the code that handles the notification. If your dialog procedure says

  case WM_COMMAND:
    switch (GET_WM_COMMAND_ID(wParam, lParam)) {
    // These three edit controls are the ones that
    // contain decimal values.
    case IDC_VALUE1:
    case IDC_VALUE3:
    case IDC_VALUE5:
      switch (GET_WM_COMMAND_CMD(wParam, lParam)) {
      case EN_UPDATE:
        OnEditUpdate(GET_WM_COMMAND_HWND(wParam, lParam));
        break;
      ...
      }
      break;
      ...
    }
    ...
  }

then you can just call your On­Edit­Update method directly.

void TriggerEditUpdateChildWindows(HWND hdlg)
{
  EnumChildWindows(hdlg, [](HWND hwnd, LPARAM lParam)
  {
    auto hdlg = reinterpret_cast<HWND>(lParam);
    wchar_t className[10];
    if (GetClassName(hwnd, className, 10) == 4 &&
        CompareStringOrdinal(className, -1,
                             L"edit", -1, TRUE) == CSTR_EQUAL) {
      OnEditUpdate(hwnd);
    }
    return TRUE;
  }, reinterpret_cast<LPARAM>(hdlg));
}

In fact, you can do even better still. As written, we're overloading the EN_UPDATE notification, which means that the On­Edit­Update message needs to decide what got updated, and detect that this was just a fake update for the purpose of updating the decimal separator. Why not just call that function directly?

void TriggerEditUpdateChildWindows(HWND hdlg)
{
  EnumChildWindows(hdlg, [](HWND hwnd, LPARAM lParam)
  {
    auto hdlg = reinterpret_cast<HWND>(lParam);
    wchar_t className[10];
    if (GetClassName(hwnd, className, 10) == 4 &&
        CompareStringOrdinal(className, -1,
                             L"edit", -1, TRUE) == CSTR_EQUAL) {
      UpdateDecimalSeparator(hwnd);
    }
    return TRUE;
  }, reinterpret_cast<LPARAM>(hdlg));
}

Wait, you can do even better still.

You already know which three edit controls you need to update: They're IDC_VALUE1, IDC_VALUE3, and IDC_VALUE5. So you can just update them.

void UpdateDecimalSeparators(HWND hdlg)
{
  static const int editsWithDecimals[] = {
    IDC_VALUE1, IDC_VALUE3, IDC_VALUE5 };
  for (int id : editsWithDecimals) {
    UpdateDecimalSeparator(GetDlgItem(hdlg, id));
  }
}

In fact, it's highly likely that you already obtained these child window handles and have stashed them away in member variables.

void UpdateDecimalSeparators(HWND hdlg)
{
  UpdateDecimalSeparator(m_hwndValue1);
  UpdateDecimalSeparator(m_hwndValue3);
  UpdateDecimalSeparator(m_hwndValue5);
}

There you go. No need to generate a fake notification (that may confuse other parts of your program). If you want to update the decimal separator in three edit controls, then just update the decimal separator in three edit controls.

Comments (7)
  1. Erik F says:

    This sounds like a variation on the theme of Don’t use global state to manage a local problem. It’s interesting how so many problems can be sorted into a few general categories!

    1. Joker_vD says:

      Or “Trying to make one thing to do two things”. It’s also quite a common theme.

  2. Chris B says:

    How would this work if you need to make other culture specific updates, e.g. update the currency symbol from $ to €? Do you have to update each impacted aspect of the edit control individuallly (e.g. UpdateDecimalSeparator followed by UpdateCurrencySymbol) or is there a generic way to tell the control “hey – the culture just changed, please redraw yourself.”

    1. Whatever your program does when the locale changes, factor it into a separate function and call it.

  3. Joker_vD says:

    …You know what, there is still another possibility: don’t even bother. The user opens the dialog, then goes to the Control Panel, changes his locale settings, and then comes back? He might as well spend two more mouse clicks and dismiss/re-open the dialog.

    1. florian says:

      Seems like a valid approach, to me. As a user, I wouldn’t expect any program to pick up my changed locale settings for an already active dialog box. But maybe that’s just my habituation over the years.

      It’s another story if the locale settings are somehow crucial for the user to be able to proceed with their most important dialog box ever in their life, like “enter today’s properly formatted local date to confirm you want to marry me, or close the dialog box to abort”.

      :'(

  4. Karl says:

    Might not be that easy with sub/superclassed controls, used by 3-part components in your app. Edit boxes can hide anywhere (common in ms office controls).

Comments are closed.

Skip to main content