How do I detect a WM_KEYDOWN on child window? - winapi

For a standard control like button, edit, etc I can subclass it but how is this done for a child window(I'm using as a container)? I know the WM_KEYDOWN is sent to the parent window but I couldn't get it directly from the respective child window. I've tried subclassing it(not sure if this does make sense since it's a window and has its own window procedure already in the WNDCLASSW.lpfnWndProc member) but this subprocedure didn't get a WM_KEYDOWN message anyway. I can get the control's hwnd from the cursor position (I need to consider keyboard focus too) by using GetCursorPos() and WindowFromPoint() like below but I feel it's hacky. What's the proper way to do this?
currently it look like this:
case WM_KEYUP:
case WM_KEYDOWN:
{
POINT p;
if(GetCursorPos(&p))
{
HWND control = WindowFromPoint(p);
if(control)
{
// just testing it
int len = GetWindowTextLength(control);
wchar_t buffer[len + 1];
GetWindowText(control, buffer, sizeof(buffer));
MessageBox(NULL, buffer, L"", MB_OK);
}
}
}
break;
the window look like this:
the goal is catch the WM_KEYDOWN on the respective window where the user typed something, then call a function to deal with this event, like window1_onKeyDown(), window2_onKeyDown() and such.
Here's full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void createWindow1(HWND);
void createWindow2(HWND);
HBRUSH hBrush1, hBrush2;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.lpszClassName = L"my window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
hBrush1 = CreateSolidBrush(RGB(173, 164, 237));
hBrush2 = CreateSolidBrush(RGB(171, 171, 171));
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 330, 270, NULL, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DeleteObject(hBrush1);
DeleteObject(hBrush2);
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg)
{
case WM_CREATE:
createWindow1(hwnd);
createWindow2(hwnd);
break;
case WM_KEYUP:
case WM_KEYDOWN:
{
POINT p;
if(GetCursorPos(&p))
{
HWND control = WindowFromPoint(p);
if(control)
{
int len = GetWindowTextLength(control);
wchar_t buffer[len + 1];
GetWindowText(control, buffer, sizeof(buffer));
MessageBox(NULL, buffer, L"", MB_OK);
}
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void createWindow1(HWND hOwner)
{
WNDCLASSW wc = {0};
wc.lpszClassName = L"window2";
wc.hInstance = NULL;
wc.hbrBackground = hBrush1;
wc.lpfnWndProc = WndProc1;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"window2",
WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_EX_CONTROLPARENT,
5, 5, 200, 100,
hOwner, 0, NULL, 0);
}
void createWindow2(HWND hOwner)
{
WNDCLASSW wc = {0};
wc.lpszClassName = L"window3";
wc.hInstance = NULL;
wc.hbrBackground = hBrush2;
wc.lpfnWndProc = WndProc2;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"window3",
WS_VISIBLE | WS_TABSTOP | WS_CHILD,
5, 120, 200, 100,
hOwner, 0, NULL, 0);
}
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
CreateWindow(L"Button", L"Button A",
WS_VISIBLE | WS_TABSTOP | WS_CHILD,
5, 5, 80, 25,
hwnd, 0, NULL, 0);
break;
case WM_KEYUP:
case WM_KEYDOWN:
MessageBox(NULL, L"hello from proc1", L"", MB_OK);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
CreateWindow(L"Button", L"Button ",
WS_VISIBLE | WS_TABSTOP | WS_CHILD,
5, 5, 80, 25,
hwnd, 0, NULL, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}

You can use SetFocus in the child window.
Some code:
case WM_KEYDOWN:
{
POINT p;
HWND control = GetFocus();
if (control)
{
int len = GetWindowTextLength(control);
wchar_t* buffer = new wchar_t[len + 1];
GetWindowText(control, buffer, len + 1);
MessageBox(NULL, buffer, L"", MB_OK);
}
}
break;
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
CreateWindow(L"Button", L"Button A",
WS_VISIBLE | WS_TABSTOP | WS_CHILD,
5, 5, 80, 25,
hwnd, 0, NULL, 0);
break;
case WM_LBUTTONDOWN:
SetFocus(hwnd);
break;
case WM_KEYUP:
case WM_KEYDOWN:
MessageBox(NULL, L"hello from proc1", L"", MB_OK);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
You can also use the current method, and then use PostMessage/SendMessage to forward the WM_KEYDOWN message to the child window:
if (control)
{
int len = GetWindowTextLength(control);
wchar_t* buffer = new wchar_t[len + 1];
GetWindowText(control, buffer, len + 1);
MessageBox(NULL, buffer, L"", MB_OK);
PostMessage(hwnd_child, msg, wParam, lParam);
}

Related

CreateWindow “Edit” typed characters are 'invisible'?

After having researched all I could find on the proper syntax for the CreateWindow("Edit") call, I have to throw in the towel: when I run the program, all I get in the Edit box is "invisible characters". The cursor is moving right as I type, but the characters I enter are nowhere to be seen. Only when I select the box content with the mouse do I see the text. But as soon as I release the mouse, I can not longer see anything.
Here is the entire code which leads to 'text not showing' in the Edit control:
#include <windows.h>
#define FILE_MENU_NEW 1
#define FILE_MENU_OPEN 2
#define FILE_MENU_QUIT 3
#define CHANGE_TITLE 4
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void AddMenus(HWND);
void AddControls(HWND);
HMENU hMenu;
HWND hEdit;
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd)
{
WNDCLASS wc = {0}; // Assign 0 to all its elements initialy
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hInstance = hInstance;
wc.lpszClassName = L"myWindowClass";
wc.lpfnWndProc = WindowProc; // this is a pointer to a function
if(!RegisterClass(&wc))
return -1;
HWND hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
L"myWindowClass",
L"Learn to Program Windows - Roger Breton",
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
100, 100, 800, 600,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL)
{
return 0;
}
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
switch(wParam)
{
case FILE_MENU_QUIT:
DestroyWindow(hWnd);
break;
case FILE_MENU_NEW:
MessageBeep(MB_ICONINFORMATION);
break;
case CHANGE_TITLE:
wchar_t text[100];
GetWindowTextW(hEdit, text, 100);
SetWindowTextW(hWnd, text);
break;
}
case WM_CREATE:
AddMenus(hWnd);
AddControls(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW));
EndPaint(hWnd, &ps);
}
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
void AddMenus(HWND hWnd)
{
hMenu = CreateMenu();
HMENU hFileMenu = CreateMenu();
HMENU hSubMenu = CreateMenu();
AppendMenu(hSubMenu, MF_STRING, NULL, L"Sub-Menu");
AppendMenu(hFileMenu, MF_STRING, FILE_MENU_NEW, L"New");
AppendMenu(hFileMenu, MF_POPUP, (UINT_PTR)hSubMenu, L"Sub-menu ");
AppendMenu(hFileMenu, MF_SEPARATOR, NULL, NULL);
AppendMenu(hFileMenu, MF_STRING, FILE_MENU_QUIT, L"Quit");
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, L"File");
AppendMenu(hMenu, MF_STRING, NULL, L"Aide");
SetMenu(hWnd, hMenu);
}
void AddControls(HWND hWnd)
{
CreateWindowW(L"Static", L"Enter text here:", WS_VISIBLE | WS_CHILD | WS_BORDER | SS_CENTER , 200, 100, 150, 50, hWnd, NULL, NULL, NULL);
hEdit = CreateWindowW(L"Edit", NULL, WS_VISIBLE | WS_CHILD | WS_BORDER , 200, 152, 100, 50, hWnd, NULL, NULL, NULL);
CreateWindowW(L"Button", L"Changez title", WS_VISIBLE | WS_CHILD, 200, 204, 150, 50, hWnd, (HMENU)CHANGE_TITLE, NULL, NULL);
}
I tried to recreate the source file many times, to no avail.
You have a missing break; statement at the end of your case WM_COMMAND: block. As it stands, your code will 'fall through' to the case WM_CREATE: code after processing any WM_COMMAND.
Adding that break; statement appears to fix your code (when I test it):
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_COMMAND:
switch (wParam) {
case FILE_MENU_QUIT:
DestroyWindow(hWnd);
break;
case FILE_MENU_NEW:
MessageBeep(MB_ICONINFORMATION);
break;
case CHANGE_TITLE:
wchar_t text[100];
GetWindowTextW(hEdit, text, 100);
SetWindowTextW(hWnd, text);
break;
}
break; // ** You missed this line! **
case WM_CREATE:
AddMenus(hWnd);
AddControls(hWnd);
break;
//...
Such mistakes can be spotted if you enable all compiler warnings: The static code analyser in MSVC gives the following message:
warning C26819: Unannotated fallthrough between switch labels (es.78).

