Currently, I am using custom window-procedures on edit-boxes in a modeless dialog to have real-time feedback to the user, i.e. disabling a command button if a required field is empty/invalid or enable it otherwise as well as filtering user input (i.e. allowing only HEX digits, ...)
This should happen "as you type", not when loosing or gaining focus.
For filtering user input in real-time (including clipboard pastes), window prodecures are the way to go, but is there any other elegant way to at least handle the common case "If edit box is empty, disable the OK button" so I can avoid writing a custom window procedure for every edit box, that would otherwise have a specific, but re-usable type of filtering procedure?
oldWindowProc = (WNDPROC)GetWindowLongA(hPassword, GWL_WNDPROC);
custom_user_data3 = (WNDPROC*)HeapAlloc(GetProcessHeap(), 0, sizeof(WNDPROC) + 4 * sizeof(HWND));
custom_user_data3[0] = oldWindowProc;
custom_user_data3[1] = (WNDPROC)hTestButton;
custom_user_data3[2] = (WNDPROC)hUrl;
custom_user_data3[3] = (WNDPROC)hUsername;
custom_user_data3[4] = (WNDPROC)hPassword;
SetWindowLongA(hPassword, GWL_USERDATA, (LONG)custom_user_data3);
The other control's HWND are transferred (cast to WNDPROC, but really HWNDs) to the custom window procedure, so it can act on them using EnableWindow.
Inside the window prodecure I then do this:
if (msg == WM_KEYUP || msg == WM_KEYDOWN || msg == WM_CHAR)
{
// IsWindow: Precaution to not break functionality in case of renaming of controls
if (IsWindow(hwnd_testbutton) && IsWindow(hwnd_username_edit) && IsWindow(hwnd_password_edit) &&
IsWindow(hwnd_url_edit) &&
((GetWindowTextLengthA(hwnd_url_edit) <= 0) || (GetWindowTextLengthA(hwnd_username_edit) <= 0) || (GetWindowTextLengthA(hwnd_password_edit) <= 0)))
{
EnableWindow(hwnd_testbutton, false);
}
else
{
EnableWindow(hwnd_testbutton, true);
}
}
Everything you need1 is already implemented in the dialog manager: Whenever the contents of a child edit control change it2 sends an EN_CHANGE
notification to the parent. The parent is the dialog and its dialog box procedure is under your control.
The remaining challenge is transliterating the cryptogram posed by the documentation into human-readable:
The parent window of the edit control receives this notification code through a
WM_COMMAND
message.
The dialog box procedure needs to handle WM_COMMAND
messages. Decoding the arguments depends on the message source as outlined in this table. EN_CHANGE
notifications fall into the row designated by Control
:
HIWORD
(wParam)
is the notification code (i.e., EN_CHANGE
)LOWORD
(wParam)
holds the control ID of the senderlParam
is the window handle (HWND
) of the senderThe following example illustrates how to respond to change notifications. It implements a dialog box procedure (DlgProc
) that handles EN_CHANGE
notifications from an edit control (IDC_EDIT
) to toggle the enabled state of a button (IDC_BTN
).
main.c:
#include <Windows.h>
#include "resources.h"
#include <stdbool.h>
#pragma comment(linker, "\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
LRESULT CALLBACK DlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CLOSE:
// End the modal dialog loop; a modeless dialog would call
// `DestroyWindow()` instead
EndDialog(hDlg, 0);
return TRUE;
case WM_COMMAND:
// Filter on `EN_CHANGE` notifications sent from the `IDC_EDIT` control
if (LOWORD(wParam) == IDC_EDIT && HIWORD(wParam) == EN_CHANGE)
{
// Determine whether the edit control contains any text
HWND hwnd_edit = (HWND)lParam;
bool non_empty = GetWindowTextLengthW(hwnd_edit) > 0;
// En-/disable `IDC_BTN` accordingly
HWND hwnd_btn = GetDlgItem(hDlg, IDC_BTN);
EnableWindow(hwnd_btn, non_empty);
// Message handled
return TRUE;
}
break;
default:
break;
}
// Default processing
return FALSE;
}
int APIENTRY wWinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PWSTR cmdline, int cmdshow)
{
return (int)DialogBoxW(hInst, MAKEINTRESOURCEW(IDD_MAIN), NULL, DlgProc);
}
resources.h:
#pragma once
#define IDD_MAIN 101
#define IDC_EDIT 1001
#define IDC_BTN 1002
resources.rc:
#include "winres.h"
#include "resources.h"
IDD_MAIN DIALOGEX 0, 0, 160, 40
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "EN_CHANGE Demo"
FONT 9, "Segoe UI", 0, 0, 0x0
BEGIN
EDITTEXT IDC_EDIT 10, 10, 100, 20
PUSHBUTTON "Test", IDC_BTN 115, 10, 35, 20, WS_DISABLED
END
This is fairly straightforward: The entry point spins up a modal dialog declared in a resource script. The only point worth mentioning is that the button (IDC_BTN
) has the WS_DISABLED
window style set. This correlates with the edit control's (IDC_EDIT
) default of holding no text.
1 Not "everything". Filtering input requires a different solution. That makes for a good question.
2 I don't know what "it" is here. I assume it's the dialog manager, but the edit control is special.