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;
}
Related
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
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.
I have enabled Visual styles for my application:
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
I can set font for the date in the edit control with following approach:
HFONT hf = (HFONT)SendMessage(hwndDateTimePicker, WM_GETFONT, (WPARAM)0, (LPARAM)0);
if (hf != NULL)
{
LOGFONT lf = { 0 };
GetObject(hf, sizeof(LOGFONT), &lf);
lf.lfWeight = FW_BOLD;
lf.lfUnderline = TRUE;
hf = CreateFontIndirect(&lf);
SendMessage(GetDlgItem(hDlg, IDC_DATETIMEPICKER1), WM_SETFONT
(WPARAM)hf, (LPARAM)TRUE);
}
However, trying to use GetDC and SetTextColor (for example) does not work.
Below image illustrates my goal (color is changed to yellow).
I have tried subclassing the control, but tjat failed. Here is the code, in case you can spot a mistake:
LRESULT CALLBACK Decimalni(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CTLCOLOREDIT:
return (LRESULT)((HBRUSH)GetStockObject(GRAY_BRUSH));
case WM_NCDESTROY:
::RemoveWindowSubclass(hwnd, Decimalni, 0);
return DefSubclassProc(hwnd, message, wParam, lParam);
break;
}
return ::DefSubclassProc(hwnd, message, wParam, lParam);
}
// in WM_INITDIALOG
SetWindowSubclass(hwndDTP, Decimalni, 0, 0 );
QUESTION:
Is there a workaround for my problem?
Maybe there is a way to get the handle of the edit control and subclass it, for example? Just a thought...
EDIT:
This is the best I could have done:
// subclass procedure for date time picker
LRESULT CALLBACK DTP(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
RECT rcClient = { 0 };
GetClientRect(hwnd, &rcClient);
// Fill client area with desired brush, I used light gray as an example
FillRect(hdc, &rcClient, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
BITMAPINFO bmi;
// Create a memory DC
HDC memDC = CreateCompatibleDC(hdc);
// Create a DIB header for parent
memset(&bmi, 0, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = rcClient.right - rcClient.left;
bmi.bmiHeader.biHeight = rcClient.bottom - rcClient.top;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biCompression = BI_RGB;
// Create a DIB bitmap
HBITMAP tempBmp = CreateDIBSection(0, &bmi, DIB_RGB_COLORS, 0, 0, 0);
// Select tempBmp onto DC to force size and DIB change on the DC
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, tempBmp);
// Okay get datetime picker to draw on our memory DC
DefSubclassProc(hwnd, WM_PRINTCLIENT, (WPARAM)memDC, (LPARAM)(PRF_CLIENT));
// Transfer the memory DC onto datetime picker DC excluding default white color
TransparentBlt(hdc, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
memDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, GetSysColor(COLOR_WINDOW));
// cleanup
SelectObject(memDC, oldBmp);
DeleteObject(tempBmp);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
return 0L;
case WM_NCDESTROY:
::RemoveWindowSubclass(hwnd, DTP, 0);
return DefSubclassProc(hwnd, message, wParam, lParam);
}
return ::DefSubclassProc(hwnd, message, wParam, lParam);
}
In main windiw/dialog, just subclass the DateTime picker:
SetWindowSubclass(hwndOfYourDateTimePicker, DTP, 0, 0);
NEW QUESTIONS:
You will notice that dropdown button is not visually appealing. Is there a way to correct this?
ClearType font might give some artifacts, is there a way to solve this?
Results of my experiments with catching WM_CTLCOLORxxx in the parent window
The code is below. Without any arguments it merely prints which WM_CTLCOLORxxx messages comes in. With arguments it tries to change both the control color and the text background color (to differnt things, but based on your them for simplicity).
Windows 7, no visual styles/Common Controls 5: no WM_CTLCOLORxx messages; no change in colors
Windows 7, visual styles/Common Controls 6: several WM_CTLCOLORSTATIC messages, but setting colors seems to have no effect
wine: one WM_CTLCOLREDIT message, returned brush is applied but text background color is not
This is all with a 32-bit binary; I doubt using a 64-bit binary would change anything (at least, I hope not).
So merely checking the parent window for a WM_CTLCOLORxxx message won't work. This answer doesn't answer the question, but there's surely room for more experimentation (perhaps more subclassing tests?).
I looked at the visual styles/Common Controls 6 setup in Spy++ on Windows 7 and I didn't see any children of the date-time picker.
Hopefully this helps for now.
// 17 february 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
#define COBJMACROS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
HWND dtp;
BOOL returnColors = FALSE;
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_CLOSE:
PostQuitMessage(0);
return 0;
#define CTLCOLOR(which) \
case which: \
printf("%s %p\n", #which, (HWND) lParam); \
if (returnColors) { \
SetBkColor((HDC) wParam, GetSysColor(COLOR_ACTIVECAPTION)); \
return (LRESULT) GetSysColorBrush(COLOR_GRADIENTACTIVECAPTION); \
} \
break; /* fall through to DefWindowProc() */
CTLCOLOR(WM_CTLCOLORMSGBOX)
CTLCOLOR(WM_CTLCOLOREDIT)
CTLCOLOR(WM_CTLCOLORLISTBOX)
CTLCOLOR(WM_CTLCOLORBTN)
CTLCOLOR(WM_CTLCOLORDLG)
CTLCOLOR(WM_CTLCOLORSCROLLBAR)
CTLCOLOR(WM_CTLCOLORSTATIC)
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int main(int argc, char *argv[])
{
INITCOMMONCONTROLSEX icc;
WNDCLASSW wc;
HWND mainwin;
MSG msg;
returnColors = argc > 1;
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = ICC_DATE_CLASSES;
if (InitCommonControlsEx(&icc) == 0) {
fprintf(stderr, "InitCommonControlsEx() failed: %I32u\n", GetLastError());
return 1;
}
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.hInstance = GetModuleHandle(NULL);
if (RegisterClassW(&wc) == 0) {
fprintf(stderr, "RegisterClassW() failed: %I32u\n", GetLastError());
return 1;
}
mainwin = CreateWindowExW(0,
L"mainwin", L"Main Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
320, 240,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (mainwin == NULL) {
fprintf(stderr, "create main window failed: %I32u", GetLastError());
return 1;
}
dtp = CreateWindowExW(0,
DATETIMEPICK_CLASSW, L"",
DTS_LONGDATEFORMAT | WS_CHILD | WS_VISIBLE,
20, 20, 200, 180,
mainwin, NULL, GetModuleHandle(NULL), NULL);
if (dtp == NULL) {
fprintf(stderr, "create date-time picker failed: %I32u\n", GetLastError());
return 1;
}
printf("dtp %p\n", dtp);
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
i tried to make the progress bar in c++ and it successfully worked with setting the position but when i tried to make it marquee i always get errors
#include <windows.h>
#include <commctrl.h>
#include <tchar.h>
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
HWND hWndButton;
HWND hEdit;
HWND hProgress;
char finalName[25];
char name[10];
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nCmdShow){
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASS wincl; /* Data structure for the windowclass */
ZeroMemory(&wincl, sizeof(WNDCLASS));
/* The Window structure */
wincl.hInstance = hInst;
wincl.lpszClassName = "Window Class";
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClass (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindow (
"Window Class", /* Classname */
("Code::Blocks Template Windows App"), /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
433, /* Windows decides the position */
134, /* where the window ends up on the screen */
500, /* The programs width */
500, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hInst, /* 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);
UpdateWindow(hwnd);
}
return 1;
}
/* 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_DESTROY:
PostQuitMessage (wParam); /* send a WM_QUIT to the message queue */
exit(1);
break;
case WM_CREATE:
UpdateWindow(hwnd);
hWndButton = CreateWindow("BUTTON", "Press Me!", WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON, 0, 25, 100, 25, hwnd, (HMENU)1, NULL, NULL);
hEdit = CreateWindow("EDIT", "Write your name!", WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOVSCROLL|ES_AUTOHSCROLL, 0, 0, 200, 25, hwnd, (HMENU)2, NULL, NULL);
hProgress = CreateWindow(PROGRESS_CLASS, NULL, WS_CHILD|WS_VISIBLE|PBS_MARQUEE, 0, 100, 500, 20, hwnd, (HMENU)3, NULL, NULL);
SendMessage(hProgress, PBM_SETMARQUEE, 1, 1000);
UpdateWindow(hwnd);
break;
case WM_COMMAND:
if(wParam == 1){
GetWindowText(hEdit, _T(name), 10);
strcpy(finalName, "Hello, ");
strcat(finalName, name);
MessageBox(hwnd, (LPCSTR)finalName, "Your Name", MB_OK|MB_ICONINFORMATION);
}
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
}
saying that PBS_MARQUEE and PBM_SETMARQUEE were not defined in that scope though i included the commctrl.h header file, what is the problem??
The marquee mode was new for Windows XP, so you must define NTDDI_VERSION to be at least NTDDI_WINXP to include those constants. See Using the Windows Headers for more information.
I would like to add a child window to Metatrader4's chart window which always stays on top, without blinking, just statically there all the time upon eveything (in parent window). I am doing this from a DLL (C++).
I call this method from the mql side:
MT4_EXPFUNC int __stdcall testWindow(HWND hwnd) {
prnt_hWnd = hwnd;
CreateThread(0, NULL, ThreadProc, (LPVOID)L"Window Title", NULL, NULL);
return 0;
}
The parent window's (chart) handle is given as a parameter.
DWORD WINAPI ThreadProc( LPVOID lpParam )
{
MSG messages;
/*
... in createWindowClass:
WNDCLASSEX wc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = (LPCWSTR)L"MyClass";
wc.lpszClassName = (LPCWSTR)szClassName;
wc.lpfnWndProc = DLLWindowProc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
...
*/
CreateWindowClass(L"MyClass");
HWND hwnd = CreateWindowEx (0, L"MyClass", NULL, WS_VISIBLE | WS_CHILD , CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, prnt_hWnd, NULL, GetModuleHandle(NULL), NULL );
ShowWindow (hwnd, SW_SHOWNORMAL);
while (GetMessage (&messages, NULL, 0, 0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return 1;
}
I handle window's messages this way:
LRESULT CALLBACK DLLWindowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint( hwnd, &ps );
EndPaint( hwnd, &ps );
return 0;
}
case WM_COMMAND:
/* */
break;
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
My child window at the begining appears, then after (I guess) the parent window is getting being redrawn, suddenly it disappears, then it is just blinking (fastly appears-disappear).
My goal is to have a child window on that chart statically, so always topmost, without blinking. I could achieve this only without the WS_CHILD property. But then my child window is not ON the parent.
Try adding the WS_CLIPCHILDREN style to the chart window. I would pass the handle on the MQL4 side in init() via some MT4 export function. For example, SetChartWnd( HWND hChartWnd ) and passing WindowHandle( Symbol(), Period() ) as the parameter.
Then inside that function, I would try doing something like the following:
if ( ::IsWindow( hChartWnd ) ) {
DWORD style = GetWindowLong( hChartWnd, GWL_STYLE );
style |= WS_CLIPCHILDREN;
SetWindowLong( hChartWnd, GWL_STYLE, style );
}
}