Win32 DateTime_SetMonthCalColor seems not work - winapi

I want to change the background color of a datetimepicker contorl's calender. According windows SDK ,there's a marco DateTime_SetMonthCalColor.
DateTime_SetMonthCalColor(hwnd,MSC_BACKGROUND,RGB(0,120,250));
But not work. I do this on windows control spy,send the same message DTM_SETMCCOLOR , no effect either. So what's wrong ?

MCSC_BACKGROUND sets the background color of the month calendar control itself, which only shows when the dropdown is resized to more than the default single month. The background of individual months is set with MCSC_MONTHBK instead.
The month calendar control gets created each time the dropdown is opened. In order to resize it, the parent window of the date-time picker control must process the DTN_DROPDOWN notification, like in the minimal example below.
HWND CreateDTC(HWND hWnd)
{
INITCOMMONCONTROLSEX icex = { sizeof(icex), ICC_DATE_CLASSES };
InitCommonControlsEx(&icex);
return CreateWindowExW(0, DATETIMEPICK_CLASS, L"DateTime",
WS_BORDER | WS_CHILD | WS_VISIBLE | DTS_SHOWNONE,
10, 10, 150, 25, hWnd, nullptr, hInst, nullptr);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hDTC = nullptr;
switch(msg)
{
case WM_CREATE:
hDTC = CreateDTC(hWnd);
DateTime_SetMonthCalColor(hDTC, MCSC_BACKGROUND, RGB(0, 120, 250)); // blue
DateTime_SetMonthCalColor(hDTC, MCSC_MONTHBK, RGB(120, 250, 0)); // green
break;
case WM_NOTIFY:
switch(((LPNMHDR)lParam)->code)
{
case DTN_DROPDOWN:
HWND hMC = DateTime_GetMonthCal(hDTC);
RECT rc;
MonthCal_GetMinReqRect(hMC, &rc);
MoveWindow(hMC, 0, 0, (5 * rc.right) / 2, (3 * rc.bottom) / 2, FALSE);
break;
}
break;
//...
}
return 0;
}

Related

change label's text fails after set its background color

In order to make the static control background transparent, I did handle the WM_CTLCOLORSTATIC like this:
// make label transparent
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkMode(hdcStatic, TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
}
the label is created like this:
LRESULT CALLBACK Win2Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
hTab3_label =
CreateWindowW(L"Static", L"This is my dear text!",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 90, 130, 25, hwnd, (HMENU) 18, NULL, NULL);
break;
// ...
}
}
the GUI is fine so far:
but when I set a new text to it from a button call, like this:
case WM_COMMAND:
switch(LOWORD(wParam))
{
// ....
case ID_TAB1_BUTTONB:
//MessageBox(NULL, L"Click on B button", L"", MB_OK);
SetWindowText(hTab3_label, L"This is the new text!");
break;
}
break;
the static control gets messed up, without replace the previous text for some reason, like the image below. If I remove the case WM_CTLCOLORSTATIC the text is replaced properly. What am I missing?

How to put an EDIT control in a custom popup window using Win32 API?

