Stop program continuing WM_KEYDOWN process - winapi

I'm not sure if it's the correct wording for my question but the issue revolves around it. I have 2 boxes that both validate on KillFocus. And another method which is called if the user presses the Next button, which calls a method that evaluates if they can continue, which validates these fields.
Due to how old this code base is, modifying this will cause issues elsewhere so I need to find a way around this without changing the way the can continue sequence is called. Here's some scenarios.
The user enters an invalid value in field 1, they press enter, the program fires the kill focus method and shows the error message, the enter key has pressed the next button which in turn validates the it again and shows the error again (different MsgBox same error). Meaning unless they unfocus manually then press enter they will always get two message boxes.
I believe this is due to the above reason as they have pressed enter which killed the focus instead of just calling can next.
Is there a way to stop the entire WM_KEYDOWN trail if it fails within the KillFocus method?
I'm sorry if this is a little bit vague and hazey, this is what I believe is happening.

#DavidHeffernan do you know of any other way to validate fields in the way that WM_KILLFOCUS does?
Allow me to make a suggestion. You can validate edit control's input in EN_CHANGE handler. From the docs:
Sent when the user has taken an action that may have altered text in an edit control.
Each time user types something, you will get this notification, which seems like a good place to validate data.
If data is invalid, you would then disable Next button using EnableWindow and indicate error somehow.
You could use EM_SHOWBALLOONTIP to pop tooltip with error message or simply change the background color of the edit control to red.
Below is the small example that illustrates my point. You should add better error checking of course, but the main idea is there:
#include <windows.h>
#include <CommCtrl.h>
#define IDC_BTN_NEXT 1000
#define IDC_BOX1 2000
#define IDC_BOX2 3000
// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
// link with Common Controls library
#pragma comment( lib, "comctl32.lib")
void onBtnNext()
{
MessageBeep(0);
}
void onKillFocus(HWND box)
{
//==================== these are needed to disable Next button
HWND hwnd = ::GetParent(box);
if (NULL == hwnd) // critical error
return; // TODO: add error handling
HWND btnNext = ::GetDlgItem(hwnd, IDC_BTN_NEXT);
if (NULL == btnNext) // critical error
return; // TODO: add error handling
//==============================================================
int len = ::GetWindowTextLength(box);
if (0 == len) // it is ok, empty text, just return
return;
// if possible, use std::wstring here, I assumed you can't...
wchar_t *txt = new wchar_t[len +1];
if (0 == ::GetWindowText(box, txt, len + 1)) // critical error, according to documentation
{
// TODO: add error handling
delete[] txt;
return;
}
//====== simple validation for illustration only, treat uppercase letter as error
int isTextValid = ::isupper(txt[0]);
for (int i = 1; 0 == isTextValid && i < (len + 1); isTextValid = ::isupper(txt[++i]));
delete[] txt;
//==============================================
if (isTextValid)
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof(EDITBALLOONTIP);
ebt.pszText = L" Tooltip text";
ebt.pszTitle = L" Tooltip title";
ebt.ttiIcon = TTI_ERROR_LARGE;
if (!::SendMessage(box, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt))
{
//TODO: tooltip won't show, handle error
}
EnableWindow(btnNext, FALSE); // disable Next button
return; // our work is successfully done
}
if (!::SendMessage(box, EM_HIDEBALLOONTIP, 0, 0))
{
//TODO: tooltip won't hide, handle error
}
EnableWindow(btnNext, TRUE); // enable Next button
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
HWND hwndBox1 = CreateWindowEx(0, WC_EDIT, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
20, 20, 250, 20, hwnd, (HMENU)IDC_BOX1,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBox1) // add better error handling, this is for illustration only
return -1;
HWND hwndBox2 = CreateWindowEx(0, WC_EDIT, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
20, 50, 250, 20, hwnd, (HMENU)IDC_BOX2,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBox2) // add better error handling, this is for illustration only
return -1;
HWND hwndBtnNext = CreateWindowEx(0, WC_BUTTON, L"Next",
WS_CHILD | WS_VISIBLE | BS_CENTER | BS_DEFPUSHBUTTON,
20, 80, 50, 25, hwnd, (HMENU)IDC_BTN_NEXT,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBtnNext) // add better error handling, this is for illustration only
return -1;
}
return 0L;
case WM_COMMAND:
{
switch (HIWORD(wParam))
{
case BN_CLICKED:
{
if (LOWORD(wParam) != IDC_BTN_NEXT)
break;
onBtnNext();
}
break;
case EN_CHANGE:
{
if (LOWORD(wParam) != IDC_BOX1 && (LOWORD(wParam) != IDC_BOX2))
break;
onKillFocus((HWND)lParam);
}
break;
default:
break;
}
}
break;
case WM_CLOSE:
::DestroyWindow(hwnd);
return 0L;
case WM_DESTROY:
{
::PostQuitMessage(0);
}
return 0L;
default:
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Main_Window";
wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
return 0;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&iccex);
hwnd = CreateWindowEx(0, L"Main_Window", L"Test",
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
50, 50, 305, 160, NULL, NULL, hInstance, 0);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}

