This question already has answers here:
How can I change the background color of a button WinAPI C++
(4 answers)
Closed 7 years ago.
I have recently started learning windows API and would like to make more interactive GUI applications. All code is written in C++.
What I have done is made a custom button window, not using the built in button class. I want each button to have different text and would like to set this in the parent window.
So I create the button and save the handle:
hNavBtnNews = CreateWindow(szUW_NAV_BTN, "News", WS_CHILD | WS_VISIBLE, 540, 0, 100, HEADER_HEIGHT, header, NULL, NULL, NULL);
Then to make sure this hasn't failed I check the handle and attempt drawing text:
if(hNavBtnNews == NULL){
printf("\nFailed to Create Window Nav Button \n");
}else{
printf("\nCreated Window Nav Button");
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hNavBtnNews, &ps);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, BG_TXT_COLOR);
GetClientRect(hNavBtnNews, &rect);
DrawText(hdc, "News", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hNavBtnNews, &ps);
}
This is all done in the WM_CREATE case of the parent window procedure (which itself works just fine). The text is a light grey color and the background of the button is a dark blue. Except the text is not being drawn. There are no compiler warnings or errors either. Perhaps subclassing built in controls would be better for this, though I don't know how. Any help in solving this issue would be greatly appreciated.
Consider the following brief snippet as an example of how simple sub-classing can be:
LRESULT CALLBACK myDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (uMsg)
{
case WM_PAINT:
onBtnPaint(hWnd, wParam, lParam);
return TRUE;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
HWND button = GetDlgItem(hwndDlg, IDC_BUTTON_TO_SUBCLASS);
SetWindowSubclass(button, myDrawButtonProc, 0, 0);
}
return TRUE;
...
...
...
Related
Now I have a button control to which I want to assign a custom window procedure,
HWND buttonControl = CreateWindow(
L"BUTTON",
L"Click Me!",
WS_VISIBLE | WS_CHILD,
100, 100, 200, 50,
hWnd, //Parent Window,
0,
NULL,
NULL
);
The Window Procedure :
LRESULT CALLBACK customWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
...
return DefWindowProc(hWnd, message, wParam, lParam);
}
I have found some answers that *almost answers my question, such as:
How to change a window procedure at runtime?
Why you have to save the original window procedure of the window.
My question is :
after registering a class like,
WNDCLASS button_wc = { 0 };
button_wc.lpszClassName = L"BUTTON";
button_wc.lpfnWndProc = customWndProc; // Custom window procedure from above.
RegisterClass(&button_wc);
How am I supposed to assign it to the button control?
I know that I have to use SetWindowPtrLong to assign it to the button control, but I don't understand how to do it. In the above link it is also said that I have to save the old window procedure?!
So am I supposed to call SetWindowPtrLong and assign the value when I am creating the WNDCLASS to WNDCLASS::lpfnWndProc like:
button_wc.lpfnWndProc = (WNDPROC)SetWindowPtrLong(buttonControl, GWL_WNDPROC, (LONG_PTR)&customWndProc);
or am I supposed to assign it somewhere else?
EDIT:
This is how I am assigning the custom window procedure:
LRESULT customWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(message) {
case WM_CREATE: {
std::cout << "CREATE" << std::endl;
break;
}
case WM_LBUTTONDOWN: {
std::cout << "LBUTTONDOWN" << std::endl;
break;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HBRUSH brush = CreateSolidBrush(RGB(20, 140, 240));
FillRect(hdc, &ps.rcPaint, brush);
DeleteObject(brush);
EndPaint(hWnd, &ps);
break;
}
}
return DefSubclassProc(hWnd, message, wParam, lParam);
}
SetWindowSubclass(buttonControl, customWndProc, 17, 0); //Setting the subclass
But the window procedure is not catching the WM_CREATE and WM_LBUTTONDOWN messages but it is catching WM_PAINT messages?! Am I doing something wrong?
Any help is greatly appreciated! Thank you in advance.
So I'm creating a custom window procedure for a child control,
HWND buttonControl = CreateWindow(
L"BUTTON",
L"Click me!",
WS_CHILD | WS_VISIBLE,
100, 100, 200, 50,
hWnd, // Parent Window
0,
NULL,
NULL
);
The custom window procedure :
LRESULT customWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch(message) {
case WM_LBUTTONDOWN : {
std::cout << "Clicked" << std::endl;
break;
}
case WM_LBUTTONUP : {
std::cout << "Un-Clicked" << std::endl;
break;
}
}
return DefSubclassProc(hWnd, message, wParam, lParam); // A "DefWindowPrc" but for subclasses
}
Now I am assigning the custom window procedure to my control using SetWindowSubclass like:
SetWindowSubclass(
buttonControl, // The window we want the subclass to be assigned to
customWndProc, // The custom window procedure we defined above.
17, // Any unique number to identify this subclass with
(DWORD_PTR)nullptr // As we don't want to access data from our subclass we pass a nullptr.
)
This is all defined in commctrl.h.
And that's all I had to do to assign a custom window procedure to a child control.
Thanks to #IInspectable for the answer.
I have created a Windows application. The elements that I create are using subclassing as I wanted to handle mouse hover events.
DWORD dwStyleOfIcons = SS_BITMAP | SS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER;
img1 = CreateWindow(L"STATIC", NULL, dwStyleOfIcons,
posX, posY, imgWt, imgHt,
hWnd, (HMENU)ICON1_CLICKED, hInst, NULL);
SetWindowSubclass(img1, StaticSubClassWndProc, ICON1_CLICKED, 0);
In my StaticSubClassWndProc(), I handle WM_MOUSEMOVE, WM_MOUSELEAVE, and WM_MOUSEHOVER:
LRESULT CALLBACK StaticSubClassWndProc (HWND hwndsubclass, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR uidsubclass , DWORD_PTR dwrefdata)
{
...
switch(Msg)
{
case WM_MOUSEHOVER: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture2.bmp";
modifyImage(texture);
}
break;
}
case WM_MOUSELEAVE: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture.bmp";
modifyImage(texture);
}
break;
}
There are many STATIC items in my application, which all I wanted the behavior of a pop up context menu, like when I hover over the image it changes to a selected image, and when the cursor is out of view the image changes back to normal. I was able to do that.
I was able to do this for images which act as icons, but how do I do it for static text controls? Essentially, in a pop up menu, the selected text is all highlighted:
Is there no simpler way to make my elements in this window behave like a pop up menu? All I want is this custom structure of pop up menu behavior.
I think you did not handle the TrackMouseEvent function correctly, which caused your child window to be unable to process the WM_MOUSEHOVER and WM_MOUSELEAVE messages.
I tested the following code and it worked for me:
HBITMAP hBmp1 = (HBITMAP)LoadImage(NULL, L"test1.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HBITMAP hBmp2 = (HBITMAP)LoadImage(NULL, L"test2.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HWND img1;
LRESULT CALLBACK StaticSubClassWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uidsubclass, DWORD_PTR dwrefdata)
{
switch (msg)
{
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT lpEventTrack;
lpEventTrack.cbSize = sizeof(TRACKMOUSEEVENT);
lpEventTrack.dwFlags = TME_HOVER | TME_LEAVE;
lpEventTrack.hwndTrack = img1;
lpEventTrack.dwHoverTime = 100;
TrackMouseEvent(&lpEventTrack);
break;
}
case WM_MOUSEHOVER:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp1);
}
break;
}
case WM_MOUSELEAVE:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp2);
}
break;
}
default:
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
}
But you need to be aware that WM_MOUSEHOVER and WM_MOUSELEAVE will trigger frequently, so I don't think you should use this method to load pictures when the mouse is hovered or left, which will frequently trigger the loading of pictures.
I want the text change as time changes like a clock, however, it doesn't change. I found that the text will change when I minimize or maximize the window.
I guess I should redraw the window, but I am new to windows api, anyone good advice?
This is the main.cpp codeļ¼
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
//....
}
void Paint(HWND hwnd, LPCTSTR txt)
{
UpdateWindow(hwnd);
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, txt, -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
// Thread function
DWORD WINAPI ThreadFun(LPVOID lpParameter)
{
HWND hwnd = (HWND)lpParameter;
while (1)
{
string dateStr = Ticker::GetCurrentTimeStr();
Paint(hwnd, dateStr.c_str());
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
CreateThread(NULL, 0, ThreadFun, hwnd, 0, NULL);
}
return 0;
case WM_PAINT:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
You need to call InvalidateRect to tell the system the drawing area has changed.
Edit
Instead of creating a new thread, you can create a timer with SetTimer (see example) and respond to WM_TIMER message. Call InvalidateRect in response to WM_TIMER, to repaint the window every second.
Do all of the painting in response to WM_PAINT.
Use BeginPaint/EndPaint only in response to WM_PAINT, don't use BeginPaint/EndPaint elsewhere.
I want to change the background color of a button in runtime.
The problem is, the button does not have a black background which is what my code should produce.
Instead, it looks like is has the arrow of a drop-down control on it.
What exactly am I doing wrong here?
First I subclassed the Button:
// HWND hParent is the parent window
// HINSTANCE hInstance is the current module
HWND h = CreateWindow("Button", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
340, 10, 20, 20,
hParent, NULL, hInstance, NULL);
SetWindowSubclass(h, &MyWndProc, MyButtonId, NULL);
The ID is defined as:
enum
{
MyButtonId = 100,
};
And the subclass procedure:
LRESULT CALLBACK MyWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if( uIdSubclass == MyButtonId )
{
switch( msg )
{
case WM_ERASEBKGND:
{
HDC dc = (HDC)wParam;
SetBkColor(dc, RGB(127,127,127));
return 0;
}
}
}
return DefSubclassProc(hWnd, msg, wParam, lParam);
}
You did not pass the button ID to the CreateWindow function, so your button does not have the ID you think it does.
The SetBkColor does not set backgrounds for buttons. It sets backgrounds for subsequent calls to TextOut.
You probably meant to use BS_OWNERDRAW, not SS_OWNERDRAW.
When you use the owner draw style you have to draw the button background and text and border. You do this in the parent window handler for WM_DRAWITEM. So you don't need to subclass the button at all.
I saw this documentation on MSDN.
I am trying to remove the standard frame of the window. I successfully extended the frame into client area, but the following snippet does not work. My window looks exactly the same as without it....
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform the application of the frame change.
SetWindowPos(hWnd,
NULL,
rcClient.left, rcClient.top,
(rcClient.right - rcClient.left), (rcClient.bottom - rcClient.top),
SWP_FRAMECHANGED);
}
Could anybody help me please?
I think you can do it by passing WS_OVERLAPPED (not WS_OVERLAPPEDWINDOW) as the dwStyle parameter to CreateWindowEx when creating the window.
It's really simple, just go to your window procedure, then the message WM_NCCALCSIZE and return 0 when WPARAM is TRUE
// Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_NCCALCSIZE:
if (wparam == TRUE) return 0;
break;
}
...
}
As a clarification the code that you showed serves to force the previous code