Why does my subclassed button procedure mess up my program? - c++

I am relatively new to C++. I have a question why the program posted below behaves the way it does when the constant DEMOMETHOD is set to 1 in main.cpp and button_class.h.
The program demonstrates subclassing a few buttons created by a single c++ class - there are 4 instances of the class (4 buttons) and two (labeled button1 and button2) are subclassed.
I'm hoping to learn:
why return 0; is needed in the handler of the wm_lbuttondown message in button_class.cpp for multiinstance subclassing to work correctly (set DEMOMETHOD = 0 for correct program operation).
why the other subclassed buttons and the non subclassed buttons work incorrectly after either one of the two subclassed buttons are clicked. Example: the quit button doesn't work if you first click on Button 2 (or 1) when DEMOMETHOD = 1.
I posted compilable (gcc compiler, Windows 7) code to make it easier to observe the behavior I saw and to, hopefully, help other new c++ programmers that stumble upon this post.
Thanks in advance.
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1 or 0 in button_class.h
#define DEMOMETHOD 1 //SET TO 1 to make program break, set to 0 to make program work (do this in the button_class.h file too)
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h> //for _T
//windows 7 flags
#define WINVER 0x0601
#define _WIN32_WINNT 0x0601 // Windows 7 https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
#include <iostream>
#include "button_class.h"//my button class
using namespace std;
//forward declares
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
std::string str(long data);
//declares-pre
#define IDC_CHECKBOX101 101
#define IDC_BUTTON1 102
#define IDC_BUTTON2 103
#define IDC_STATIC 104
//globals, create 4 buttons using our button class, 2 will be subclassed
Button bt,cbt,bt1,bt2;//button and checkbox button
HWND hDlg,hstatic; //main window and static label
TCHAR szClassName[ ] = _T("MyWindowsApp");
HINSTANCE hInstance;
//main window, starts the program
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgument,int nCmdShow)
{
HWND hwnd;
MSG messages;
std::string tmp;
hInstance = hInst;//save instance to global variable
//create and register our main window class
WNDCLASSEX wincl; // Data structure for the windowclass
wincl.hInstance = hInst;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WndProc; // This function is called by windows
wincl.style = CS_DBLCLKS; // Catch double-clicks
wincl.cbSize = sizeof (WNDCLASSEX);
// Use default icon and mouse-pointer
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; // No menu
wincl.cbClsExtra = 0; // No extra bytes after the window class
wincl.cbWndExtra = 0; // structure or the window instance
wincl.hbrBackground = (HBRUSH) (COLOR_3DFACE+1);
// Register the window class, and if it fails quit the program
if (!RegisterClassEx (&wincl)) {
MessageBox(NULL,"Failed to register WNDCLASSEX.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
//class is registered, now create main window
hwnd = CreateWindowEx (
0,//extended style
szClassName,
_T("Demo Multi Instance of Button Class"),
WS_OVERLAPPEDWINDOW, // styles - default window
CW_USEDEFAULT, // Windows decides the position
CW_USEDEFAULT, // where the window ends up on the screen
777, // The program's width (hardcoded for this demo)
411, // and height in pixels (hardcoded for this demo)
HWND_DESKTOP, // The window is a child-window to desktop
NULL, // No menu
hInst, // Program Instance handler
NULL // No Window Creation data
);
if (!hwnd) {
MessageBox(NULL,"Failed to create main window.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
hDlg = hwnd;//main window
// Make the window visible on the screen
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd);
while (GetMessage (&messages, NULL, 0, 0)) {
// Translate virtual-key messages into character messages
TranslateMessage(&messages);
// Send message to WindowProcedure (WndProc)
DispatchMessage(&messages);
}
// The program return-value is 0 - The value that PostQuitMessage() gave
return messages.wParam;
}
//handle messages intended for main window
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
std::string tmp;
LRESULT checked;
DWORD dwstyles,dwstylesex;
switch (message) { //wndproc handle the messages
case WM_CREATE://create controls
//always on top checkbox button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX;
dwstylesex = 0;
cbt.t.hbutton = cbt.Create(hwnd,cbt,dwstylesex,dwstyles,277,8,122,26,(long) IDC_CHECKBOX101,hInstance,"Always on top",false);
//quit button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt.t.hbutton = bt.Create(hwnd,bt,dwstylesex,dwstyles,691,7,65,30,(long) IDCANCEL,hInstance,"&Quit",false);
//button1 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt1.t.hbutton = bt1.Create(hwnd,bt1,dwstylesex,dwstyles,50,50,65,30,(long) IDC_BUTTON1,hInstance,"Button1",true);
if (bt1.t.hbutton == NULL) {
MessageBox(NULL,"button1 failed","error",0);
}
//button2 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt2.t.hbutton = bt2.Create(hwnd,bt2,dwstylesex,dwstyles,50,90,65,30,(long) IDC_BUTTON2,hInstance,"Button2",true);
if (bt2.t.hbutton == NULL) {
MessageBox(NULL,"button2 failed","error",0);
}
//create a statio control and display information about DEMOMETHOD
hstatic = CreateWindowEx(0,"Static",
#if (DEMOMETHOD == 0)
"Program should work correctly since DEMOMETHOD is set to 0",
#else
"Program should mess up since DEMOMETHOD is set to 1",
#endif
WS_CHILD | WS_VISIBLE,
50, 140,
450, 30,
hwnd,
(HMENU) IDC_STATIC,
hInstance,
NULL);
break;
case WM_SIZE:
break;
case WM_DESTROY: {
PostQuitMessage(0); // send a WM_QUIT to the message queue
break;
}
case WM_CLOSE: {
DestroyWindow(hwnd);
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {// LOWORD ctrlid. The HIWORD specifies the notification code.
case IDC_CHECKBOX101: { // always on top
//leftover functionality from another app
if (HIWORD(wParam) == BN_CLICKED) {
checked = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
if (checked) { // Force the program to stay always on top
SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} else { // else no more topmost program state
SetWindowPos(hDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}
break;
}
case IDCANCEL: {//quit button
if (HIWORD(wParam) == BN_CLICKED) {
PostMessage(hDlg,WM_CLOSE,0,0);
return 0;
}
break;
}//idcancel
}// loword switch
break;
}//wm_command
default: // for messages that we don't deal with
return DefWindowProc (hwnd, message, wParam, lParam);
} //for switch(msg)
return 0;
}//end wndproc function
std::string str(long data)//convert non decimnal numeric to string
{
try {
return std::to_string((long) data);
} catch(int x) {
return std::to_string((long) data);
}
return "error in str function";
}
button_class.h
#ifndef BUTTONCLASSGUARD
#define BUTTONCLASSGUARD
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1/0 in main.cpp
#define DEMOMETHOD 1 //SET TO 1 to make program break (do this in main.cpp file too) set to 0 to make program work
#include <iostream>
//windows 7 flags
#define WINVER 0x0601 //windows 7
#define _WIN32_WINNT 0x0601 // Windows 7 https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
//#include <windowsx.h> //for get_x_lparam
#include <commctrl.h> //for subclass safer also make sure linker calls libcomctl32.a (gcc compiler)
class Button //define the Button class
{
private:
public:
struct T { //seems like a more convenient way to allow sets/gets for a class that only I will use
long x;
long xx;
long y;
long yy;
HWND hparent;
HINSTANCE hinstance;
long ctrlid;
HWND hbutton;
} t;
Button() //constructor
{
}
~Button() //destructor
{
}
//forward declares
HWND Create(HWND hparent, Button &tbt,DWORD dwstylesex,DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF);
//next is our callback function for safe subclassing using setwindowsubclass
// https://learn.microsoft.com/en-us/windows/win32/controls/subclassing-overview
//stackoverflow article said static is needed and my testing confirmed this
static LRESULT CALLBACK OnEvent_Button(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
};//Button class end
#endif//buttonclassguard
button_class.cpp
//custom button class
#include "button_class.h"
extern std::string str(long data);//for demo I use extern keyword to give access to str function, normally that function is in a utilities class
HWND Button::Create(HWND hparent, Button &tbt, DWORD dwstylesex, DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF)
{
HWND result;
HGDIOBJ hFont = GetStockObject(ANSI_VAR_FONT);
bool tbool;
static long InstCount = 0;
std::string tmp;
tbt.t.hinstance = hinst;
tbt.t.x = x;
tbt.t.xx = xx;
tbt.t.y = y;
tbt.t.yy = yy;
tbt.t.hparent = hparent;
tbt.t.ctrlid = ctrlid;
//now create a button
tbt.t.hbutton = (HWND) NULL;
if (dwstyles == 0) {
dwstyles = WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | WS_CLIPSIBLINGS | BS_NOTIFY;
}
if (Caption == "") {
Caption = "&Ok";
}
result = CreateWindowEx(dwstylesex,"Button",
Caption.c_str(),
dwstyles,
tbt.t.x,tbt.t.y,
tbt.t.xx,tbt.t.yy,
hparent,
(HMENU) ctrlid,
hinst,
NULL);
if (!result) {
MessageBox(NULL, "Button creation Failed.", "Error", MB_OK | MB_ICONERROR);
return result;
}
if (hFont > 0) {
SendMessage(result, WM_SETFONT,(WPARAM) hFont, 0);
}
if (SubClassTF == true) {
//subclass if here
InstCount++;//instantiation count
//now subclass using safer method https://devblogs.microsoft.com/oldnewthing/20110506-00/?p=10723 (raymond chen) (safer subclass)
//the 'this' keyword seems to change its stripes depending on whether onevent_button is static or not
// if static it seems to track the instance of the class &tbt)
// if not static (remove static keyword from .h forward declare) it seems to point to the class not an instance of the class
//static is correct for this exercise
tbool = SetWindowSubclass(
result,//window being subclassed
reinterpret_cast<SUBCLASSPROC>(this->OnEvent_Button), //&tbt.OnEvent_Button), //&OnEvent_Button), //or &tbt.onevent_button worked too sort of onevent_button can be outside the class
InstCount, //id of this subclass, my choice
reinterpret_cast<DWORD_PTR>(this) //&tbt)//was: (&tbt) ken suspects use of this is ng since, for mult instances this points to the class not the instance of the class imho semi tested
);//returns bool with result
if (tbool == false) { //subclass failed if false
tmp = "subclass failed for " + Caption;
MessageBox(NULL,tmp.c_str(),"error",0);
}
} //subclass
tbt.t.hbutton = result;
return result;//return hwnd to caller
}//::create funtion end
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button *tp = reinterpret_cast<Button *>(dwRefData);//c++ way, works
std::string tmp;
switch(uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long) dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL,tmp.c_str(),"clicked",0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL,"return 0 next - should work when you click on another button next","message",0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL,"break is next - doesn't work if you click on another button next","message",0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
reinterpret_cast<UINT_PTR>(uIdSubclass) //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://learn.microsoft.com/en-us/windows/win32/controls/subclassing-overview
break;//return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return DefSubclassProc(hwnd, uMsg, wParam, lParam);//need this if you plan to use break for any case item
} //onevent_button

In fact, these two problems are caused by the same problem. When you use return 0, you will return directly without calling the DefSubclassProc function outside of switch.
If you use break, you will jump out switch, call the DefSubclassProc function again. This is why the same button is triggered continuously when the DEMOMETHOD value is 1.
So the solution is very simple, you just need to modify the following code:
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button* tp = reinterpret_cast<Button*>(dwRefData);//c++ way, works
std::string tmp;
switch (uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long)dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL, tmp.c_str(), "clicked", 0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL, "return 0 next - should work when you click on another button next", "message", 0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL, "break is next - doesn't work if you click on another button next", "message", 0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
uIdSubclass //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://learn.microsoft.com/en-us/windows/win32/controls/subclassing-overview
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return 0; //need this if you plan to use break for any case item
} //onevent_button

Related

How to handle VK_MENU (alt) keypresses properly using WinAPI?

I am writing a windows application using the WinAPI (win32) on C++, but can't figure out what's happening with the ALT key. I am handling both WM_KEYDOWN and WM_SYSKEYDOWN, so all keypresses should be coming through. However, while every ALT keyup is detected, every second ALT keydown is being skipped.
Do I have to generate my own keydown event whenever I get a second WM_SYSKEYUP in a row, or am I doing something wrong with my message handling?
Details
Tap these keys: ALT ALT ALT ALT ALT (alt five times)
Expected message sequence: DUDUDUDUDU (down then up, repeated five times)
Observed message sequence: DUUDUUDU (every second down is missing)
Tap these keys: ALT C ALT C ALT C ALT C ALT C (add other keypresses in between - release ALT before tapping C)
Expected VK_MENU message sequence: DUDUDUDUDU (down then up, repeated five times)
Observed VK_MENU message sequence: DUDUDUDUDU (as expected!)
It seems that adding other keypresses (or even mouse events) cancels whatever Windows was doing and forces it to send the app all the ALT keypresses.
Example code (Visual c++17):
Creates a window which adds 'D' to the window title when ALT is pressed and 'U' when ALT is released
#ifndef UNICODE
#define UNICODE
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define STRICT
#include <Windows.h>
#include <optional>
#include <malloc.h>
WCHAR* title;
int titleLen = 0;
const int maxTitleLen = 64;
void updateWindowTitle(WCHAR c, HWND hwnd)
{
if (titleLen >= maxTitleLen) return;
title[titleLen++] = c;
title[titleLen] = 0;
SetWindowTextW(hwnd, title);
}
LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_SYSKEYDOWN: //needed for keys like alt (VK_MENU)
case WM_KEYDOWN:
//process_keydown_event(wParam);
if (!(lParam & 0x40000000)) { //ignore autorepeat
if (wParam == VK_MENU) updateWindowTitle(L'D', hwnd);
}
break;
case WM_SYSKEYUP: //needed for keys like alt (VK_MENU)
case WM_KEYUP:
//process_keyup_event(wParam);
if (wParam == VK_MENU) updateWindowTitle(L'U', hwnd);
break;
//etc...
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// *********** STANDARD BORING SETUP BELOW ************
std::optional<int> processMessages() noexcept
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
// Return argument to PostQuitMessage as we are quitting
return msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return {}; //return empty optional when not quitting
}
int CALLBACK WinMain(
HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
int nShowCmd)
{
// Make global title point to valid memory
title = static_cast<WCHAR*>(malloc(sizeof(WCHAR) * maxTitleLen));
// Register window class
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.hInstance = hInst;
wc.hIcon = nullptr;
wc.hIconSm = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = L"my window class name";
RegisterClassEx(&wc);
// Create window 800x500
HWND hwnd = CreateWindowW(
wc.lpszClassName, L"",
WS_VISIBLE | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 500,
nullptr, nullptr, hInst, nullptr
);
// (check HWND is not NULL)
//main loop
while (true)
{
if (const auto errorcode = processMessages())
{
return errorcode.value();
}
}
}
I can reproduce the problem through code, and then I capture the message of the window through spy++.
We can see that when the ALT key is pressed for the first time, the WM_SYSKEYDOWN and WM_SETTEXT messages are successfully triggered:
When the ALT key is pressed the second time, it actually triggers the WM_SYSKEYDOWN message, but after a series of message sending(WM_CAPTURECHANGED,WM_MENUSELECT...).This resulted in the message not being processed in the main window, but sent to hmenu:00000000, so the main window did not process the WM_SYSKEYDOWN message.
You can handle it by processing the WM_SYSCOMMAND message:
case WM_SYSCOMMAND:
if (wParam == SC_KEYMENU)
{
return 0;
}
else
return DefWindowProc(hwnd, msg, wParam, lParam);
break;
It works for me.

Effect of MoveWindow in EnumChildWindows over listview inside the Dialog Box: Why ListView Header is not correctly scrolling

I have a listview control (lvc) and it is inside a DialogBox(dbx) and that dbx also has a vertical scroll bar.
Whenever the scrollbar is scrolled EnumChildWindows is called to enumerate all the child window of the dbx. The callback function contains a MoveWindow function that would move that lvc. lvc is scrolling fine but not its column headers, they are not moving with the list view.
If i comment out the MoveWindow function inside the callback function then nothing changes. ( Off-course lvc won't move! ) that means EnumChildWindow has got no problem, but MoveWindow inside the callback function is causing problem and i am sure about this because calling MoveWindow function from outside the callback function works correctly ( because in this example there is only one control, i.e. lvc, so i don't need to enumerate all the child window ).
here is the Code:
main.cpp
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h>
#define _WIN32_IE 0x0700
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <vector>
#include "res.h"
#define btn 0
#include <iostream>
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK diaproc(HWND hwmd, UINT msg, WPARAM wp, LPARAM lp);
BOOL CALLBACK edc(HWND hwmd,LPARAM lp);
HINSTANCE gi;
int iPrevVscroll=0;
/* Make the class name into a global variable */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
gi = wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS ; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; /* No menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szClassName, /* Classname */
_T("Code::Blocks Template Windows App"), /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
544, /* The programs width */
375, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
CreateWindow(WC_BUTTON, "CLICK", WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, 10, 10, 80, 30, hwnd, (HMENU)btn, gi, NULL );
break;
case WM_COMMAND:{
if( LOWORD(wParam) == btn && HIWORD(wParam) == BN_CLICKED ) DialogBox(gi, MAKEINTRESOURCE(dia), hwnd,(DLGPROC)diaproc);
DWORD err = GetLastError();
std::cout<<err<<std::endl<<dia;
}
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
BOOL CALLBACK diaproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lp)
{
static HWND lv_hwnd;
static int sci;
switch(msg)
{
case WM_INITDIALOG:
{
INITCOMMONCONTROLSEX is;
is.dwSize = sizeof(INITCOMMONCONTROLSEX);
is.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&is);
int col_fmt[5] = { LVCFMT_CENTER, LVCFMT_LEFT, LVCFMT_CENTER, LVCFMT_CENTER, LVCFMT_CENTER };
int col_wid[5] = { 30, 90, 50, 30, 70 };
std::vector<TCHAR*> col_nam(5);
col_nam[0] = _T("S.No"); col_nam[1] = _T("Description"); col_nam[2] = _T("HSN"); col_nam[3] = _T("QTY"); col_nam[4] = _T("Rate");
lv_hwnd = CreateWindow(
WC_LISTVIEW,
_T(""),
WS_CHILD | LVS_REPORT | LVS_EDITLABELS | WS_VISIBLE,
10, 0, 300, 200,
hwnd,
NULL,
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL
);
ListView_SetExtendedListViewStyle(lv_hwnd, LVS_EX_FLATSB | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_LABELTIP );
LVCOLUMN lvc;
lvc.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_TEXT;
for(int i =0; i < 5; i++)
{
lvc.fmt = col_fmt[i];
lvc.cx = col_wid[i];
lvc.pszText = col_nam[i];
lvc.iSubItem = i;
ListView_InsertColumn(lv_hwnd, i, &lvc);
}
SetScrollRange(hwnd, SB_VERT, 0, 225, TRUE);
SetScrollPos(hwnd, SB_VERT, 0, TRUE);
} return FALSE;
case WM_VSCROLL:
{
RECT rc; GetWindowRect(lv_hwnd, &rc);
POINT pt1 = {rc.left, rc.top}; ScreenToClient(hwnd, &pt1);
POINT pt2 = {rc.right, rc.bottom}; ScreenToClient(hwnd, &pt2);
std::cout<<"rc.top : "<< rc.top<<"\nrc.bottom: "<< rc.bottom <<"\nrc.right : "<<rc.right<<"\nrc.left : "<<rc.left<<"\n\n";
std::cout<<"pt1.y : "<< pt1.y<<"\npt2.y: "<< pt2.y<<"\npt2.x : "<<pt2.x<<"\npt1.x : "<<pt1.x<<"\n\n\n";
switch(LOWORD(wParam))
{
case SB_PAGEDOWN:
case SB_LINEDOWN:
sci += 10; break;
case SB_PAGEUP:
case SB_LINEUP:
sci -= 10; break;
case SB_THUMBTRACK:
sci = HIWORD(wParam); break;
};
sci = sci < 0 ? 0 : sci > 225 ? 225 : sci;
SetScrollPos(hwnd, SB_VERT, sci, FALSE);
//MoveWindow(lv_hwnd, pt1.x, pt1.y - sci + iPrevVscroll, pt2.x - pt1.x, pt2.y - pt1.y, TRUE);
EnumChildWindows(hwnd, edc, (LPARAM)sci);
}; return TRUE;
case WM_COMMAND:
if(LOWORD(wParam) == IDCANCEL) EndDialog(hwnd, wParam); return TRUE;
default: return FALSE;
}
}
BOOL CALLBACK edc(HWND hwnd, LPARAM lp)
{
long s = (long) lp;
RECT rc; GetWindowRect(hwnd, &rc);
POINT pt1 = {rc.left, rc.top}; ScreenToClient(hwnd, &pt1);
POINT pt2 = {rc.right, rc.bottom}; ScreenToClient(hwnd, &pt2);
MoveWindow(hwnd, pt1.x, pt1.y + s - iPrevVscroll, pt2.x - pt1.x, pt2.y - pt1.y, TRUE);
}
res.h
#define lv 1
#define dia 2
res.rc
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "res.h"
dia DIALOGEX 0,0,500,300
CAPTION "New Invoice"
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU | WS_THICKFRAME | WS_VSCROLL
FONT 8, "Ms Shell Dlg"
{
}
in main.cpp you find the MoveWindow and other related functions at the bottom.
The following Images are useful.
also that the logic of scrolling in both MoveWindow is different, again, for the purpose of illustration.
Initially i was working on a project with many controls when i encountered this problem. I analysed this separately and found out what i have written above. Although I bypassed this problem through adopting a different method to scroll down all the controls(the one which do not includes calling MoveWindow from inside EnumChildWindows ), but i am curious to know the reason and the solution of this problem.
Thank you for your time on this long post. Any suggestions or improvement would also be amazing!
From the remarks section of EnumChildWindows() reference:
If a child window has created child windows of its own,
EnumChildWindows enumerates those windows as well.
So what you are doing here is scrolling the listview control and then also scroll the header control separately. The result is that the header control moves relative to the listview control as seen in your 2nd screenshot.
Instead you should only move immediate children of the dialog box, because grand children will move automatically with their parents.
Possible solutions:
Check parent/child relationship in the EnumChildWindows() callback (e. g. by calling GetParent() on the child and compare it with your dialog handle).
Instead of calling EnumChildWindows() and MoveWindow(), call ScrollWindowEx() with SW_SCROLLCHILDREN. This is the easiest way of implementing scrolling, so I would prefer this solution.