The issue was solved in a "unconventional" way, but it worked. I noticed through debugging that the program lost focus twice on the box, once when ENTER was pressed and once when the message box popped up.
I used a static bool to avoid my program doing the error checking twice. It looks something like this -
void onKillFocus()
{
static bool isValidated = false;
if(!isValidated)
{
isValidated = true;
if(/*ValidationCheck*/)
{
//messagebox for error
}
}
}
By using this, the validation is only ran once when focus is killed stopping the message box from appearing twice, as the static bool is only alive for as long as the method is ran, meaning it's reset every time killfocus is called.

Related

Creating ComboBox in Win32 not working properly

I am creating a Win32 ComboBox for the first time. And I have a problem here.
When calling CreateWindow for the ComboBox, it calls the WndProc callback function again with the WM_CREATE message, so what happens is the ComboBox makes a child ComboBox, again and again like recursion.
Here is the code:
#include <stdio.h>
#include <conio.h>
#include <Windows.h>
#include <random>
#include <time.h>
#include <string>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = L"ComboBox";
const WCHAR *items[] = { L"Apple", L"Orange", L"Melon", L"Grape", L"Strawberry" };
HWND hwnd;
enum COMMAND_ID {
COMMAND_ID_CONTROL_COMBO_0
};
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
srand(time(NULL));
g_hInst = hInstance;
WNDCLASS wndClass;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WndProc;
wndClass.lpszClassName = lpszClass;
wndClass.lpszMenuName = NULL;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndClass);
hwnd = CreateWindow(
lpszClass,
lpszClass,
WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
(HMENU)NULL,
hInstance,
NULL);
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (true)
{
GetMessage(&msg, NULL, 0, 0);
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
static HWND hCombo;
static WCHAR str[128];
switch (msg)
{
case WM_CREATE:
{
hCombo = CreateWindow(
L"combobox",
NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWN,
10, 10, 200, 200,
hWnd,
(HMENU)COMMAND_ID_CONTROL_COMBO_0,
g_hInst,
NULL);
for (int i = 0; i < 5; ++i)
{
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)items[i]);
}
SendMessage(hCombo, CB_SETCURSEL, 0, NULL);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case COMMAND_ID_CONTROL_COMBO_0:
switch (HIWORD(wparam))
{
case CBN_SELCHANGE:
{
int iCurSel = SendMessage(hCombo, CB_GETCURSEL, NULL, NULL);
SendMessage(hCombo, CB_GETLBTEXT, iCurSel, (LPARAM)str);
SetWindowText(hWnd, str);
}
break;
case CBN_EDITCHANGE:
GetWindowText(hCombo, str, 128);
SetWindowText(hWnd, str);
break;
}
break;
default:
break;
}
}
return 0;
}
return DefWindowProc(hWnd, msg, wparam, lparam);
}
And here is the result:
I tried to put some boolean flag to execute WM_CREATE only once, and it works, I mean only creating one ComboBox without any child in it.
But, it just looked like only a white window with a border mark, there's no arrow button or anything to dropdown page that the ComboBox is supposed to have.
This recursive case never happened when I was creating different controls like Buttons, CheckBoxes, ListBoxes, etc.
And the ComboBox created doesn't look like it has the proper shape, too.
Hope I am just missing something simple.
When calling CreateWindow for the ComboBox, it calls the WndProc
callback function again with the WM_CREATE message, so what happens
is the ComboBox makes a child ComboBox, again and again like
recursion.
WM_CREATE message is sent to the window procedure of the new window after the window is created. Your first WM_CREATE message is generated by this line hwnd = CreateWindow(). Then you create another window in first WM_CREATE message, so it will generate second WM_CREATE message. Because you use the same registered class ("ComboBox" / "combobox", it is not case sensitive) to create all these windows, all of them use the same one window procedure. So you receive WM_CREATE message again and again until CreateWindow fail to create a window and return NULL.
But, it just looked like only a white window with a border mark,
there's no arrow button or anything to dropdown page that the ComboBox
is supposed to have.
The root cause is you register a class with the same name as the existing system class: "ComboBox" / "combobox". This new registered class override the existing one. It is just a common window instead of a predefined Combobox control as #RemyLebeau pointed out.
An application can register an application local class having the same
name as a system class. This replaces the system class in the context
of the application but does not prevent other applications from using
the system class.
Refer to "How the System Locates a Window Class".
To make the Combobox display in expected shape, what you need to do is changing the lpszClass to a non-predefined one, for example, like "SimpleComboBoxExample".
It is suggested to use predefined macro of Combobox Class Name: WC_COMBOBOX instead of L"combobox".
More reference: "How to Create a Simple Combo Box".
You are not actually creating a Win32 ComboBox at all. You are registering your own class named "ComboBox", and then creating a window of that class, which creates a window of that class, which creates a window of that class, and so on recursively.
You need to change this line:
LPCTSTR lpszClass = L"ComboBox";
To a different unique name, such as "MyWindowClass".
On a side note, your message loop is structured wrong. It should look like this instead:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
And in your WndProc(), the return 0; statement underneath the WM_COMMAND handler is in the wrong place. It needs to be moved inside of the WM_COMMAND handler instead:
case WM_COMMAND:
{
switch (...)
{
...
}
return 0; // <-- moved here
}
//return 0; // <-- from here