I'm trying to add an EDIT control to a window used as a dropdown for a custom combobox-like control. Initially this dropdown window was implemented as a child (WS_CHILD) window of the desktop, which is similar to the "ComboLbox" window used by the real combobox. This worked just fine, however an EDIT window seems to just refuse to accept focus when it is put into such dropdown window. I.e. it is enabled and reacts to right mouse clicks, for example, but clicking on it or calling SetFocus() fails (the latter sets last error to ERROR_INVALID_PARAMETER).
Because of this, and also because of the way custom popup windows are implemented in many examples including Raymond Chen's fakemenu sample, I've changed the dropdown implementation to use WS_POPUP, with the main application window as owner. This has a known problem with stealing activation from the owner window when the popup is shown, however this can be addressed by returning MA_NOACTIVATE from WM_MOUSEACTIVATE handler for the popup window and it indeed works well initially, i.e. the owner window keeps activation when the popup shows up. But as soon as I click the EDIT control inside the popup, it calls, from its default window proc, SetFocus() to set the focus to itself, which deactivates the parent window.
My question is how can I prevent this from happening? I know that it can be done because WinForms ToolStripManager manages to allow editing text in a dropdown without deactivating the parent window and it also uses WS_POPUP style for the popup window. But how does it do it?
A solution was suggested in comments "prevent the host window from visibly appearing inactive by handling WM_NCACTIVATE" This should work as shown in the example below.
When menu window is opened, the host window (HostProc) will receive WM_NCACTIVATE message. Host will look for "menuclass", if menu class is found then host will return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam); to prevent the title bar for host window get painted inactive.
You also need to handle WM_NCACTIVATE in fake menu window. When menu window goes out of focus, WM_NCACTIVATE is received by MenuProc, at this point the menu can close itself.
#include <windows.h>
const wchar_t* menuclass = L"menuclass";
LRESULT CALLBACK MenuProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_CREATE:
CreateWindow(L"Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 160, 30,
hwnd, NULL, NULL, NULL);
break;
case WM_NCACTIVATE:
{
if(!wparam)
{
//close the menu if its losing focus
PostMessage(hwnd, WM_CLOSE, 0, 0);
//tell parent to paint inactive, if user clicked on a different program
POINT pt;
GetCursorPos(&pt);
HWND hit = WindowFromPoint(pt);
HWND hparent = GetParent(hwnd);
if(hit != hparent && !IsChild(hparent, hit))
DefWindowProc(hparent, WM_NCACTIVATE, FALSE, 0);
}
break;
}
case WM_LBUTTONDOWN:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
//also handles other mouse/key messages associated with a menu...
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
LRESULT CALLBACK HostProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_NCACTIVATE:
//paint the window as active when custom menu starts
if(!wparam && FindWindow(menuclass, NULL))
return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam);
break;
case WM_RBUTTONUP:
{
//show the custom menu
POINT pt;
GetCursorPos(&pt);
CreateWindow(menuclass, NULL, WS_VISIBLE | WS_POPUP | WS_BORDER,
pt.x, pt.y, 200, 400, hwnd, 0, 0, 0);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpfnWndProc = HostProc;
wcex.lpszClassName = L"hostwnd";
RegisterClassEx(&wcex);
wcex.lpfnWndProc = MenuProc;
wcex.lpszClassName = menuclass;
RegisterClassEx(&wcex);
CreateWindow(L"hostwnd", L"Right click for menu ...",
WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, 0, 0, hInstance, 0);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}

Create window without titlebar, with resizable border and without bogus 6px white stripe