How do i detect the control that sent the WM_KEYDOWN?

WM_KEYDOWN message sends the message to the main window, I'd like to get control that originated the message. I've tried with GetFocus() handling WM_KEYDOWN like below but it didn't do anything, even when a press a key with focus on the buttona. How is that done? what am I missing?
Here's how I'm handling WM_KEYDOWN:
case WM_KEYDOWN:
HWND hFocus = GetFocus();
if(hFocus == buttonA)
{
buttona_onKeyDown();
}
else if(hFocus == buttonB)
{
buttonb_onKeyDown();
}
else
{
//MessageBox(NULL, L"Not found!", L"", MB_OK);
}
break;
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void buttona_onKeyDown();
void buttonb_onKeyDown();
HWND buttonA, buttonB;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Buttons";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
HWND hWnd =
CreateWindowW(wc.lpszClassName, L"Buttons",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
150, 150, 300, 200, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg)
{
case WM_CREATE:
buttonA =
CreateWindowW(L"Button", L"A",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
20, 50, 80, 25, hwnd, (HMENU) 10, NULL, NULL);
buttonB =
CreateWindowW(L"Button", L"B",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
120, 50, 80, 25, hwnd, (HMENU) 11, NULL, NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
HWND hFocus = GetFocus();
if(hFocus == buttonA)
{
buttona_onKeyDown();
}
else if(hFocus == buttonB)
{
buttonb_onKeyDown();
}
else
{
//MessageBox(NULL, L"Not found!", L"", MB_OK);
}
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void buttona_onKeyDown()
{
MessageBox(NULL, L"A OnKeyDown", L"", MB_OK);
}
void buttonb_onKeyDown()
{
MessageBox(NULL, L"B OnKeyDown", L"", MB_OK);
}

Is there a way to detect if a Window has been flashed?

Using functions FlashWindow and FlashWindowEx, one can cause a window to flash. However, I can't seem to find any API that would detect if a window has been flashed. Does one exist?
Edit
Given that FLASHWINFO has DWORD dwFlags, I would have guessed that this info would be stored in the window and would be accessible using either GetWindowLongW or GetWindowLongPtrW, but I can't find it there.
Reason I need this is for automation.
According to the ShellProc:
lParam [in]
Type: LPARAM
HSHELL_REDRAW The value is TRUE if the window is flashing, or FALSE otherwise.
So we just need to add a SetWindowsHookExA function to catch the WH_SHELL message.
Here is a sample:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK procShell(int, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HHOOK hook = SetWindowsHookEx(WH_SHELL, procShell, NULL,GetCurrentThreadId());
int e = GetLastError();
static TCHAR szAppName[] = TEXT("Test Flashing");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
UnhookWindowsHookEx(hook);
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_LBUTTONDOWN:
FlashWindow(hwnd, TRUE);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT CALLBACK procShell(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HSHELL_REDRAW) {
if (lParam)
{
OutputDebugString(L"Window is flashing\n");
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
If we press the left button, the FlashWindow will be called. Then the hook function will get the HSHELL_REDRAW message and output:

Can't expand TreeView item using TreeView_Expand

I want to make a TreeView's item dynamically create its children when the item is expanding. According to http://support.microsoft.com/kb/130697, I wrote the following code:
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <windowsx.h>
#include <CommCtrl.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 hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void MainWindow_OnDestroy(HWND hWnd);
void MainWindow_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify);
LRESULT MainWindow_OnNotify(HWND hWnd, int idFrom, NMHDR *pnmhdr);
#define ID_TREEVIEW 100
#define ID_EXPAND 101
HTREEITEM root_item;
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
WNDCLASSEX wcex = { sizeof(wcex) };
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
LoadIconMetric(NULL, IDI_APPLICATION, LIM_LARGE, &wcex.hIcon);
wcex.hCursor = static_cast<HCURSOR>(LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED));
wcex.lpszClassName = TEXT("MainWindow");
LoadIconMetric(NULL, IDI_APPLICATION, LIM_SMALL, &wcex.hIconSm);
RegisterClassEx(&wcex);
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("TreeView Test"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy);
HANDLE_MSG(hWnd, WM_NOTIFY, MainWindow_OnNotify);
HANDLE_MSG(hWnd, WM_COMMAND, MainWindow_OnCommand);
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
HWND h_treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, TEXT("TreeView"), WS_CHILD | WS_VISIBLE | TVS_HASBUTTONS | TVS_LINESATROOT, 10, 10, 400, 400, hWnd, reinterpret_cast<HMENU>(ID_TREEVIEW), GetModuleHandle(NULL), NULL);
TVINSERTSTRUCT tvis = { TVI_ROOT, TVI_LAST, { TVIF_TEXT | TVIF_CHILDREN } };
tvis.item.pszText = TEXT("Root");
tvis.item.cChildren = 1;
root_item = TreeView_InsertItem(h_treeview, &tvis);
CreateWindow(WC_BUTTON, TEXT("Expand"), WS_CHILD | WS_VISIBLE, 420, 10, 75, 23, hWnd, reinterpret_cast<HMENU>(ID_EXPAND), GetModuleHandle(NULL), NULL);
return TRUE;
}
void MainWindow_OnDestroy(HWND hWnd)
{
PostQuitMessage(0);
}
LRESULT MainWindow_OnNotify(HWND hWnd, int idFrom, NMHDR *pnmhdr)
{
switch (pnmhdr->code)
{
case TVN_ITEMEXPANDING:
{
LPNMTREEVIEW pnmtv = reinterpret_cast<LPNMTREEVIEW>(pnmhdr);
if (pnmtv->action == TVE_EXPAND)
{
LPCTSTR items[] = { TEXT("Item 1"), TEXT("Item 2"), TEXT("Item 3") };
for (LPCTSTR item : items)
{
TVINSERTSTRUCT tvis = { pnmtv->itemNew.hItem, TVI_LAST, { TVIF_TEXT } };
tvis.item.pszText = const_cast<LPTSTR>(item);
TreeView_InsertItem(pnmhdr->hwndFrom, &tvis);
}
}
break;
}
case TVN_ITEMEXPANDED:
{
LPNMTREEVIEW pnmtv = reinterpret_cast<LPNMTREEVIEW>(pnmhdr);
if (pnmtv->action == TVE_COLLAPSE)
{
TreeView_Expand(pnmhdr->hwndFrom, pnmtv->itemNew.hItem, TVE_COLLAPSE | TVE_COLLAPSERESET);
}
break;
}
}
return 0;
}
void MainWindow_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify)
{
if (id == ID_EXPAND)
{
TreeView_Expand(GetDlgItem(hWnd, ID_TREEVIEW), root_item, TVE_EXPAND);
}
}
The code creates a Window with a TreeView and a Button. When the button is clicked, the TreeView's root item should be expanded.
I can expand the root item for the first time. Either click the "Expand" button or click the "+" sign beside the root item is OK. But if I collapsed the root item, I can't expand it again by click the "Expand" button again, while the "+" sign still works. I noticed that although I've used the TVE_COLLAPSERESET flag to clear the TVIS_EXPANDEDONCE flag, it still has the flag before the TreeView_Expand call. Accroding to MSDN, I think I'll need the TVIS_EXPANDEDONCE flag to remain unset. Where am I doing it wrong?
Use PostMessage to reset the TVE_COLLAPSERESET flag:
PostMessage(pnmhdr->hwndFrom, TVM_EXPAND, TVE_COLLAPSE | TVE_COLLAPSERESET, reinterpret_cast<LPARAM>(pnmtv->itemNew.hItem));
To my understanding, the TreeView send the TVN_ITEMEXPANDED notification, which send message back to the TreeView, which may cause the problem. We may need to delay the message until the current message has been processed.