Basic window creation

I have a problem handling a Windows window, even though I did it this way once before and it worked fine. After reading through the most common suggestions for this problem, it still resides. Could someone tell me why Input-handling is broken?
Intended Behaviour:
create a window titled 'FirstTry'
Make its background black, using PatBlt
Show a message box when the main loop is entered the first time, and after pressing 'w'.
Close the window when pressing either Alt+F4, Escape, or the Close-button, displaying a closing message.
Observed behaviour:
as intended
as intended
MessageBox shows up the first time, but is not retriggerable with 'w'
Window it not closable, except with TaskManager (one time it showed the 'closing Application'-MessageBox as intended, but only one time)
window draggable until the first 'entered loop'-MessageBox is closed, after that its fixed
little blue 'busy'-circle of windows10 is shown full-time, after the first MessageBox
Conclusion: The Message-handling is broken.
And i cannot figure out why...
System:
Windows 10, Version 1803 (Build 17134.81), 64-bit
Compiler from VS 2017 Community Edition:
vcvarsall.bat amd64
cl -MTd -nologo -FC -Zi -W4 -WX -wd4100 -wd4312 FirstTry.cpp /link User32.lib Gdi32.lib
#include "windows.h"
static bool bAppIsRunning = false;
static bool bMessageAlreadyShown = false;
LRESULT CALLBACK win_MainWNDCallback(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam){
LRESULT result = 0;
switch(msg){
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:{
WPARAM vKeyCode = wParam;
bool bWasDown = ((lParam & (1 << 30)) != 0);
bool bIsDown = ((lParam & (1 << 31)) == 0);
if (bWasDown != bIsDown)
{
switch (vKeyCode)
{
case VK_ESCAPE:{
bAppIsRunning = false;
}break;
default:{
result = DefWindowProc(wnd,msg,wParam,lParam);
}break;
}
}
}break;
default:{
result = DefWindowProc(wnd,msg,wParam,lParam);
}break;
}
return result;
}
int CALLBACK WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPSTR LpCmdLine, int NCmdShow){
WNDCLASSA wndCLass = {};
wndCLass.style = CS_HREDRAW | CS_VREDRAW;
wndCLass.lpfnWndProc = win_MainWNDCallback;
wndCLass.hInstance = HInstance;
wndCLass.lpszClassName = (LPCSTR)"WindowClass";
if(RegisterClassA(&wndCLass)){
HWND wnd = CreateWindowExA(
0, wndCLass.lpszClassName, (LPCSTR)"FirstTry",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
1240, 720,
0, 0, HInstance, 0);
if(wnd){
bAppIsRunning = true;
HDC DeviceContext = GetDC(wnd);
PatBlt(DeviceContext, 0, 0, 1240, 720, BLACKNESS);
ReleaseDC(wnd, DeviceContext);
while(bAppIsRunning){
if(!bMessageAlreadyShown){
MessageBoxA(NULL, (LPCSTR)"Successfully entered loop.", (LPCSTR)"Success!", MB_ICONINFORMATION | MB_OK);
bMessageAlreadyShown = true;
}
MSG msg;
while(PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)){
switch(msg.message){
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:{
WPARAM vKeyCode = msg.wParam;
bool bWasDown = ((msg.lParam & (1<<30)) != 0);
bool bIsDown = ((msg.lParam & (1<<31)) != 0);
if(bIsDown != bWasDown){
switch(vKeyCode){
case 'W':{
bMessageAlreadyShown = false;
}break;
default:{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}break;
}
}
}
}
}
}
MessageBoxA(NULL, (LPCSTR)"Closing Application.", (LPCSTR)"Bye bye!", MB_ICONINFORMATION | MB_OK);
}
}
return ERROR_SUCCESS;
}
The main problem with your code is that you are calling TranslateMessage() and DispatchMessage() only when you receive certain key press messages. You need to call them in your main message loop for ALL messages instead. And you should be processing ALL of the messages in your WndProc callback.
You are also using TCHAR-based APIs, but are misusing LPCTSTR typecasts. You need to use the TEXT() macro instead when casting string/char literals to TCHAR.
Try something more like this instead:
#include <windows.h>
static bool bMessageAlreadyShown = false;
LRESULT CALLBACK win_MainWNDCallback(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP: {
WPARAM vKeyCode = wParam;
bool bWasDown = ((lParam & (1 << 30)) != 0);
bool bIsDown = ((lParam & (1 << 31)) == 0);
if (bWasDown != bIsDown) {
switch (vKeyCode) {
case 'W':
case VK_ESCAPE:
DestroyWindow(wnd);
return 0;
}
}
break;
}
case WM_ERASEBKGND:
PatBlt((HDC)wParam, 0, 0, 1240, 720, BLACKNESS);
return 0;
}
return DefWindowProc(wnd, msg, wParam, lParam);;
}
int CALLBACK WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPSTR LpCmdLine, int NCmdShow) {
WNDCLASS wndCLass = {};
wndCLass.style = CS_HREDRAW | CS_VREDRAW;
wndCLass.lpfnWndProc = win_MainWNDCallback;
wndCLass.hInstance = HInstance;
wndCLass.lpszClassName = TEXT("WindowClass");
if (RegisterClass(&wndCLass)) {
HWND wnd = CreateWindowEx( 0, wndCLass.lpszClassName, TEXT("FirstTry"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 1240, 720, 0, 0, HInstance, 0);
if (wnd) {
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) {
if (!bMessageAlreadyShown) {
bMessageAlreadyShown = true;
MessageBox(NULL, TEXT("Successfully entered loop."), TEXT("Success!"), MB_ICONINFORMATION | MB_OK);
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
MessageBox(NULL, TEXT("Closing Application."), TEXT("Bye bye!"), MB_ICONINFORMATION | MB_OK);
return ERROR_SUCCESS;
}
Note that I removed your bAppIsRunning variable, as it has beecome redundant once the message loop processes the WM_QUIT message instead.
I also removed handling of ALT-F4, as the OS handles that for you automatically. It closes the window, triggering a WM_CLOSE message. By default, DefWindowProc() handles WM_CLOSE by destroying the window, which triggers a WM_DESTROY message.
I also added handling for WM_ERASEBKGND to draw a background on the window. Drawing from outside of the message loop is wrong. As soon as the window needs to be refreshed onscreen, any drawing you do is lost, so you have to redraw everything in response to WM_ERASEBKGND and WM_PAINT.

How to implement "teamviewer quickconnect"-like button into foreign window?

I'd like to put a button in a foreign windows' title bar, much like Teamviewer does with the Quickconnect feature, or like Chrome has one in the top-right for switching users.
I know this is a repeat of How is Teamviewers Quickconnect button accomplished?
I'm just wondering if it would be possible to get a working example or a link to an open-source program that implements this. The answers given there are rather advanced for me. As in, how am I supposed to "hook" and "intercept" WM_NCPAINT message and so on.
This is the most simple example i can develop:
You need Visual Studio, add 2 project to the solution:
first project (HookDLL) is a dll project, second (Running app) is a win32 console project
in main.cpp (at project Running app) add this:
__declspec(dllimport) void RunHook();
int _tmain(int argc, _TCHAR* argv[])
{
RunHook();
return 0;
}
in dllmain button.cpp (at HookDLL project) add this code:
#include <Windows.h>
#include <stdio.h>
HINSTANCE hinstDLL;
HHOOK hhook_wndproc;
HWND b = NULL;
HBRUSH blue_brush = NULL, yellow_brush, red_brush;
int button_status = 0;
LRESULT CALLBACK DefaultWindowProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
if(!blue_brush)
{
blue_brush = CreateSolidBrush(RGB(0, 0, 255));
yellow_brush = CreateSolidBrush(RGB(255, 255, 0));
red_brush = CreateSolidBrush(RGB(255, 0, 0));
}
break;
case WM_PAINT:
{
HBRUSH b;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
switch(button_status)
{
case 0:
b = blue_brush;
break;
case 1:
b = yellow_brush;
break;
default:
b = red_brush;
}
FillRect(hdc, &ps.rcPaint, b);
EndPaint(hwnd, &ps);
}
return 0;
case WM_MOUSEMOVE:
if(button_status == 0)
{
SetTimer(hwnd, 1, 100, NULL);
button_status = 1;
InvalidateRect(hwnd, NULL, false);
}
return 0;
case WM_TIMER:
{
POINT pt;
GetCursorPos(&pt);
if(button_status == 1 && WindowFromPoint(pt) != hwnd)
{
KillTimer(hwnd, 1);
button_status = 0;
InvalidateRect(hwnd, NULL, false);
}
}
return 0;
case WM_MOUSELEAVE:
button_status = 0;
InvalidateRect(hwnd, NULL, false);
return 0;
case WM_LBUTTONDOWN:
button_status = 2;
InvalidateRect(hwnd, NULL, false);
return 0;
case WM_LBUTTONUP:
if(button_status == 2) MessageBox(GetParent(hwnd), "teamviewer like button clicked", "Message", MB_OK);
button_status = 1;
InvalidateRect(hwnd, NULL, false);
return 0;
}
return DefWindowProc(hwnd, Msg, wParam, lParam);
}
void InitButton(HWND parent, int xPos, int yPos)
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = DefaultWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstDLL;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "DEFAULT_CLASS";
RegisterClass(&wc);
b = CreateWindowEx(WS_EX_TOOLWINDOW, "DEFAULT_CLASS", NULL, WS_BORDER | WS_POPUP | WS_VISIBLE, xPos, yPos, 20, 20, parent, NULL, hinstDLL, NULL);
}
LRESULT WINAPI HookCallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode >= 0 && lParam != 0)
{
CWPRETSTRUCT *msg = (CWPRETSTRUCT*)lParam;
if(!IsWindow(msg->hwnd) || (GetWindowLong(msg->hwnd, GWL_STYLE) & WS_CHILD) != 0) return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
switch(msg->message)
{
case WM_SHOWWINDOW:
if(!b && msg->wParam != 0)
{
b = (HWND)1;// see NOTES 5
RECT a;
GetWindowRect(msg->hwnd, &a);
InitButton(msg->hwnd, a.right - 150, a.top);
}
break;
case WM_SIZE:
if(GetParent(b) == msg->hwnd)
{
RECT a;
GetWindowRect(msg->hwnd, &a);
SetWindowPos(b, 0, a.right - 150, a.top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
break;
case WM_SIZING:
case WM_MOVING:
if(GetParent(b) == msg->hwnd)
{
RECT* lprc = (LPRECT) msg->lParam;
SetWindowPos(b, 0, lprc->right - 150, lprc->top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
}
}
return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
}
__declspec(dllexport) void RunHook()
{
hhook_wndproc = SetWindowsHookEx(WH_CALLWNDPROCRET, HookCallWndProc, hinstDLL, 0);
char aux[10];
gets_s(aux);
UnhookWindowsHookEx(hhook_wndproc);
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hinstDLL = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Now, make dll project depedent of running app project in project->project dependencies:
NOTES:
1) i dont use NC paint code, because not always works, if windows buffer non client region, erases customized NC paint buttons
2) in 64 bits enviroment, you need to run a 32 bits hook for 32 bits apps, and other hook for 64 bits apps
3) YOU CAN NOT DEBUG YOUR HOOK WHEN IS CONNECTED TO ANOTHER PROCCESS, i suggest you debug it with a windows in your app and thread, and test it late in another proccess when is working
4) i use a button like approach for simplicity
5) this line
b = (HWND)1;
I use it for "solve" a multi thread problem, i suggest you make better code (syncronization) this case
HOW THIS WORKS:
run the app
when it start install a hook
open any other app (same 32/64 bits, see NOTE 2)
you must see a blue button at left side of title bar
click it and see a message box
for finish hook: just press ENTER at console window
CODE FLOW:
Running app just calls RunHook() procedure in dll, and dll do the work
RunHook() in dll starts a hook HookCallWndProc (global)
HookCallWndProc captures required message and creates the window button using InitButton() procedure
DefaultWindowProc handles button message

