Focus change using tab key doesn't work on rich edit control, but works fine if rich edit control is replaced by WC_EDIT control.
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
LoadLibrary(TEXT("msftedit.dll"));
WNDCLASSEX mainwcex;
mainwcex.cbSize = sizeof(WNDCLASSEX);
mainwcex.style = CS_HREDRAW | CS_VREDRAW;
mainwcex.lpfnWndProc = WindowProc;
mainwcex.cbClsExtra = 0;
mainwcex.cbWndExtra = 0;
mainwcex.hInstance = hInstance;
mainwcex.hIcon = NULL;
mainwcex.hCursor = (HICON) LoadCursor(NULL, IDC_ARROW);
mainwcex.hbrBackground = NULL;
mainwcex.lpszMenuName = NULL;
mainwcex.lpszClassName = "mainwindow";
mainwcex.hIconSm = NULL;
RegisterClassEx(&mainwcex);
HWND mainWindow = CreateWindowEx(
NULL,
"mainwindow",
NULL,
WS_OVERLAPPEDWINDOW,
100,
100,
600,
400,
NULL,
NULL,
hInstance,
NULL);
HWND richEditControl = CreateWindowEx(
NULL,
"RICHEDIT50W", // Works fine if replaced by WC_EDIT.
"Rich Edit",
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
50,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
HWND button1 = CreateWindowEx(
NULL,
WC_BUTTON,
"Button1",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
200,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
HWND button2 = CreateWindowEx(
NULL,
WC_BUTTON,
"Button2",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
350,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
ShowWindow(mainWindow, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (!IsDialogMessage(mainWindow, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return static_cast<int>(msg.wParam);
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
Modify WM_GETDLGCODE return value for the rich edit control.
DLGC_WANTTAB flag and DLGC_WANTMESSAGE flag for VK_TAB key need to be removed from the return value.
This solution is based on the following MSDN article by Raymond Chen: Those who do not understand the dialog manager are doomed to reimplement it, badly.
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK RichEditProc(HWND, UINT, WPARAM, LPARAM);
WNDPROC richEditOrigProc;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
LoadLibrary(TEXT("msftedit.dll"));
WNDCLASSEX mainwcex;
mainwcex.cbSize = sizeof(WNDCLASSEX);
mainwcex.style = CS_HREDRAW | CS_VREDRAW;
mainwcex.lpfnWndProc = WindowProc;
mainwcex.cbClsExtra = 0;
mainwcex.cbWndExtra = 0;
mainwcex.hInstance = hInstance;
mainwcex.hIcon = NULL;
mainwcex.hCursor = (HICON)LoadCursor(NULL, IDC_ARROW);
mainwcex.hbrBackground = NULL;
mainwcex.lpszMenuName = NULL;
mainwcex.lpszClassName = "mainwindow";
mainwcex.hIconSm = NULL;
RegisterClassEx(&mainwcex);
HWND mainWindow = CreateWindowEx(
NULL,
"mainwindow",
NULL,
WS_OVERLAPPEDWINDOW,
100,
100,
600,
400,
NULL,
NULL,
hInstance,
NULL);
HWND richEditControl = CreateWindowEx(
NULL,
"RICHEDIT50W",
"Rich Edit",
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
50,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
richEditOrigProc = (WNDPROC) SetWindowLongPtr(richEditControl, GWLP_WNDPROC, (LONG_PTR) RichEditProc);
HWND button1 = CreateWindowEx(
NULL,
WC_BUTTON,
"Button1",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
200,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
HWND button2 = CreateWindowEx(
NULL,
WC_BUTTON,
"Button2",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
350,
50,
100,
25,
mainWindow,
NULL,
hInstance,
NULL);
ShowWindow(mainWindow, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (!IsDialogMessage(mainWindow, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return static_cast<int>(msg.wParam);
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK RichEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_GETDLGCODE:
{
// THIS IS THE IMPORTANT PART
// ***********************************
LRESULT lres = CallWindowProc(richEditOrigProc, hWnd, uMsg, wParam, lParam);
lres &= ~DLGC_WANTTAB;
if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN && ((MSG *)lParam)->wParam == VK_TAB) {
lres &= ~DLGC_WANTMESSAGE;
}
return lres;
// ***********************************
}
break;
}
return CallWindowProc(richEditOrigProc, hWnd, uMsg, wParam, lParam);
}**
Related
I'm learning to write basic Win32 apps in C++ and am trying to pass typed text from one editable textbox to a new window after pressing a button.
I noticed that the default text buffer capacity for such a transfer is 20 characters in Visual Studio 2019 (I am on 64-bit Windows 10). After I try to pass a string longer than 20 characters, I get an exception thrown.
I would like to know how to increase that buffer capacity, since eventually I want to be able to pass a filepath into the text input window and open that file.
My code:
#include <windows.h>
#include <string>
#include <iostream>
//lresult callback prototype
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//window handles
HWND hMainWindow;
HINSTANCE hMainInstance;
HWND hLblOutput;
HWND hTxtInput;
HWND hButton;
#define IDC_TEXTBOX 1000
#define IDC_BUTTON 1001
//call to winmain - equivalent of main for win32 environments
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = TEXT("NiceWindowsApp");
if (!RegisterClass(&wc))
return 1;
hMainWindow = CreateWindow(wc.lpszClassName, TEXT("My Windows Application"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL);
hMainInstance = wc.hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
//callback definition
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int offset = 0;
switch (message) {
case WM_CREATE:
hMainWindow = hWnd;
hTxtInput = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), TEXT("Type something here"),
WS_VISIBLE | WS_CHILD | ES_LEFT, 50, 50, 400, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
hButton = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("BUTTON"), TEXT("Press Me!"), WS_VISIBLE | WS_CHILD | WM_COPY | ES_LEFT, 500, 30, 100, 60, hWnd,
(HMENU)IDC_BUTTON, hMainInstance, NULL);
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON)
{
//CANNOT HANDLE MORE THAN 20 CHARACTERS!!!
std::string input;
GetWindowTextA(hTxtInput, reinterpret_cast<char*> ((char*)input.c_str()), 400);
++offset;
hLblOutput = CreateWindowEx(WS_EX_STATICEDGE, TEXT("EDIT"), input.c_str(), WS_VISIBLE | WS_CHILD | ES_READONLY | ES_LEFT, 50, 200 + offset * 26, 800, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
I have tried to increase the third parameter in GetWindowTextA() to various numbers up to 4000, but it didn't seem to help.
One correct way to do this is:
std::wstring text;
text.resize(GetWindowTextLengthW(hTxtInput));
text.resize(GetWindowTextW(hTxtInput, text.data(), text.size() + 1));
Solved as per the below:
#include <windows.h>
#include <string>
#include <iostream>
//lresult callback prototype
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//window handles
HWND hMainWindow;
HINSTANCE hMainInstance;
HWND hLblOutput;
HWND hTxtInput;
HWND hButton;
CHAR s_text_1[]{ "Some text.." };
CHAR s_text_2[]{ 0 };
#define IDC_TEXTBOX 1000
#define IDC_BUTTON 1001
//call to winmain - equivalent of main for win32 environments
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = TEXT("NiceWindowsApp");
if (!RegisterClass(&wc))
return 1;
hMainWindow = CreateWindow(wc.lpszClassName, TEXT("My Windows Application"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0, 0, 640, 480, 0, 0, hInstance, NULL);
hMainInstance = wc.hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
//callback definition
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int offset = 0;
switch (message) {
case WM_CREATE:
hMainWindow = hWnd;
hTxtInput = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), s_text_1,
WS_VISIBLE | WS_CHILD | ES_LEFT, 50, 50, 400, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
hButton = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("BUTTON"), TEXT("Press Me!"), WS_VISIBLE | WS_CHILD | WM_COPY | ES_LEFT, 500, 30, 100, 60, hWnd,
(HMENU)IDC_BUTTON, hMainInstance, NULL);
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON)
{
//CANNOT HANDLE MORE THAN 20 CHARACTERS!!!
std::wstring input;
//GetWindowTextW(hTxtInput, reinterpret_cast<char*> ((char*)input.c_str()), 400);
int lgth = GetWindowTextLength(hTxtInput);
//GetWindowTextW(hTxtInput, reinterpret_cast<wchar_t*> ((wchar_t*)input.c_str()), lgth);
//GetWindowTextA(hTxtInput, char[], 400);
GetWindowText(hTxtInput, s_text_1, 255);
++offset;
hLblOutput = CreateWindowEx(WS_EX_STATICEDGE, TEXT("EDIT"), s_text_1, WS_VISIBLE | WS_CHILD | ES_READONLY | ES_LEFT, 50, 200 + offset * 26, 800, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
SetWindowText(hLblOutput, s_text_1);
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Thanks for your hints everybody.
EDIT: I now realise that this solution is not technically perfect and can lead to undefined behaviour and/or memory leaks. I'll take the advice presented in the other answers and comments into account and adjust the code accordingly.
EDIT 2.0 (2023.02.04): My most recent code is below. Hopefully it's more robust.
#include <windows.h>
#include <string>
#include <iostream>
//last update - 04.02.2023 as per StackOverflow thread recommendations.
//lresult callback prototype
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//window handles
HWND hMainWindow;
HINSTANCE hMainInstance;
HWND hLblOutput;
HWND hTxtInput;
HWND hButton;
CHAR s_text_1[]{ "Some text.." };
#define IDC_TEXTBOX 1000
#define IDC_BUTTON 1001
//call to winmain - equivalent of main for win32 environments
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = TEXT("NiceWindowsApp");
if (!RegisterClass(&wc))
return 1;
hMainWindow = CreateWindow(wc.lpszClassName, TEXT("My Windows Application"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0, 0, 640, 480, 0, 0, hInstance, NULL);
hMainInstance = wc.hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
//callback definition
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int offset = 0;
switch (message) {
case WM_CREATE:
hMainWindow = hWnd;
hTxtInput = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), s_text_1,
WS_VISIBLE | WS_CHILD | ES_LEFT, 50, 50, 400, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
hButton = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("BUTTON"), TEXT("Press Me!"), WS_VISIBLE | WS_CHILD | WM_COPY | ES_LEFT, 500, 30, 100, 60, hWnd,
(HMENU)IDC_BUTTON, hMainInstance, NULL);
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON)
{
std::wstring input;
//resize added as suggested
input.resize(GetWindowTextLengthW(hTxtInput));
GetWindowTextW(hTxtInput, input.data(), input.size() + 1);
++offset;
hLblOutput = CreateWindowEx(WS_EX_STATICEDGE, TEXT("EDIT"), s_text_1, WS_VISIBLE | WS_CHILD | ES_READONLY | ES_LEFT, 50, 200 + offset * 26, 800, 25, hWnd,
(HMENU)IDC_TEXTBOX, hMainInstance, NULL);
SetWindowTextW(hLblOutput, input.data());
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
In my WinAPI application, I have a series of edit controls in a child window. I would like the user to be able to move between them by pressing on the tab key to go forward and shift-tab to go back, but I can't seem to figure out how to use WS_TABSTOP with child windows. What I intend to have happen is that when the user clicks the tab key, the subsequent edit control is selected. However, when I click the tab in the window of the following code the cursor simply disappears.
Here is a minimal reproducible example:
//libraries
#pragma comment ("lib", "Comctl32.lib")
#pragma comment ("lib", "d2d1.lib")
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <CommCtrl.h>
// C RunTime Header Files
#include <vector>
#include <string>
#define IDS_APP_TITLE 103
#define IDI_PRACTICE 107
#define IDI_SMALL 108
#define IDC_PRACTICE 109
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst; // current instance
WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
ATOM MyRegisterClass(HINSTANCE hInstance);
HWND childHWND;
HWND InitInstance(HINSTANCE hInstance, int nCmdShow);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_PRACTICE, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// Perform application initialization:
HWND hWnd = InitInstance(hInstance, nCmdShow);
if(!hWnd)
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PRACTICE));
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
if (!IsDialogMessage(hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
HWND edit1 = CreateWindow(WC_EDIT, L"", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP, 100, 100, 100, 100, hWnd, (HMENU)1, hInst, NULL);
HWND edit2 = CreateWindow(WC_EDIT, L"", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP, 300, 100, 100, 100, hWnd, (HMENU)2, hInst, NULL);
break;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
wcex.lpszClassName = L"Parent";
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
//Child wnd class
WNDCLASSEXW wcexChild;
wcexChild.cbSize = sizeof(WNDCLASSEX);
wcexChild.style = CS_HREDRAW | CS_VREDRAW;
wcexChild.lpfnWndProc = WndProcChild;
wcexChild.cbClsExtra = 0;
wcexChild.cbWndExtra = 0;
wcexChild.hInstance = hInstance;
wcexChild.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
wcexChild.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcexChild.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
wcexChild.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
wcexChild.lpszClassName = L"Child";
wcexChild.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcexChild) && RegisterClassExW(&wcex);
}
HWND InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(L"Parent", L"PARENT", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);
childHWND = CreateWindowW(L"Child", L"", WS_CHILD | WS_VISIBLE | WS_EX_CONTROLPARENT,
0, 0, 700, 700, hWnd, nullptr, hInstance, nullptr);
if (!hWnd)
{
return NULL;
}
ShowWindow(childHWND, nCmdShow);
UpdateWindow(childHWND);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return hWnd;
}
Problem here is that if (!IsDialogMessage(hWnd, &msg)) gets called on the wrong window.
Replacing the line with if (!IsDialogMessage(childHWND, &msg)) gets TAB navigation to work.
From the IsDialogMessage documentation:
Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.
In the posted code, the "window that contains controls" is the direct parent of the edit controls, which is childHWND.
[ EDIT ] One other problem pointed out in the comments (thanks #IInspectable) is that the extended style WS_EX_CONTROLPARENT is mistakenly passed with the style flags, instead of the extended style flags. To fix that, the call to childHWND = CreateWindowW(L"Child", L"", WS_CHILD | WS_VISIBLE | WS_EX_CONTROLPARENT, ... should be changed to childHWND = CreateWindowExW(WS_EX_CONTROLPARENT, L"Child", L"", WS_CHILD | WS_VISIBLE, ... instead.
I need a Calendar application to get START and END dates (2 DATES) from the user. I do not know how to implement it in the best way. So, I started looking this Month Calendar Control example. It has only one calendar, but I need 2-month calendars.
But, the first problem I faced is that I am getting this error
Error LNK2019 unresolved external symbol __imp_InitCommonControlsEx referenced in function "void __cdecl CreateControls(struct HWND__ *)" (?CreateControls##YAXPEAUHWND__###Z)
I cannot figure out the solution.
The complete code as given on the website:
#include <windows.h>
#include <commctrl.h>
#include <wchar.h>
#include <strsafe.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateControls(HWND);
void GetSelectedDate(HWND, HWND);
HWND hStat;
HWND hMonthCal;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Month Calendar";
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc ;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Month Calendar",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 250, 300, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
LPNMHDR lpNmHdr;
switch(msg) {
case WM_CREATE:
CreateControls(hwnd);
break;
case WM_NOTIFY:
lpNmHdr = (LPNMHDR) lParam;
if (lpNmHdr->code == MCN_SELECT) {
GetSelectedDate(hMonthCal, hStat);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void CreateControls(HWND hwnd) {
hStat = CreateWindowW(WC_STATICW, L"",
WS_CHILD | WS_VISIBLE, 80, 240, 80, 30,
hwnd, (HMENU)1, NULL, NULL);
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(icex);
icex.dwICC = ICC_DATE_CLASSES;
InitCommonControlsEx(&icex);
hMonthCal = CreateWindowW(MONTHCAL_CLASSW, L"",
WS_BORDER | WS_CHILD | WS_VISIBLE | MCS_NOTODAYCIRCLE,
20, 20, 200, 200, hwnd, (HMENU)2, NULL, NULL);
}
void GetSelectedDate(HWND hMonthCal, HWND hStat) {
SYSTEMTIME time;
const int dsize = 20;
wchar_t buf[dsize];
ZeroMemory(&time, sizeof(SYSTEMTIME));
SendMessage(hMonthCal, MCM_GETCURSEL, 0, (LPARAM) &time);
size_t cbDest = dsize * sizeof(wchar_t);
StringCbPrintfW(buf, cbDest, L"%d-%d-%d",
time.wYear, time.wMonth, time.wDay);
SetWindowTextW(hStat, buf);
}
You need to link Comctl32.lib:
#pragma comment(lib,"Comctl32.lib")
I'm trying to handle WM_KEYDOWN message using following wndproc and winmain but when I press a key(i.e. arrow keys which ascii value 24, 25, 26, 27) it doesn't handled.
WndProc
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
/*TO CHECK uMsg message*/
char buff[256];
_itoa_s(uMsg, buff, 10);
SetWindowText(hEdit, buff);
/**********************/
switch (uMsg)
case WM_CREATE:
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "Edit", "", WS_CHILD | WS_VISIBLE |ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL, 735, 5, 150, 100, hWnd, NULL, hInst, NULL);
HButton1 = CreateWindowEx(NULL, "BUTTON", "START", WS_CHILD | WS_VISIBLE | SS_CENTER, 2, 2, 80, 20, hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
HButton2 = CreateWindowEx(NULL, "BUTTON", "B", WS_CHILD | WS_VISIBLE | SS_CENTER, 82, 2, 80, 20, hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);
HButton3 = CreateWindowEx(NULL, "BUTTON", "STOP", WS_CHILD | WS_VISIBLE | SS_CENTER, 162, 2, 80, 20, hWnd, (HMENU)IDC_BUTTON3, hInst, NULL);
Hmainbmp = CreateWindowEx(NULL, "STATIC", "", WS_CHILD | WS_VISIBLE | SS_BITMAP | WS_THICKFRAME, 1, 23, 600, 500, hWnd, NULL, hInst, NULL);
break;
case WM_KEYDOWN:
PRESSED_KEY = wParam;
break;
case WM_COMMAND:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
return(0L);
}
WinMain
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){
MSG msg;
WNDCLASSEX wc;
HMENU MainMenu, FileMenu;
MainMenu = CreateMenu();
FileMenu = CreatePopupMenu();
AppendMenu(FileMenu, MF_STRING, IDC_OPEN, "Open");
AppendMenu(MainMenu, MF_POPUP, (UINT_PTR)FileMenu, "FILE");
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.lpszMenuName = lpszAppName;
wc.lpszClassName = lpszAppName;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hIconSm = (HICON)LoadImage(hInstance, lpszAppName, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
if (!RegisterClassEx(&wc)) return(FALSE);
hInst = hInstance;
hWnd = CreateWindowEx(0, lpszAppName, lpszTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 900, 600, NULL, MainMenu, hInstance, NULL);
if (!hWnd) return(FALSE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Why below case doesn't work? And how can I handle WM_KEYDOWN?
case WM_KEYDOWN:
PRESSED_KEY = wParam;
break;
UPDATE
When I minimize and maximize window again, WM_KEYDOWN processed but if I click any button, it isn't working.
According to the WM_KEYDOWN document:
Posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when the ALT key is not pressed.
However, there are other buttons and edit boxes in your window. So when you press other buttons or click on the edit box, the focus will be on that window.which causes the WndProc function to be unable to process the WM_KEYDOWN message.
You can use the SetFocus function to set the keyboard focus window.Then you can get the WM_KEYDOWN message through the main window, and display the value through the Edit control.
Like the following code:
case WM_KEYDOWN:
PRESSED_KEY = wParam;
_itoa_s(wParam, buff, 10);
SetWindowTextA(hEdit, buff);
break;
Of course, if you don't want to focus on a specific window to get keyboard information, I recommend you use the SetWindowsHookEx function to get the global keyboard message, without focusing on a specific window.
Edit:
If you want to handle all WM_KEYDOWN messages, you can use the SetWindowsHookEx function.
You only need to modify a few parts of your code:
1.Add a function to handle the keyboard:
LRESULT __stdcall KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char buff[256] = "";
PKBDLLHOOKSTRUCT key = (PKBDLLHOOKSTRUCT)lParam;
//a key was pressed
if (wParam == WM_KEYDOWN && nCode == HC_ACTION)
{
_itoa_s(key->vkCode, buff, 10);
SetWindowTextA(hEdit, buff);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
2.Set the hook process to the keyboard in the main function:
HHOOK _k_hook;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szCmdLine, _In_ int nCmdShow)
{
MSG msg;
WNDCLASSEX wc;
HMENU MainMenu, FileMenu;
_k_hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
...
}
3.Finally, don't forget to delete the hook process after the program ends:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HWND HButton1, HButton2, HButton3, Hmainbmp;
switch (uMsg)
{
...
case WM_DESTROY:
if (_k_hook)
UnhookWindowsHookEx(_k_hook);
PostQuitMessage(0);
break;
...
}
}
In this way, you don't need to process the message of WM_KEYDOWN in WndProc. The message of pressing the key is processed in the KeyboardProc function, and the value of the key is displayed in the Edit control.
I've created in plain win32 c++ an options menu with an edit box using embedded resources. The edit box ID_EDIT_OPTIONS_BOX has a default value of 30. I can change to value in the edit box to eg 10. I've used the OK-button the place the code for reading and writing the editbox. After I click OK, this new value is stored in my variable INT testInt. But when i re-open the options menu, the edit box still reflects the default value as stored in the resource file.
The basic code I use is this:
testInt = GetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, NULL, FALSE);
SetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, testInt, FALSE);
Do I need to refresh the window or am I missing something really trivial? Below I've listed the full program code, resource header and file.
The program code:
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include "resource.h"
#define WIN32_LEAN_AND_MEAN
// Global variables
static TCHAR szWindowClass[] = _T("DesktopApp");
static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");
HINSTANCE hInst;
int testInt;
// Forward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK CheckOptionsProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
MessageBox(NULL,
_T("Call to RegisterClassEx failed!"),
_T("Windows Desktop Guided Tour"),
NULL);
return 1;
}
// Store instance handle in our global variable
hInst = hInstance;
HWND hWnd = CreateWindow(
szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 100,
NULL,
NULL,
hInstance,
NULL
);
if (!hWnd)
{
MessageBox(NULL,
_T("Call to CreateWindow failed!"),
_T("Windows Desktop Guided Tour"),
NULL);
return 1;
}
ShowWindow(hWnd,
nCmdShow);
UpdateWindow(hWnd);
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_FILE_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
case ID_EDIT_OPTIONS:
DialogBox(hInst, MAKEINTRESOURCE(ID_EDIT_OPTIONS), hWnd, (DLGPROC)CheckOptionsProc);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
LRESULT CALLBACK CheckOptionsProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_EDIT_OPTIONS_OK:
testInt = GetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, NULL, FALSE);
SetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, testInt, FALSE);
EndDialog(hDlg, IDOK);
break;
}
}
return 0;
}
The resource header
#define IDR_MENU 10
#define ID_FILE_EXIT 20
#define ID_EDIT_OPTIONS 30
#define ID_EDIT_OPTIONS_BOX 40
#define ID_EDIT_OPTIONS_OK 50
And resource file
#include "resource.h"
#include <windows.h>
IDR_MENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
POPUP "&Edit"
BEGIN
MENUITEM "&Options...", ID_EDIT_OPTIONS
END
END
ID_EDIT_OPTIONS DIALOGEX 0, 0, 260, 128
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Options"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
FONT 9, "SEGOE UI"
BEGIN
CONTROL "&OK", ID_EDIT_OPTIONS_OK, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 145, 110, 50, 11
CONTROL "30", ID_EDIT_OPTIONS_BOX, EDIT, ES_RIGHT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 200, 13, 18, 10
END
Use SetDlgItemInt when dialog received WM_INITDIALOG message:
LRESULT CALLBACK CheckOptionsProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
SetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, testInt, FALSE);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_EDIT_OPTIONS_OK:
testInt = GetDlgItemInt(hDlg, ID_EDIT_OPTIONS_BOX, NULL, FALSE);
EndDialog(hDlg, IDOK);
break;
}
}
return 0;
}
and remember to initialize testInt:
int testInt = 30;