TreeView item lost higlight color after minimize

I have simple win32 application (not dialog box), and treeview in this application.
All works, but if I have selected item in the treeview, and I minimize and restore application, selected item changes blue higlighting color to grayed color.
How to correct this without using custom draw?
If I have treeview in the dialog box, selected item has always blue color.
CODE:
HWND g_hTree;
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG Msg;
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return (int) Msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
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, MAKEINTRESOURCE(IDI_TREEVIEWTEST));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_TREEVIEWTEST);
wcex.lpszClassName = L"TreeViewTestClass";
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
InitCommonControls();
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(L"TreeViewTestClass", L"Tree View Test", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
void AddItems();
#define TVS_STYLES (TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS | TVS_INFOTIP | TVS_TRACKSELECT | WS_VSCROLL | WS_TABSTOP)
#define ID_TREEVIEW 505
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_CREATE:
{
g_hTree = CreateWindowEx(0, WC_TREEVIEWW, L"Test_Tree_View", WS_CHILD | WS_VISIBLE | WS_BORDER | TVS_STYLES, 10, 10, 240, 480, hWnd, (HMENU)ID_TREEVIEW, NULL, NULL);
AddItems();
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void AddItems()
{
TVITEM tvi;
HTREEITEM htSelected;
TVINSERTSTRUCT tvinsert; // struct to config out tree control
HTREEITEM htParent; // Tree item handle
HTREEITEM htBefore; // .......
HTREEITEM htRoot;
tvinsert.hParent=NULL; // top most level no need handle
tvinsert.hInsertAfter=TVI_ROOT; // work as root level
tvinsert.item.mask=TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
tvinsert.item.pszText=L"Parent1";
tvinsert.item.iImage=0;
tvinsert.item.iSelectedImage=1;
htParent = TreeView_InsertItem(g_hTree, &tvinsert);
htRoot = htParent;
htBefore = htParent;
tvinsert.hParent=htParent; // handle of the above data
tvinsert.hInsertAfter=TVI_LAST; // below parent
tvinsert.item.pszText=L"Child 1";
htParent = TreeView_InsertItem(g_hTree, &tvinsert);
tvinsert.hParent=htParent;
tvinsert.item.pszText=L"Child Of Child 1";
htParent = TreeView_InsertItem(g_hTree, &tvinsert);
tvinsert.hParent=htBefore; // handle of the above data
tvinsert.hInsertAfter=TVI_LAST; // below parent
tvinsert.item.pszText=L"Child 2";
htParent = TreeView_InsertItem(g_hTree, &tvinsert);
tvinsert.hParent=NULL; // top most level no need handle
tvinsert.hInsertAfter=TVI_LAST; // work as root level
tvinsert.item.pszText=L"Parent2";
htParent = TreeView_InsertItem(g_hTree, &tvinsert);
}
It seems your tree-view control is losing focus when the window is minimized. You can try handling WM_ACTIVATE and using SetFocus to manually set focus to the tree-view control.

Resources