c++ Win32 Api GetMessage closing program inside thread

I'm building a interface using the win32 api and I wanted to manage all the suff in another class with a thread to keep doing work in the main.
I have this code:
WindowManager.h
class UIManager::WindowManager {
private:
//Class data
HINSTANCE hInstance;
WNDCLASSEX winClass; /* Data structure for the windowclass */
MSG msg;
HWND hwnd;
//Window Data
LPCSTR wiName;
int startX = 250;
int startY = 150;
int endX = 544;
int endY = 375;
//Private managers
void makeWindow();
public:
WindowManager(HINSTANCE & hInstance, std::string wiName = "Window Name");
void show();
};
WindowManager.c (I use bind because I have the class inside a namespace and that is the only way I found that thread allows me to compile instead of throwing an error)
#include "WindowManager.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//Private
//
void UIManager::WindowManager::makeWindow()
{
int ms = GetMessage(&msg, 0, 0, 0); //I do this to see if it gets to
while (GetMessage(&msg, NULL, 0, 0)) { //this part, but never happens
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//Public
//
UIManager::WindowManager::WindowManager(HINSTANCE & hInstance, std::string wiName)
{
this->wiName = wiName.c_str();
this->hInstance = hInstance;
winClass.cbSize = sizeof(winClass);
winClass.hInstance = hInstance;
winClass.lpszClassName = this->wiName;
winClass.lpfnWndProc = WndProc; //Execution callback
//Load default editable ellements
winClass.hCursor = LoadCursor(0, IDC_ARROW); /*Default*/
winClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); /*Default*/ //Alt+Tab Dialog
winClass.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
winClass.lpszMenuName = NULL; /* No menu */
RegisterClassEx(&winClass);
//Create Window
hwnd = CreateWindowEx(
0,
this->wiName, /* Title Class */
this->wiName, /* Title Text */
WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
startX, /* X Start */
startY, /* Y Start */
endX, /* The programs width */
endY, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hInstance, /* Program Instance handler */
NULL
);
SetWindowPos(hwnd, 0, 0, 0, 20, 20, 0);
}
void UIManager::WindowManager::show()
{
std::thread listener(std::bind(&WindowManager::makeWindow, this));
listener.detach();
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{ //This part is executed
//...blablabla
break;
}
case WM_COMMAND:
{//This part is never executed
//...blablabla
break;
}
case WM_DESTROY:
{//This part is never executed
//...blabla
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
The window execute and show properly and even execute WM_CREATE, but when the "GetMessage" is executed it instantly end the program without throwing any error or something like that.
I added an int with the value of the GetMessage to see if it gets to the "while" in debug mode but it happens.
Is there a way to see the error that may be throwing or prevent it from closing the program? Or is it that I'm doing wrong calling "GetMessage" from inside a thread?
your code has several problems:
You create window in one thread, but try to run meesage loop in another thread. GetMessage only handles messages of windows belonging to the calling thread.
You never wait for background thread processing messages and instead detach from it and continue execution in main thread probably ending application.
You don't inspect the value returned by GetMessage and use it as boolean.
If you want to make background ui thread you need to move all window creation code there and call join on thread. And message loop should look like this:
::MSG msg;
for(;;)
{
auto const res{::GetMessage(&msg, NULL, 0, 0))};
if(0 < res)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else if(0 == res)
{ // PostQuitMessage was called...
break;
}
else
{ // an error occurred...
break;
}
}

WinMain/Win32 Window not displaying, but shows up in Processes tab of Task Manager

I'm a newbie to c++, and I'm stuck on displaying the window. I'm not getting any errors, but my Window is not displaying on the desktop. When I open the task manager, it appears under the 'Proccesses' tab. I haven't been finding any solutions to this problem, so any help is appreciated. Thanks! :)
**Note: I'm using Microsoft Visual Studio 2012
**Note: Not exactly a newbie to c++, but more to creating a win32 application
#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
static TCHAR WindowClass[] = L"Window";
LRESULT CALLBACK WindowProc(
HWND WinH,
UINT Msg,
WPARAM wParam,
LPARAM lParam
)
{
switch (Msg)
{
PAINTSTRUCT pntStruct;
static HDC hdc;
case WM_PAINT:
{
BeginPaint(
WinH,
&pntStruct
);
TextOut(hdc,
5, 5,
L"Hello, World!", _tcslen(L"Hello, World!"));
//pntStruct.rcPaint
EndPaint(
WinH,
&pntStruct
);
} break;
case WM_SIZE:
{
} break;
case WM_MOVE:
{
} break;
case WM_DESTROY:
{
} break;
case WM_CLOSE:
{
} break;
default:
{
return DefWindowProc(WinH, Msg, wParam, lParam);
} break;
case WM_ACTIVATEAPP:
{
if (WM_ACTIVATEAPP)
{
OutputDebugStringA("WM_ACTIVEAPP->TRUE");
}
else
{
OutputDebugStringA("WM_ACTIVEAPP->FALSE");
}
} break;
}
return 0;
};
int WINAPI WinMain(
HINSTANCE Window,
HINSTANCE PrevInstance,
LPSTR Cmd,
int CmdShow
)
{
WNDCLASSEX wclass;
//wclass.cbSize = sizeof(WNDCLASS);
wclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wclass.lpfnWndProc = WindowProc;
wclass.cbClsExtra = 0;
wclass.cbWndExtra = 0;
wclass.hInstance = Window;
//wclass.hIcon; TODO: CREATE ICON
//wclass.hCursor;
//wclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wclass.lpszMenuName = NULL;
wclass.lpszClassName = (LPCTSTR)WindowClass;
// HICON hIconSm;
RegisterClassEx(&wclass);
HWND CreateWin = CreateWindow(
WindowClass,
L"NAME OF WINDOW",
WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,//WIDTH:[TODO]->Make custom width to fit window
CW_USEDEFAULT,//HEIGHT:[TODO]->Make custom width to fit window
0,
0,
Window,
0
);
ShowWindow(CreateWin, CmdShow);
UpdateWindow(CreateWin);
MSG message;
while (GetMessage(&message, NULL, 0, 0) > 0)
{
TranslateMessage(&message);
DispatchMessage(&message);
};
return 0;
};
There are lots of things wrong with this code.
You are declaring WindowClass as a TCHAR[], but initializing it with a wchar_t[]. Same thing with the lpString parameter of TextOut(). That will only work if UNICODE is defined for the project, otherwise you will get a compiler error. When you use TCHAR, you need to wrap string literals with the TEXT() macro so they use the correct character type. Otherwise, stop using TCHAR and just use Unicode APIs for everything. You only need to use TCHAR if your code needs to support both ANSI (Win9x/ME) and Unicode (NT4+) compilations. Nobody really supports Win9x/Me anymore, so new code should focus on just Unicode APIs.
You are not zeroing the contents of the WNDCLASSEX structure, so all of the fields that you have intentionally commented out and not assigned values to (most importantly, the cbSize field) will contain random values from the stack. That is likely to cause RegisterClassEx() to fail, which you are not checking for. To avoid this problem, ALWAYS zero out API structures before using them. This is especially important for structures that grow in size over time (when newer Windows releases introduce new structure fields). Such structures typically have a cbSize field so the API knows which version of the structure you are using, so you must provide an accurate value. And you need to zero out any unused fields so you do not get unexpected behavior from the API.
You are not checking if CreateWindow() fails, such as a side effect of RegisterClassEx() failing.
Your WindowProc() is supposed to pass unhandled messages to DefWindowProc(), but you are not doing that. Most of your case blocks are simply discarding messages so Windows cannot process them. Is that what you really want? I doubt it. In particular, the default behavior of DefWindowProc() for WM_CLOSE is to destroy the window, triggering the WM_DESTROY message.
Your WM_DESTROY handler is not calling PostQuitMessage() to put a WM_QUIT message into the calling thread's message queue so GetMessage() can return 0 to break your message loop and let the app exit.
Your WM_PAINT handler is not using the HDC that BeginPaint() provides to you, you are drawing using an uninitialized HDC variable.
With all of that said, try something more like this:
#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h> // Or: remove this
static TCHAR WindowClass[] = TEXT("Window");
// or: static WCHAR WindowClass[] = L"Window";
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
static const TCHAR* HelloWorld = TEXT("Hello, World!");
// or: const WCHAR* HelloWorld = L"Hello, World!";
PAINTSTRUCT pntStruct = {0};
HDC hdc = BeginPaint(hWnd, &pntStruct);
TextOut(hdc, 5, 5, HelloWorld, _tcslen(HelloWorld));
// or: TextOutW(hdc, 5, 5, HelloWorld, lstrlenW(HelloWorld));
EndPaint(hWnd, &pntStruct);
break;
}
case WM_SIZE:
{
//...
break;
}
case WM_MOVE:
{
//...
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
case WM_CLOSE:
{
//...
break;
}
case WM_ACTIVATEAPP:
{
if (WM_ACTIVATEAPP)
{
OutputDebugString(TEXT("WM_ACTIVEAPP->TRUE"));
// or: OutputDebugStringW(L"WM_ACTIVEAPP->TRUE");
}
else
{
OutputDebugString(TEXT("WM_ACTIVEAPP->FALSE"));
// or: OutputDebugStringW(L"WM_ACTIVEAPP->FALSE");
}
break;
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wclass = {0}; // Or: WNDCLASSEXW
wclass.cbSize = sizeof(wclass);
wclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wclass.lpfnWndProc = &WindowProc;
wclass.cbClsExtra = 0;
wclass.cbWndExtra = 0;
wclass.hInstance = hInstance;
wclass.hIcon = NULL; // TODO: CREATE ICON
wclass.hCursor = NULL;
wclass.hbrBackground = NULL;//(HBRUSH)(COLOR_WINDOW+1);
wclass.lpszMenuName = NULL;
wclass.lpszClassName = WindowClass;
wclass.hIconSm = NULL;
if (!RegisterClassEx(&wclass)) // Or: RegisterClassExW()
{
// error! Use GetLastError() to find out why...
return 0;
}
HWND hCreateWin = CreateWindow( // Or: CreateWindowW()
WindowClass,
TEXT("NAME OF WINDOW"), // Or: L"NAME OF WINDOW"
WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,//WIDTH:[TODO]->Make custom width to fit window
CW_USEDEFAULT,//HEIGHT:[TODO]->Make custom width to fit window
0,
0,
hInstance,
0
);
if (!hCreateWin)
{
// error! Use GetLastError() to find out why...
return 0;
}
ShowWindow(hCreateWin, nCmdShow);
UpdateWindow(hCreateWin);
MSG message;
while (GetMessage(&message, NULL, 0, 0) > 0)
{
TranslateMessage(&message);
DispatchMessage(&message);
};
return 0;
};

Show tooltip on invalid input in edit control

I have subclassed edit control to accept only floating numbers. I would like to pop a tooltip when user makes an invalid input. The behavior I target is like the one edit control with ES_NUMBER has :
So far I was able to implement tracking tooltip and display it when user makes invalid input.
However, the tooltip is misplaced. I have tried to use ScreenToClient and ClientToScreen to fix this but have failed.
Here are the instructions for creating SCCE :
1) Create default Win32 project in Visual Studio.
2) Add the following includes in your stdafx.h, just under #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='*'\"")
3) Add these global variables:
HWND g_hwndTT;
TOOLINFO g_ti;
4) Here is a simple subclass procedure for edit controls ( just for testing purposes ) :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
POINT pt;
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
if (GetCaretPos(&pt)) // here comes the problem
{
// coordinates are not good, so tooltip is misplaced
ClientToScreen( hwnd, &pt );
/************************** EDIT #1 ****************************/
/******* If I delete this line x-coordinate is OK *************/
/*** y-coordinate should be little lower, but it is still OK **/
/**************************************************************/
ScreenToClient( GetParent(hwnd), &pt );
/************************* Edit #2 ****************************/
// this adjusts the y-coordinate, see the second edit
RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClientRect.bottom;
/**************************************************************/
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
TRUE, (LPARAM)&g_ti);
SendMessage(g_hwndTT, TTM_TRACKPOSITION,
0, MAKELPARAM(pt.x, pt.y));
}
return FALSE;
}
else
{
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&g_ti);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
5) Add the following WM_CREATE handler :
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, L"EDIT", L"edit", WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );
// try with tooltip
g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
if( !g_hwndTT )
MessageBeep(0); // just to signal error somehow
g_ti.cbSize = sizeof(TOOLINFO);
g_ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
g_ti.hwnd = hWnd;
g_ti.hinst = hInst;
g_ti.lpszText = TEXT("Hi there");
if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
MessageBeep(0); // just to have some error signal
// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
6) Initialize common controls in MyRegisterClass ( before return statement ) :
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES |
ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_STANDARD_CLASSES ;
if( !InitCommonControlsEx(&iccex) )
MessageBeep(0); // signal error
That's it, for the SSCCE.
My questions are following :
How can I properly position tooltip in my main window? How should I manipulate with caret coordinates?
Is there a way for tooltip handle and toolinfo structure to not be global?
Thank you for your time.
Best regards.
EDIT #1:
I have managed to achieve quite an improvement by deleting ScreenToClient call in the subclass procedure. The x-coordinate is good, y-coordinate could be slightly lower. I still would like to remove global variables somehow...
EDIT #2:
I was able to adjust y-coordinate by using EM_GETRECT message and setting y-coordinate to the bottom of the formatting rectangle:
RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClient.bottom;
Now the end-result is much better. All that is left is to remove global variables...
EDIT #3:
It seems that I have cracked it! The solution is in EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP messages! Tooltip is placed at the caret position, ballon shape is the same as the one on the picture, and it auto-dismisses itself properly. And the best thing is that I do not need global variables!
Here is my subclass procedure snippet:
case WM_CHAR:
{
// whatever... This condition is for testing purpose only
if( ! IsCharAlpha( wParam ) && IsCharAlphaNumeric( wParam ) )
{
SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
else
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof( EDITBALLOONTIP );
ebt.pszText = L" Tooltip text! ";
ebt.pszTitle = L" Tooltip title!!! ";
ebt.ttiIcon = TTI_ERROR_LARGE; // tooltip icon
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
}
break;
After further testing, I have decided to put this as an answer so others can clearly spot it.
The solution is in using EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP messages. You do not need to create tooltip and associate it to an edit control! Therefore, all I need to do now is simply subclass edit control and everything works :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof( EDITBALLOONTIP );
ebt.pszText = L" Tooltip text! ";
ebt.pszTitle = L" Tooltip title!!! ";
ebt.ttiIcon = TTI_ERROR_LARGE; // tooltip icon
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
else
{
SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
That's it!
Hopefully this answer will help someone too!
I'm giving the comment as an answer (I should have done that earlier) so that it's clear that the question has been answered:
MSDN Docs for TTM_TRACKPOSITION says that the x/y values are "in screen coordinates".
I'm not totally sure, but the y-coordinate probably corresponds to the top of the caret, you could add half of the edit box height if you want to position your tooltip in the middle of the edit box.
EDIT
re Global variables, you could bundle all your global variables into a structure, allocate memory for the structure and pass the pointer of the structure using the SetWindowLongPtr API call for the edit window using the GWLP_USERDATA, the window proc can then retrieve the values using GetWindowLongPtr...
As a follow-up to comments regarding the use of the SetProp function to remove the need to hold onto a pair of globals for the tool-tip data, I present the following solution.
Note: By error-checking on calls to GetProp, I've designed a WndProc for the subclassed edit control that would function regardless of whether or not it was desired to make use of tool-tips. If the property isn't found, I simply omit any tool-tip handling code.
Note 2: One downside to all of the available approaches to making the tooltip info non-global is that it introduces coupling between the subclassed WndProc and the parent window's wndProc.
By using dwRefData, one must check that it holds a non-NULL
pointer.
By using SetWindowLongPtr, one must remember an index into the
user-data.
By using SetProp, one must remember a textual property name. I find
this easier.
Removing the call to SetProp removes the tool-tip functionality. I.e you could use the same subclassed wndProc for edit controls whether they took advantage of tooltips or not.
Anyhoo, on with the (Code::Blocks) code.
#define _WIN32_IE 0x0500
#define _WIN32_WINNT 0x0501
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <ctype.h>
#include <cstdio>
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
/* Make the class name into a global variable */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");
HWND g_hwndTT;
TOOLINFO g_ti;
typedef struct mToolTipInfo
{
HWND hwnd;
TOOLINFO tInfo;
} * p_mToolTipInfo;
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));
switch (message)
{
case WM_CHAR:
{
POINT pt;
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
if (GetCaretPos(&pt)) // here comes the problem
{
// coordinates are not good, so tooltip is misplaced
ClientToScreen( hwnd, &pt );
RECT lastCharRect;
lastCharRect.left = lastCharRect.top = 0;
lastCharRect.right = lastCharRect.bottom = 32;
HDC editHdc;
char lastChar;
int charHeight, charWidth;
lastChar = (char)wParam;
editHdc = GetDC(hwnd);
charHeight = DrawText(editHdc, &lastChar, 1, &lastCharRect, DT_CALCRECT);
charWidth = lastCharRect.right;
ReleaseDC(hwnd, editHdc);
//pt.x += xOfs + charWidth; // invalid char isn't drawn, so no need to advance xPos to reflect width of last char
pt.y += charHeight;
if (tmp)
{
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&tmp->tInfo);
SendMessage(tmp->hwnd, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y));
}
}
return FALSE;
}
else
{
if (tmp)
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&tmp->tInfo );
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_DESTROY:
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));
if (tmp)
{
delete(tmp);
RemoveProp(hwnd, _T("tipData"));
}
}
return 0;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
HINSTANCE hInst;
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; /* No menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szClassName, /* Classname */
_T("Code::Blocks Template Windows App"), /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
544, /* The programs width */
375, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, _T("EDIT"), _T("edit"), WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );
p_mToolTipInfo tmp = new mToolTipInfo;
SetProp(hEdit, _T("tipData"), tmp);
// try with tooltip
//g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
tmp->hwnd = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
//if( !g_hwndTT )
if( !tmp->hwnd )
MessageBeep(0); // just to signal error somehow
// g_ti.cbSize = sizeof(TOOLINFO);
// g_ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
// g_ti.hwnd = hWnd;
// g_ti.hinst = hInst;
// g_ti.lpszText = _T("Hi there");
tmp->tInfo.cbSize = sizeof(TOOLINFO);
tmp->tInfo.uFlags = TTF_TRACK | TTF_ABSOLUTE;
tmp->tInfo.hwnd = hWnd;
tmp->tInfo.hinst = hInst;
tmp->tInfo.lpszText = _T("Hi there");
// if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
if( ! SendMessage(tmp->hwnd, TTM_ADDTOOL, 0, (LPARAM)&tmp->tInfo) )
MessageBeep(0); // just to have some error signal
// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hWnd, message, wParam, lParam);
}
return 0;
}