I want a window without title bar but with resizable frames and shadow.
This can easily be achieved by removing WS_CAPTION and adding WS_THICKFRAME, however, since Windows 10, there's a 6px white non-client area.
With the following code I create a window and paint all the client area with black, the window gets a left, right and bottom 6px transparent margins, however the top margin is white.
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"", // Window text
0,
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
ShowWindow(hwnd, nCmdShow);
LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle |= WS_THICKFRAME;
lStyle = lStyle & ~WS_CAPTION;
SetWindowLong(hwnd, GWL_STYLE, lStyle);
SetWindowPos(hwnd, NULL, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// Paint everything black
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOWTEXT));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Renders:
How can I remove the white stripe ?
I also found this related Qt bug report QTBUG-47543 which was closed as not being a Qt problem, because it can be reproduced with win32 api.
That's not a bug. In Windows 10 the borders on left/right/bottom are transparent. The top border is not transparent. You should leave it as is. Probably nobody will complain.
To change it, you must modify the non-client area. This is rather difficult in Windows Vista and above. See Custom Window Frame Using DWM for reference.
Find border thickness
Use DwmExtendFrameIntoClientArea to get access to non-client area
Use BeginBufferedPaint to draw opaque color over non-client area
Windows 10 example:
(See the next example for compatibility with Windows Vista, 7, 8)
//requires Dwmapi.lib and UxTheme.lib
#include <Windows.h>
#include <Dwmapi.h>
void my_paint(HDC hdc, RECT rc)
{
HBRUSH brush = CreateSolidBrush(RGB(0, 128, 0));
FillRect(hdc, &rc, brush);
DeleteObject(brush);
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static RECT border_thickness;
switch (uMsg)
{
case WM_CREATE:
{
//find border thickness
SetRectEmpty(&border_thickness);
if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_THICKFRAME)
{
AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
border_thickness.left *= -1;
border_thickness.top *= -1;
}
else if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_BORDER)
{
SetRect(&border_thickness, 1, 1, 1, 1);
}
MARGINS margins = { 0 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE };
HDC memdc;
HPAINTBUFFER hbuffer = BeginBufferedPaint(hdc, &rc, BPBF_TOPDOWNDIB, &params, &memdc);
my_paint(memdc, rc);
BufferedPaintSetAlpha(hbuffer, &rc, 255);
EndBufferedPaint(hbuffer, TRUE);
EndPaint(hwnd, &ps);
return 0;
}
case WM_NCACTIVATE:
return 0;
case WM_NCCALCSIZE:
if (lParam)
{
NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lParam;
sz->rgrc[0].left += border_thickness.left;
sz->rgrc[0].right -= border_thickness.right;
sz->rgrc[0].bottom -= border_thickness.bottom;
return 0;
}
break;
case WM_NCHITTEST:
{
//do default processing, but allow resizing from top-border
LRESULT result = DefWindowProc(hwnd, uMsg, wParam, lParam);
if (result == HTCLIENT)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ScreenToClient(hwnd, &pt);
if (pt.y < border_thickness.top) return HTTOP;
}
return result;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int)
{
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
CreateWindowEx(0, CLASS_NAME, NULL,
WS_VISIBLE | WS_THICKFRAME | WS_POPUP,
10, 10, 600, 400, NULL, NULL, hInstance, NULL);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
For compatibility with Windows Vista/7/8 use this procedure instead. This will paint over left/top/bottom borders as well as top border. This window will appear as a simple rectangle, with resizing borders:
//for Windows Vista, 7, 8, 10
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static RECT border_thickness;
switch (uMsg)
{
case WM_CREATE:
{
//find border thickness
SetRectEmpty(&border_thickness);
if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_THICKFRAME)
{
AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
border_thickness.left *= -1;
border_thickness.top *= -1;
}
else if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_BORDER)
{
SetRect(&border_thickness, 1, 1, 1, 1);
}
MARGINS margins = { 0 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE };
HDC memdc;
HPAINTBUFFER hbuffer = BeginBufferedPaint(hdc, &rc, BPBF_TOPDOWNDIB, &params, &memdc);
my_paint(memdc, rc);
BufferedPaintSetAlpha(hbuffer, &rc, 255);
EndBufferedPaint(hbuffer, TRUE);
EndPaint(hwnd, &ps);
return 0;
}
case WM_NCACTIVATE:
return 0;
case WM_NCCALCSIZE:
if (lParam)
return 0;
case WM_NCHITTEST:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ScreenToClient(hwnd, &pt);
RECT rc;
GetClientRect(hwnd, &rc);
enum {left=1, top=2, right=4, bottom=8};
int hit = 0;
if (pt.x < border_thickness.left) hit |= left;
if (pt.x > rc.right - border_thickness.right) hit |= right;
if (pt.y < border_thickness.top) hit |= top;
if (pt.y > rc.bottom - border_thickness.bottom) hit |= bottom;
if (hit & top && hit & left) return HTTOPLEFT;
if (hit & top && hit & right) return HTTOPRIGHT;
if (hit & bottom && hit & left) return HTBOTTOMLEFT;
if (hit & bottom && hit & right) return HTBOTTOMRIGHT;
if (hit & left) return HTLEFT;
if (hit & top) return HTTOP;
if (hit & right) return HTRIGHT;
if (hit & bottom) return HTBOTTOM;
return HTCLIENT;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Just to expand on this a little; in order to remove the white stripe one just has to remove the corresponding value from the first rect in NCCALCSIZE. pywin32 code would be:
if msg == WM_NCCALCSIZE:
if wParam:
res = CallWindowProc(
wndProc, hWnd, msg, wParam, lParam
)
sz = NCCALCSIZE_PARAMS.from_address(lParam)
sz.rgrc[0].top -= 6 # remove 6px top border!
return res
I think we don't need to work with DWM to remove this border. This white top resize border belongs to the non-client area of a window. So for removing it you should handle window messages related to the resizing and activating of non-client area of a window like below: ( tested only on Win 10 )
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/* When we have a custom titlebar in the window, we don't need the non-client area of a normal window
* to be painted. In order to achieve this, we handle the "WM_NCCALCSIZE" which is responsible for the
* size of non-client area of a window and set the return value to 0. Also we have to tell the
* application to not paint this area on activate and deactivation events so we also handle
* "WM_NCACTIVATE" message. */
switch( nMsg )
{
case WM_NCACTIVATE:
{
/* Returning 0 from this message disable the window from receiving activate events which is not
desirable. However When a visual style is not active (?) for this window, "lParam" is a handle to an
optional update region for the nonclient area of the window. If this parameter is set to -1,
DefWindowProc does not repaint the nonclient area to reflect the state change. */
lParam = -1;
break;
}
/* To remove the standard window frame, you must handle the WM_NCCALCSIZE message, specifically when
its wParam value is TRUE and the return value is 0 */
case WM_NCCALCSIZE:
if( wParam )
{
/* Detect whether window is maximized or not. We don't need to change the resize border when win is
* maximized because all resize borders are gone automatically */
WINDOWPLACEMENT wPos;
// GetWindowPlacement fail if this member is not set correctly.
wPos.length = sizeof( wPos );
GetWindowPlacement( hWnd, &wPos );
if( wPos.showCmd != SW_SHOWMAXIMIZED )
{
RECT borderThickness;
SetRectEmpty( &borderThickness );
AdjustWindowRectEx( &borderThickness,
GetWindowLongPtr( hWnd, GWL_STYLE ) & ~WS_CAPTION, FALSE, NULL );
borderThickness.left *= -1;
borderThickness.top *= -1;
NCCALCSIZE_PARAMS* sz = reinterpret_cast< NCCALCSIZE_PARAMS* >( lParam );
// Add 1 pixel to the top border to make the window resizable from the top border
sz->rgrc[ 0 ].top += 1;
sz->rgrc[ 0 ].left += borderThickness.left;
sz->rgrc[ 0 ].right -= borderThickness.right;
sz->rgrc[ 0 ].bottom -= borderThickness.bottom;
return 0;
}
}
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
case WM_NCCALCSIZE: {
// This must always be last.
NCCALCSIZE_PARAMS* sz = reinterpret_cast<NCCALCSIZE_PARAMS*>(lparam);
// Add 8 pixel to the top border when maximized so the app isn't cut off
// on windows 10, if set to 0, there's a white line at the top
// of the app and I've yet to find a way to remove that.
sz->rgrc[0].top += 1;
sz->rgrc[0].right -= 8;
sz->rgrc[0].bottom -= 8;
sz->rgrc[0].left -= -8;
// Previously (WVR_HREDRAW | WVR_VREDRAW), but returning 0 or 1 doesn't
// actually break anything so I've set it to 0. Unless someone pointed a
// problem in the future.
return 0;
}
Change the style of dialog.
LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle |= WS_THICKFRAME; // 6px white stripe cause of this.
lStyle = lStyle & ~WS_CAPTION;

SendMessage(..., CB_GETCURSEL, 0, 0) always returns 0 (Winapi)

First of all forgive me, but I am total newb. I am trying to write a program that recognize my combobox selection when I click on the screen (I pick the item I want to place there). However I cannot, because the SendMessage function always returns 0. How can I get the proper result?
HWND g_Combobox;
/* ... */
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
/* ... */
switch (message)
{
case WM_CREATE:
{
HWND g_Combobox = CreateWindowEx( WS_EX_CLIENTEDGE, L"COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
CBS_DROPDOWNLIST, 5, 25, 180, 200, hWnd, (HMENU) ID_MYCOMBO, hInst, NULL );
SendMessage( g_Combobox, CB_ADDSTRING, 0,( LPARAM ) L"item 1" );
SendMessage( g_Combobox, CB_ADDSTRING, 0,( LPARAM ) L"item 2" );
SendMessage( g_Combobox, CB_ADDSTRING, 0,( LPARAM ) L"item 3" );
/* ... */
}
break;
case WM_LBUTTONDOWN:
{
switch (SendMessage(g_Combobox, CB_GETCURSEL, 0, 0))
{
case 0: //always picks this one
MessageBox( NULL, L"0", L"Oh noes!", MB_ICONEXCLAMATION );
break;
default:
MessageBox( NULL, L"something diffrent than 0", L"Yeah...", MB_ICONEXCLAMATION );
break;
}
}
What am I doing wrong?
HWND g_Combobox = CreateWindowEx(...
Replace with:
g_Combobox = CreateWindowEx(...
Your current code fills local variable, leaving global variable unchanged. This is why SendMessage working with global variable gives unexpected results.
To solve such kind of problems in the future:
Use debugger.
Use maximal available compiler warning level.

How to enable buttons when scroll bar hits bottom with Win32?

I'm writing a license agreement dialog box with Win32 and I'm stumped. As usual with these things I want the "accept/don't accept" buttons to become enabled when the slider of the scroll bar of the richedit control hits bottom, but I can't find a way to get notified of that event. The earliest I've been able to learn about it is when the user releases the left mouse button.
Is there a way to do this?
Here's what I tried so far:
WM_VSCROLL and WM_LBUTTONUP in richedit's wndproc
EN_MSGFILTER notification in dlgproc (yes the filter mask is getting set)
WM_VSCROLL and WM_LBUTTONUP in dlgproc.
EN_VSCROLL notification in dlgproc
I got so desperate I tried polling but that didn't work either because apparently timer messages stop arriving while the mouse button is down on the slider. I tried both:
timer callback (to poll) in dlgproc
timer callback (to poll) in richedit's wndproc
You need to sub-class the edit box and intercept the messages to the edit box itself. Here's an artical on MSDN about subclassing controls.
EDIT: Some code to demonstrate the scroll bar enabling a button:
#include <windows.h>
#include <richedit.h>
LRESULT __stdcall RichEditSubclass
(
HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param
)
{
HWND
parent = reinterpret_cast <HWND> (GetWindowLong (window, GWL_HWNDPARENT));
WNDPROC
proc = reinterpret_cast <WNDPROC> (GetWindowLong (parent, GWL_USERDATA));
switch (message)
{
case WM_VSCROLL:
{
SCROLLINFO
scroll_info =
{
sizeof scroll_info,
SIF_ALL
};
GetScrollInfo (window, SB_VERT, &scroll_info);
if (scroll_info.nPos + static_cast <int> (scroll_info.nPage) >= scroll_info.nMax ||
scroll_info.nTrackPos + static_cast <int> (scroll_info.nPage) >= scroll_info.nMax)
{
HWND
button = reinterpret_cast <HWND> (GetWindowLong (parent, 0));
EnableWindow (button, TRUE);
}
}
break;
}
return CallWindowProc (proc, window, message, w_param, l_param);
}
LRESULT __stdcall ApplicationWindowProc
(
HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param
)
{
bool
use_default_proc = false;
LRESULT
result = 0;
switch (message)
{
case WM_CREATE:
{
CREATESTRUCT
*creation_data = reinterpret_cast <CREATESTRUCT *> (l_param);
RECT
client;
GetClientRect (window, &client);
HWND
child = CreateWindow (RICHEDIT_CLASS,
TEXT ("The\nQuick\nBrown\nFox\nJumped\nOver\nThe\nLazy\nDog\nThe\nQuick\nBrown\nFox\nJumped\nOver\nThe\nLazy\nDog"),
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_DISABLENOSCROLL,
0, 0, client.right, client.bottom - 30,
window,
0,
creation_data->hInstance,
0);
SetWindowLong (window, GWL_USERDATA, GetWindowLong (child, GWL_WNDPROC));
SetWindowLong (child, GWL_WNDPROC, reinterpret_cast <LONG> (RichEditSubclass));
SetWindowLong (child, GWL_ID, 0);
child = CreateWindow (TEXT ("BUTTON"), TEXT ("Go Ahead!"), WS_CHILD | WS_VISIBLE | WS_DISABLED, 0, client.bottom - 30, client.right, 30, window, 0, creation_data->hInstance, 0);
SetWindowLong (window, 0, reinterpret_cast <LONG> (child));
SetWindowLong (child, GWL_ID, 1);
}
break;
case WM_COMMAND:
if (HIWORD (w_param) == BN_CLICKED && LOWORD (w_param) == 1)
{
DestroyWindow (window);
}
break;
default:
use_default_proc = true;
break;
}
return use_default_proc ? DefWindowProc (window, message, w_param, l_param) : result;
}
int __stdcall WinMain
(
HINSTANCE instance,
HINSTANCE unused,
LPSTR command_line,
int show
)
{
LoadLibrary (TEXT ("riched20.dll"));
WNDCLASS
window_class =
{
0,
ApplicationWindowProc,
0,
4,
instance,
0,
LoadCursor (0, IDC_ARROW),
reinterpret_cast <HBRUSH> (COLOR_BACKGROUND + 1),
0,
TEXT ("ApplicationWindowClass")
};
RegisterClass (&window_class);
HWND
window = CreateWindow (TEXT ("ApplicationWindowClass"),
TEXT ("Application"),
WS_VISIBLE | WS_OVERLAPPED | WS_SYSMENU,
CW_USEDEFAULT,
CW_USEDEFAULT,
400, 300, 0, 0,
instance,
0);
MSG
message;
int
success;
while (success = GetMessage (&message, window, 0, 0))
{
if (success == -1)
{
break;
}
else
{
TranslateMessage (&message);
DispatchMessage (&message);
}
}
return 0;
}
The above doesn't handle the user moving the cursor in the edit box.
I would recommend starting up Spy++ and seeing which windows messages are getting sent to where.
http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx
Why not use the EM_GETTHUMB message. (Assuming Rich Edit 2.0 or later).
If you are lucky this bottom position will match EM_GETLINECOUNT.
Even though it is possible, I don't think you should do it that way - the user will have no clue why the buttons are disabled. This can be very confusing, and confusing the user should be avoided at all costs ;-)
That's why most license dialogs have radio buttons for accept/decline with decline enabled by default, so you actively have to enable accept.

Resources