Listview Item return after delete pure win32

My problem with Listview on pure Win32 program
When i delete the item then click at it location before delete the item return.
So the Listview Can't delete any items actually
This the program and source :
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#define BTN_DELETE 123
static HWND resList=NULL;
LVITEM LvItem;
LVCOLUMN lvc;
HINSTANCE MainInstance;
HWND Test_FORM;
LRESULT CALLBACK Test_FORM_WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
resList= CreateWindow(WC_LISTVIEW,"", WS_CHILD | WS_BORDER | WS_VISIBLE | LVS_REPORT,0,0,700,420,hwnd,(HMENU)666,MainInstance,NULL);
CreateWindow("button", "Delete",WS_VISIBLE | WS_CHILD ,20, 430, 150, 30,hwnd, (HMENU) BTN_DELETE, MainInstance, NULL);
memset(&lvc,0,sizeof(lvc));
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM ;
lvc.iSubItem = 0;
lvc.pszText = "Items";
lvc.cx = 200;
SendMessage(resList,LVM_INSERTCOLUMN,0,(LPARAM)&lvc);
memset(&LvItem,0,sizeof(LvItem));
LvItem.mask=LVIF_TEXT;
LvItem.cchTextMax = 256;
LvItem.iItem=0;
LvItem.iSubItem=0;
LvItem.pszText="Item 0";
SendMessage(resList,LVM_INSERTITEM,0,(LPARAM)&LvItem);
}
break;
case WM_COMMAND: { if(HIWORD(wParam) == BN_CLICKED) { switch(LOWORD(wParam)) { case BTN_DELETE: { ListView_DeleteAllItems(resList); } break; } } } break;
case WM_CLOSE: {DestroyWindow(hwnd); PostQuitMessage(0); } break;
}
return DefWindowProc(hwnd,msg,wParam,lParam);
}
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
MSG Msg;
MainInstance=hInstance;
WNDCLASS ResClass;
ResClass.cbClsExtra = 0;
ResClass.cbWndExtra = 0;
ResClass.hbrBackground = CreateSolidBrush(RGB(45,45,45));
ResClass.hCursor = LoadCursor(NULL,IDC_ARROW);
ResClass.hIcon = NULL;
ResClass.lpszMenuName = NULL;
ResClass.style = 0;
ResClass.hInstance = NULL;
ResClass.lpfnWndProc = Test_FORM_WndProc;
ResClass.lpszClassName = "RES_CL";
RegisterClass(&ResClass);
Test_FORM = CreateWindow("RES_CL","Test",WS_DLGFRAME | WS_SYSMENU | WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,700,500,NULL,0,NULL,NULL);
Test_FORM_WndProc(Test_FORM,WM_CREATE,NULL,NULL);
if(Test_FORM == NULL){return 1;}
while(GetMessage(&Msg,NULL,0,0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
It looks like your main problem is this line of code:
Test_FORM_WndProc(Test_FORM,WM_CREATE,NULL,NULL);
This should simply not be there. When you call the window proc with WM_CREATE, a second list view is created. Simply remove this line of code from your program.
Some other comments:
Your handling of WM_CLOSE is odd. You don't need to handle that message since the default handler will call DestroyWindow. And you should not call PostQuitMessage from there. Instead call it from WM_DESTROY. So, replace the WM_CLOSE clause with this:
case WM_DESTROY:
PostQuitMessage(0);
return 0;
It's probably cleaner to zero-initialize a struct before populating the handful of members that need values. For example:
WNDCLASS ResClass = {0};
ResClass.hbrBackground = CreateSolidBrush(RGB(45,45,45));
ResClass.hCursor = LoadCursor(NULL,IDC_ARROW);
ResClass.lpfnWndProc = Test_FORM_WndProc;
ResClass.lpszClassName = "RES_CL";
You use this approach in the WM_CREATE handler too, but the syntax above is perhaps a little more idiomatic than memset.
Finally, I'd prefer to see your WM_CREATE and WM_COMMAND handlers use return rather than letting DefWindowProc be called.

CreateWindowEx, GetLastError, Windows 7, and XP oddities and odd behavior

The following code works on Window 7, but when I run it on Windows XP, it fails with an error message returned by windows: "The system can not find the file specified".
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <string>
const char g_szClassName[] = "myWindowClass";
const char title[] = "Window Title\0";
COLORREF WinColor;
HFONT defaultFont;
NONCLIENTMETRICSA Metrics;
DWORD dwVersion;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
HBRUSH hBrushColor;
bool LastError = false;
bool W32Error (const char * Msgtext);
// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NCCREATE:
{
Metrics.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &Metrics, 0);
defaultFont = (HFONT) CreateFontIndirect (& Metrics.lfMessageFont);
return TRUE;
}
break;
case WM_CTLCOLORSTATIC: {
dwVersion = GetVersion();
dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
if ( (IsAppThemed()) && (dwMajorVersion < 6) ) {
WinColor = GetSysColor(COLOR_WINDOW);
SetBkColor((HDC)wParam, WinColor);
return (LRESULT)hBrushColor;
}
}
break;
case WM_CREATE: {
HWND hButton = CreateWindowEx(
0,
"BUTTON", "Button",
WS_TABSTOP |
WS_VISIBLE |
WS_CHILD |
BS_NOTIFY |
BS_PUSHBUTTON,
10, 10, 96, 32, hwnd,
(HMENU)50,
GetModuleHandle(NULL),
NULL);
if (W32Error ("Button Creation Failed\nReason:")) exit (1);
return TRUE;
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// Initialize common controls.
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES |
ICC_COOL_CLASSES |
ICC_INTERNET_CLASSES|
ICC_LINK_CLASS |
ICC_STANDARD_CLASSES|
ICC_PROGRESS_CLASS |
ICC_USEREX_CLASSES;
InitCommonControlsEx(&icc);
//Step 1: Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// see if something is going on before the window registration takes place....
if (W32Error ("Previous Check for Error?\nReason:")) exit (1);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
if (W32Error ("Window Registration Failed\nReason:")) exit (1);
// Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
if (W32Error ("Window Creation Failed\nReason:")) exit (1);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
SendMessageA(hwnd, WM_SETFONT, WPARAM (defaultFont), TRUE);
SendMessageA(hwnd, WM_SETTEXT, WPARAM(NULL) , LPARAM (title));
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Step 3: The Message Loop
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
bool W32Error (const char * Msgtext)
{
LPTSTR errorText = NULL;
DWORD dwLastError = GetLastError();
if (!dwLastError) {
LastError = false;
return LastError;
}
// use system message tables to retrieve error text
// allocate buffer on local heap for error text
// Important! will fail otherwise, since we're not (and CANNOT) pass insertion parameters
FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
dwLastError,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorText, // output
0, // minimum size for output buffer
0); // arguments - see note
if ( NULL != errorText ) {
std::string Message;
Message += Msgtext;
Message += "\n";
Message += errorText;
MessageBoxA(NULL, (LPCSTR)Message.c_str(), "An Internal Error Occurred", MB_OK);
LocalFree(errorText);
errorText = NULL;
LastError = true;
} else {
LastError = false;
}
return LastError;
}
As an additional, I have a resource.rc and resource.h file linked in with winres that complies in a manifest.xml that provides a controls version 6 specification.
Ok, so here is the deal, This runs on Windows 7 without a hitch. And it runs on XP, unless I put in the line if (W32Error ("Window Registration Failed\nReason:")) exit (1);
(basically, it calls my error checking routine which includes GetLastError())
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
if (W32Error ("Window Registration Failed\nReason:")) exit (1);
When I include that line, I get an error "The system can not find the file specified." The return value back from RegisterClassEx does not go into the if block, so RegisterClassEx returns Ok.
This only happens on Windows XP, it works just fine on Windows 7. If I don't include this line, it works as if nothing is wrong.
The point of this sample is to get themes to work right on both XP and Windows 7. (not an easy task.)
Why is RegisterClassEx returning Ok, but generates an error for GetLastError on Windows XP and not on Windows 7?
Also, what file is it looking for anyway? I have seen other Google results say It has to do with a messed up message loop or window procedure, but that's not the case here.
General mistake... Due to MSDN you may check GetLastError only when RegisterClassEx "return value is zero". When RegisterClassEx or CreateWindowEx or any other functions are succeeded, GetLastError may return garbage, there is no guarantee that GetLastError will returns zero (if different behavior does not described in Return value section).

Resources