I have a subclassed button that I am trying to highlight when the mouse cursor is over it. However, I cannot seem to get the TrackMouseEvent() function to work properly. Here is the code that creates the subclass:
hBtn = CreateWindow(L"button", L"", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, 0, 0, 0, hWnd, HMENU(400), hInst, NULL);
SetWindowSubclass(hBtn[0], subSIproc, 400, 0);
Here is the subclass procedure:
LRESULT CALLBACK subSIproc(HWND hButton, UINT iMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uId, DWORD_PTR dwRefData){
int HIflag=0;
switch(iMsg)
{
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT me{};
me.cbSize = sizeof(TRACKMOUSEEVENT);
me.dwFlags = TME_HOVER | TME_LEAVE;
me.hwndTrack = hButton;
me.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&me);
HIflag = 1;
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE);
}
case WM_MOUSEHOVER:
{
HIflag = 2;
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE);
}
case WM_MOUSELEAVE:
{
TRACKMOUSEEVENT me{};
me.cbSize = sizeof(TRACKMOUSEEVENT);
me.dwFlags = TME_HOVER | TME_LEAVE | TME_CANCEL;
me.hwndTrack = hButton;
me.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&me);
HIflag = 3;
RedrawWindow(hButton, NULL, NULL, RDW_INVALIDATE);
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hButton, &ps);
if(HIflag==0) FillRect(hdc, &ps.rcPaint, CreateSolidBrush(0x007F7F7F));
if(HIflag==1) FillRect(hdc, &ps.rcPaint, CreateSolidBrush(0x00FF0000));
if(HIflag==2) FillRect(hdc, &ps.rcPaint, CreateSolidBrush(0x000000FF));
if(HIflag==3) FillRect(hdc, &ps.rcPaint, CreateSolidBrush(0x0000FF00));
EndPaint(hButton, &ps);
}
}
return DefSubclassProc(hButton, iMsg, wParam, lParam);
}
The variable "HIflag" has four possible values, to determine which messages are being received, and when---'0' (gray) for no messages yet received; '1' (blue) for WM_MOUSEMOVE received; '2' (red) for WM_MOUSEHOVER received; and '3' for WM_MOUSELEAVE received.
Here is what is happening: When I initially run the program, the button is gray (no mouse messages received). The button remains gray, until I move the cursor onto the button. At that point, it turns green (indicating "WM_MOUSELEAVE"). It should turn red (for "WM_MOUSEHOVER") and not turn green until I move the cursor away from the button. The button now remains green, no matter where I move the cursor.
Does anyone know what I am doing wrong?
Your case blocks are missing breaks, so when WM_MOUSEMOVE is received then the code will fall through to the code for WM_MOUSEHOVER, which will then fall through to the code for WM_MOUSELEAVE, which will then fall through to the code for WM_PAINT, etc. So HIflag will always be 3 when any fall through to WM_PAINT occurs.
You are also leaking the HBRUSH that CreateSolidBrush() returns. You need to DeleteObject() the brush when you are done using it.
But most importantly, HIflag is a local variable inside of subSIproc(), so it will always be 0 when WM_PAINT is issued by the OS itself, rather than when it is reached as the result of fall through.
You need to store HIflag outside of subSIproc() on a per-button basis, such as inside the HWND itself using (Get|Set)WindowLongPtr(GWL_USERDATA), (Get|Set)Prop(), etc.
Try something more like this instead:
hBtn = CreateWindow(L"button", L"", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, 0, 0, 0, hWnd, HMENU(400), hInst, NULL);
SetProp(hBtn, TEXT("HIflag"), (HANDLE)0);
SetWindowSubclass(hBtn, subSIproc, 400, 0);
...
LRESULT CALLBACK subSIproc(HWND hButton, UINT iMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uId, DWORD_PTR dwRefData)
{
switch (iMsg)
{
case WM_NCDESTROY:
{
RemoveProp(hButton, TEXT("HIflag"));
break;
}
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT me{};
me.cbSize = sizeof(TRACKMOUSEEVENT);
me.dwFlags = TME_HOVER | TME_LEAVE;
me.hwndTrack = hButton;
me.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&me);
SetProp(hButton, TEXT("HIflag"), (HANDLE)1);
InvalidateRect(hButton, NULL, TRUE);
break;
}
case WM_MOUSEHOVER:
{
SetProp(hButton, TEXT("HIflag"), (HANDLE)2);
InvalidateRect(hButton, NULL, TRUE);
break;
}
case WM_MOUSELEAVE:
{
// tracking is cancelled automatically when this message is generated!
SetProp(hButton, TEXT("HIflag"), (HANDLE)3);
InvalidateRect(hButton, NULL, TRUE);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hButton, &ps);
COLORREF color;
switch ( (int) GetProp(hButton, TEXT("HIflag")) ) {
case 1: color = RGB(0x00, 0x00, 0xFF); break;
case 2: color = RGB(0xFF, 0x00, 0x00); break;
case 3: color = RGB(0x00, 0xFF, 0x00); break;
default: color = RGB(0x7F, 0x7F, 0x7F); break;
}
HBRUSH hBrush = CreateSolidBrush(color);
FillRect(hdc, &ps.rcPaint, hBrush);
DeleteObject(hBrush);
EndPaint(hButton, &ps);
return 0;
}
}
return DefSubclassProc(hButton, iMsg, wParam, lParam);
}
With that said, another way to color an owner-drawn button's background is to have the parent window handle the WM_CTLCOLORBTN message, or at least the WM_DRAWITEM message. This way, the parent window can store the button's HWND and its associated HIFlag together in an array somewhere, and not have to track the flag inside the HWND itself.
For example:
Buttons[index].hWnd = CreateWindow(L"button", L"", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, 0, 0, 0, hWnd, HMENU(index+1), hInst, NULL);
Buttons[index].HIflag = 0;
SetWindowSubclass(hBtn, subSIproc, index+1, 0);
...
LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
switch (uiMsg)
{
case WM_CTLCOLORBTN:
{
HWND hBtn = (HWND) lParam;
for (int index = 0; index < NumberOfButtons; ++index)
{
if (Buttons[index].hWnd == hBtn)
{
COLORREF color;
switch ( Buttons[index].HIflag ) {
case 1: color = RGB(0x00, 0x00, 0xFF); break;
case 2: color = RGB(0xFF, 0x00, 0x00); break;
case 3: color = RGB(0x00, 0xFF, 0x00); break;
default: color = RGB(0x7F, 0x7F, 0x7F); break;
}
return CreateSolidBrush(color);
}
}
break;
}
// or:
case WM_DRAWITEM:
{
DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT*) lParam;
COLORREF color;
switch ( Buttons[dis->CtlID-1].HIflag ) {
case 1: color = RGB(0x00, 0x00, 0xFF); break;
case 2: color = RGB(0xFF, 0x00, 0x00); break;
case 3: color = RGB(0x00, 0xFF, 0x00); break;
default: color = RGB(0x7F, 0x7F, 0x7F); break;
}
HBRUSH hBrush = CreateSolidBrush(color);
FillRect(dis->hDC, &(dis->rcItem), hBrush);
DeleteObject(hBrush);
return TRUE;
}
}
return DefWindowProc(hWnd, uiMsg, wParam, lParam);
}
LRESULT CALLBACK subSIproc(HWND hButton, UINT uiMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uId, DWORD_PTR dwRefData)
{
switch (uiMsg)
{
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT me{};
me.cbSize = sizeof(TRACKMOUSEEVENT);
me.dwFlags = TME_HOVER | TME_LEAVE;
me.hwndTrack = hButton;
me.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&me);
Buttons[uId-1].HIflag = 1;
InvalidateRect(hButton, NULL, TRUE);
break;
}
case WM_MOUSEHOVER:
{
Buttons[uId-1].HIflag = 2;
InvalidateRect(hButton, NULL, TRUE);
break;
}
case WM_MOUSELEAVE:
{
// tracking is cancelled automatically when this message is generated!
Buttons[uId-1].HIflag = 3;
InvalidateRect(hButton, NULL, TRUE);
break;
}
}
return DefSubclassProc(hButton, uiMsg, wParam, lParam);
}
Related
I am having troubles painting my static text controls. I have done some reading on this matter, and I have also tried some codes, but none of them worked. The closest that I managed to get was until the text control detects my mouse hover event. For this, I use control subclassing. However, I don't know what I can do to paint the text controls upon mouse hover.
My questions are:
I am confused whether to place the code responsible for painting the text control either in WM_MOUSEMOVE or WM_MOUSEHOVER in the subclass. The reason is because in the WM_MOUSEMOVE, I set it so that when the rectangle area of the text control detects the cursor, painting should happen. At the same time, I feel weird because shouldn't that particular role already be done by WM_MOUSEHOVER?
I really need help on how I could highlight the text control when the mouse is hovered.
// global text control handler
HWND txtHwnd; //somewhere in the global area of codes
//class for mouse event tracking
class MouseTrackEvents
{
bool m_bMouseTracking;
public:
MouseTrackEvents() : m_bMouseTracking(false) {}
void OnMouseMove(HWND hwnd)
{
if (!m_bMouseTracking)
{
// Enable mouse tracking.
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = hwnd;
tme.dwFlags = TME_HOVER;
tme.dwHoverTime = 100; //0.1s
TrackMouseEvent(&tme);
m_bMouseTracking = true;
}
}
void Reset(HWND hwnd)
{
m_bMouseTracking = false;
}
};
//subclass proc for text control
LRESULT CALLBACK OwnerTxtProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
//local mouse track object
MouseTrackEvents mouseTrack;
const int numOfMsg = 4;
int myMsgBox[numOfMsg]; //limit to only 4
HBRUSH hbrBkgndA = CreateSolidBrush(RGB(0, 255, 0));
//just want to print what wparam is..
int wmId, wmEvent;
wchar_t wParamHI[1000];
wchar_t wParamLO[1000];
//for painting
COLORREF hbrHighlight;
//HBRUSH hbrHighlight;
HDC txtHDC;
RECT txtRect;
POINT txtPt;
RECT txtRt;
switch (uMsg) {
case WM_CREATE: {
//testing purposes
myMsgBox[0] = MessageBox(nullptr, L"Yessir!", L":D", MB_OK | MB_ICONINFORMATION);
break;
}
case WM_MOUSEMOVE: {
mouseTrack.OnMouseMove(hWnd); //activate mouse tracking
GetWindowRect(hWnd, &txtRt); //see what value lies within
GetCursorPos(&txtPt);
if (PtInRect(&txtRt, txtPt)) {
//to check whether cursor is in text rect or not
MessageBox(nullptr, L"Okea..nice..!", L":D", MB_OK | MB_ICONINFORMATION);
//i dont know what to do here
//i was thinking that painting should be placed here...
//HBRUSH hbrBkgndEdit = CreateSolidBrush(RGB(100, 100, 255));
}
else {
//to check whether cursor is in text rect or not
MessageBox(nullptr, L"Oh noooo!", L":D", MB_OK | MB_ICONINFORMATION);
}
break;
}
case WM_MOUSEHOVER: {
//do something when txt ctrl is hovered
//to test whether wm_mousehover works, and the text control is mouse hovered
//myMsgBox[1] = MessageBox(nullptr, L"Yahooooo!", L":D", MB_OK | MB_ICONINFORMATION);
OutputDebugString(L"--------------This text control is being hovered---------------\n");
//testing painting when hovered
//plan is to send message to hChild to manage the painting since it is the parent window
//HDC theHDC = (HDC)wParam;
//SendMessage(hWnd, WM_CTLCOLORSTATIC, (WPARAM)theHDC, (LPARAM)txtHwnd);
//SendMessage(hWnd, WM_CTLCOLORSTATIC, (WPARAM)theHDC, lParam);
//for painting
//hbrHighlight = (COLORREF)(GetSysColor(COLOR_HIGHLIGHT));
//HDC hdcStatic = (HDC)wParam;
////SetTextColor(hdcStatic, RGB(0, 0, 0));
//SetBkColor(hdcStatic, hbrHighlight);
//for painting II
//HDC txtCtrlMe = (HDC)wParam;
//SetTextColor(txtCtrl, RGB(0, 0, 0));
//SetBkColor(txtCtrlMe, RGB(0, 255, 0));
//SendMessage(hChild, WM_CTLCOLORSTATIC, (WPARAM)txtCtrlMe, (LPARAM)txtHwnd);
//DeleteBrush(hbrBkgndA);
mouseTrack.Reset(hWnd);
//return (INT_PTR)hbrHighlight;
//mouseTrack.Reset(hWnd);
break;
//return TRUE;
}
//case WM_CTLCOLORSTATIC:
//{
// HDC txtCtrlMe = (HDC)wParam;
// //SetTextColor(txtCtrl, RGB(0, 0, 0));
// SetBkColor(txtCtrlMe, RGB(0, 255, 0));
// if (hbrBkgndA == NULL)
// {
// hbrBkgndA = CreateSolidBrush(RGB(255, 255, 255)); //eqivalent to delete brush objet
// }
// return (INT_PTR)hbrBkgndA;
// //break;
//}
//painting
/*
SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
SendMessage(hChild, WM_CTLCOLORSTATIC, (WPARAM)txtCtrlMe, (LPARAM)txtHwnd);
*/
//you can just ignore this, this is just for me to see whats inside wParam of the subclass
case WM_LBUTTONDOWN: {
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
int HI = swprintf_s(wParamHI, 1000, L"wParamHI: %d", wmEvent);
MessageBox(nullptr, wParamHI, L":D", MB_OK | MB_ICONINFORMATION);
int LO = swprintf_s(wParamLO, 1000, L"wParamLO: %d", wmId);
MessageBox(nullptr, wParamLO, L":D", MB_OK | MB_ICONINFORMATION);
break;
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
//child window proc, which contains the static text control
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
//...some other codes...
txtHwnd = CreateWindow(L"Static", L"Hello World!", WS_VISIBLE | WS_CHILD | SS_NOTIFY, 100, 100, 120, 15, hChild, (HMENU)testingTxtID, nullptr, nullptr);
//control subclassing
//a.k.a attaching subclass proc to the static text control
SetWindowSubclass(txtHwnd, OwnerTxtProc, 0, 0);
//...some other codes...
default: {
return DefWindowProc(hChild, message, wParam, lParam);
}
return 0;
}
Please ignore the codes I commented out in WM_MOUSEHOVER and also WM_CTLCOLORSTATIC inside OwnerTxtProc(...). They are just codes that I used to try to paint the text control, which didn't work.
All painting operations for your control should be only inside of a WM_CTLCOLORSTATIC handler, or even a WM_PAINT handler.
To trigger a repaint of the control, simply invalidate the control's client area using InvalidateRect().
If you want to change the painting conditions, store the relevant information in variables that the painting code uses, and then update those variables before invalidating the control.
Try something like this:
HWND txtHwnd;
bool bMouseTracking = false;
COLORREF bkgndColor = ...; // whatever you want...
COLORREF txtColor = ...; // whatever you want...
HBRUSH hbrBkgnd = nullptr;
const UINT APP_CTLCOLORSTATIC = WM_APP + 1;
LRESULT CALLBACK StaticTxtProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch (uMsg) {
case WM_NCDESTROY: {
RemoveWindowSubclass(hWnd, StaticTxtProc, uIdSubclass);
if (hbrBkgnd) {
DeleteBrush(hbrBkgnd);
hbrBkgnd = NULL;
}
bMouseTracking = false;
break;
}
case WM_MOUSEMOVE: {
if (!bMouseTracking) {
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(tme);
tme.hwndTrack = hWnd;
tme.dwFlags = TME_HOVER;
tme.dwHoverTime = 100; //0.1s
bMouseTracking = TrackMouseEvent(&tme);
}
RECT txtRt;
GetWindowRect(hWnd, &txtRt);
POINT txtPt;
GetCursorPos(&txtPt);
if (PtInRect(&txtRt, txtPt)) {
bkgndColor = ...; // whatever you want...
txtColor = ...; // whatever you want...
InvalidateRect(hWnd, NULL, TRUE);
}
break;
}
case WM_MOUSEHOVER: {
mMouseTracking = false;
bkgndColor = ...; // whatever you want...
txtColor = ...; // whatever you want...
InvalidateRect(hWnd, NULL, TRUE);
break;
}
case APP_CTLCOLORSTATIC: {
HDC hdc = reinterpret_cast<HDC>(wParam);
SetTextColor(hdc, txtColor);
SetBkColor(hdc, bkgndColor);
if (hbrBkgnd)
DeleteBrush(hbrBkgnd);
hbrBkgnd = CreateSolidBrush(bkgndColor);
return reinterpret_cast<LRESULT>(bkgndColor);
}
case WM_LBUTTONDOWN: {
// ...
break;
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_CREATE: {
bkgndColor = ...; // whatever you want...
txtColor = ...; // whatever you want...
txtHwnd = CreateWindow(L"Static", L"Hello World!", WS_VISIBLE | WS_CHILD | SS_NOTIFY, 100, 100, 120, 15, hChild, (HMENU)testingTxtID, nullptr, nullptr);
SetWindowSubclass(txtHwnd, StaticTxtProc, 0, 0);
break;
}
case WM_CTLCOLORSTATIC: {
return SendMessage(reinterpret_cast<HWND>(lParam), APP_CTLCOLORSTATIC, wParam, 0);
}
//...some other codes...
default: {
return DefWindowProc(hChild, message, wParam, lParam);
}
}
return 0;
}
(Posting solution on behalf of the question author to move it to the answer space).
I managed to deal with my problem on this matter.
//global variable(s)
COLORREF bkgndColor;
COLORREF txtColor;
HBRUSH hbrBkgnd = nullptr;
const UINT APP_CTLCOLORSTATIC = WM_APP + 1;
//class for mouse event
class MouseTrackEvents
{
bool m_bMouseTracking;
public:
MouseTrackEvents() : m_bMouseTracking(false) {}
void OnMouseMove(HWND hwnd)
{
if (!m_bMouseTracking)
{
// Enable mouse tracking.
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = hwnd;
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.dwHoverTime = 10; //1ms
TrackMouseEvent(&tme);
m_bMouseTracking = true;
}
}
void Reset(HWND hwnd)
{
m_bMouseTracking = false;
}
};
//proc for static text contorl subclass
LRESULT CALLBACK OwnerTxtProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
//local mouse track variable
MouseTrackEvents mouseTrack;
RECT txtRt;
GetWindowRect(hWnd, &txtRt);
POINT txtPt;
GetCursorPos(&txtPt);
switch (uMsg) {
//...some other codes...
case WM_NCDESTROY: {
RemoveWindowSubclass(hWnd, OwnerTxtProc, uIdSubclass);
if (hbrBkgnd) {
DeleteBrush(hbrBkgnd);
hbrBkgnd = NULL;
}
mouseTrack.Reset(hWnd);
break;
}
case WM_MOUSEMOVE: {
mouseTrack.OnMouseMove(hWnd); //activate mouse tracking
break;
}
case WM_MOUSEHOVER: {
if (PtInRect(&txtRt, txtPt)) {
OutputDebugString(L"--------------This text control is being hovered---------------\n");
bkgndColor = RGB(0, 0, 200); // blue
txtColor = RGB(200, 0, 0); // red
InvalidateRect(hWnd, NULL, FALSE);
}
else {
OutputDebugString(L"--------------This text control is being left---------------\n");
bkgndColor = RGB(100, 0, 0); // red bg
txtColor = RGB(0, 100, 0); // green txt
InvalidateRect(hWnd, NULL, FALSE);
}
mouseTrack.Reset(hWnd);
break;
}
case WM_MOUSELEAVE: {
if (!(PtInRect(&txtRt, txtPt))) {
OutputDebugString(L"--------------This text control is being hovered---------------\n");
bkgndColor = RGB(100, 0, 0); // red bg
txtColor = RGB(0, 100, 0); // green txt
InvalidateRect(hWnd, NULL, FALSE);
}
mouseTrack.Reset(hWnd);
break;
}
case APP_CTLCOLORSTATIC: {
HDC hdc = reinterpret_cast<HDC>(wParam);
SetTextColor(hdc, txtColor);
SetBkColor(hdc, bkgndColor);
if (hbrBkgnd) {
DeleteBrush(hbrBkgnd);
}
hbrBkgnd = CreateSolidBrush(bkgndColor);
//return reinterpret_cast<LRESULT&>(bkgndColor); //--> error --> so i added '&'
return (LRESULT)hbrBkgnd;
}
//...some other codes...
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
//parent of static text control
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//...some other codes...
switch (message)
{
case WM_CTLCOLORSTATIC:
{
return SendMessage(reinterpret_cast<HWND>(lParam), APP_CTLCOLORSTATIC, wParam, 0);
}
case WM_CREATE: //put all sorts of stuff when the context menu is called
{
//font to be set to
hFont = CreateFont(17, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, TEXT("Segoe UI"));
//set font of the textCtrl
//doesnt work if handler target = hChild
for (int i = 0; i < textCtrlSize; i++) {
SendMessage(textCtrl[i], WM_SETFONT, (WPARAM)hFont, TRUE);
}
bkgndColor = RGB(100, 0, 0); // red bg
txtColor = RGB(0, 100, 0); // green txt
txtHwnd = CreateWindow(L"Static", L"Hello World!", WS_VISIBLE | WS_CHILD | SS_NOTIFY, 100, 100, 120, 30, hChild, (HMENU)testingTxtID, nullptr, nullptr);
SetWindowSubclass(txtHwnd, OwnerTxtProc, 0, 0);
break;
}
//...some other codes...
default:
return DefWindowProc(hChild, message, wParam, lParam);
}
return 0;
}
I want to set width of header in a list view to width of its list view control when it is resized using win32 api.So i use ListView_SetColumnWidth() to set its width equal to its control's width but it does not work.
This is The code inside WinMain():
InitCommonControls();
hwndList1 = CreateWindow(WC_LISTVIEW , L"" , WS_VISIBLE | WS_CHILD | LVS_REPORT | WS_BORDER | WS_VSCROLL, 10 , 10 , width , height, hwnd, NULL, GetModuleHandle(NULL), 0);
//Sub classing the list control
SetWindowSubclass(hwndList1 ,ListProc,0 ,NULL);
SendMessage(hwndList1,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
hHeader1=ListView_GetHeader(hwndList1);
GetClientRect(hwndList1 , &rect1);
CreateColumn(hwndList1 , 0 , (char*)L"MASTER" , rect1.right );
//enable arrows
EnableScrollBar(hwndList1 , SB_VERT , ESB_ENABLE_BOTH);
//scroll down
SendMessage(hwndList1, WM_VSCROLL, SB_BOTTOM, 0L);
This is ListProc():
//the list proc
LRESULT CALLBACK ListProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp,UINT_PTR, DWORD_PTR ){
switch(msg)
{
case WM_NOTIFY :
if (((LPNMHDR) lp)->code == NM_CUSTOMDRAW)
{
LPNMCUSTOMDRAW lpcd = (LPNMCUSTOMDRAW)lp;
switch(lpcd->dwDrawStage)
{
case CDDS_PREPAINT :
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
{
SetBkColor(lpcd->hdc, RGB(0, 135, 234));
SetTextColor(lpcd->hdc, RGB(255, 255, 245));
return CDRF_NEWFONT;
}
break;
}
}
break;
case WM_NCPAINT:
{
RECT rc;
GetWindowRect(hwnd, &rc);
OffsetRect(&rc, -rc.left, -rc.top);
auto hdc = GetWindowDC(hwnd);
auto hpen = CreatePen(PS_SOLID, 1, RGB(201, 201, 201));
auto oldpen = SelectObject(hdc, hpen);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
SelectObject(hdc, oldpen);
DeleteObject(oldpen);
ReleaseDC(hwnd, hdc);
return 0;
}
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, ListProc, 0);
break;
}
return DefSubclassProc(hwnd, msg, wp, lp);
}
And the following code is WndProc() for parent window procedure:
//The window procedure
LRESULT CALLBACK WndProc( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam){
switch(msg){
case WM_SIZE:{
int nHeight , nWidth;
width =(int)((nWidth /2) * 0.8);
height =(int)((nHeight/2) * 0.7);
if( wParam == SIZE_RESTORED ){
SetWindowPos(hwndList1, 0 , 10, 10 , width, height,SWP_NOZORDER|SWP_NOMOVE);
RECT Rc;
GetClientRect(hwndList1, &Rc);
ListView_SetColumnWidth(hwndList1, 0, Rc.right - Rc.left);
}
else if ( wParam == SIZE_MAXIMIZED )
{
SetWindowPos(hwndList1, 0 , 20, 20, width, height,0);
RECT Rc;
GetClientRect(hwndList1, &Rc);
ListView_SetColumnWidth(hwndList1, 0, Rc.right - Rc.left);//
}
}
break;
case WM_NOTIFY:
if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW) {
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
switch(lplvcd->nmcd.dwDrawStage) {
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
if (((int)lplvcd->nmcd.dwItemSpec%2)==0) {
lplvcd->clrText = RGB(0,0,0);
lplvcd->clrTextBk = RGB(255, 255, 255);
} else {
lplvcd->clrText = RGB(0,0,0);
lplvcd->clrTextBk = RGB(255,255,255);
}
return CDRF_NEWFONT;
}
}
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam)){
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE , 0 , 0);
break;
case ID_ABOUT:
{
int ret=DialogBox( GetModuleHandle(NULL) , MAKEINTRESOURCE(ID_ABOUT) , hwnd , AboutDlgProc );
}
break;
}
break;
case WM_CLOSE:
DestroyWindow( hwnd );
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc( hwnd , msg , wParam , lParam );
}
return 0;
}
What am i missing? Is there another way to do it?
Thanks!
Use GetClientRect to find the inner rectangle for the control. Example:
case WM_SIZE:
{
//resize the listview control first
//calculate width/height
SetWindowPos(hwndList1, NULL, 0, 0, width, height, SWP_NOZORDER|SWP_NOMOVE);
RECT rc;
GetClientRect(hwndList1, &rc);
ListView_SetColumnWidth(hwndList1, 0, rc.right - rc.left);//rc.left is zero
break;
}
The client rectangle can be a bit smaller than the window rectangle.
You can use width/height to set the size of listview control using SetWindowPos or MoveWindow. This will correspond to GetWindowRect. But you want the client rectangle for column width.
You can also subclass the listview control and respond to WM_SIZE in listview_proc.
WM_SIZE is sent when the user resizes the main window. It is not triggered by default when the window is first opened. You may have to call ListView_SetColumnWidth when the window is first initialized.
Also note, you cannot handle custom draw inside ListProc. You must remove WM_NOTIFY section from ListProc, add it to WndProc only.
Suggested edit:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_SIZE:
{
int nWidth = LOWORD(lParam);
int nHeight = HIWORD(lParam);
int width = (int)((nWidth / 2) * 0.8);
int height = (int)((nHeight / 2) * 0.7);
SetWindowPos(hwndList1, 0, 20, 20, width, height, SWP_NOZORDER);
RECT rc;
GetClientRect(hwndList1, &rc);
ListView_SetColumnWidth(hwndList1, 0, rc.right - rc.left);
}
break;
case WM_NOTIFY:
if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW)
{
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
lplvcd->clrText = RGB(255, 0, 0);
lplvcd->clrTextBk = RGB(255, 255, 0);
return CDRF_NEWFONT;
}
}
break;
...
}
I'm new and I'm trying to write a simple program drawing lines with the mouse.
I have a problem with drawing these lines, because it leaves traces behind.
Here is an image of my problem:
And here is a sample of my code:
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:PostQuitMessage(0);break;
case WM_LBUTTONDOWN:
hdc = GetDC(hwnd);
last_x = LOWORD(lParam);
last_y = HIWORD(lParam);
isDown = true;
break;
case WM_MOUSEMOVE:
if (isDown)
{
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
Box = (HPEN)SelectObject(hdc, Pen);
int x = LOWORD(lParam);
int y = HIWORD(lParam);
MoveToEx(hdc, last_x, last_y, NULL);
LineTo(hdc, x, y);
}
break;
case WM_LBUTTONUP:
isDown = false;
ReleaseDC;
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
EDIT:
Now it's working well, but if You could explain me one another thing, how can i make that my old lines stayed on the Client area when i drawing new lines? Cause now i can draw only one line. Should i use Bitmap to save screen or something?
EDIT:EDIT:
Ok i used Vector to save coordinates of every line. Thank You guys for help!
You are getting residual traces because you are not erasing your old drawings before drawing a new line, or at least updating last_x and last_y on each move so that a new line connects to the end of the previous line, like in Microsoft's example.
But, you really should not draw on the window directly in the mouse message handlers at all. As soon as the window needs a repaint for any reason, all of your drawing will be lost. The correct way to handle this is to perform all of your drawing in a WM_PAINT message handler instead. Use the mouse messages to keep track of line information as needed, and then do all of the actual drawing in WM_PAINT only.
For example:
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
break;
case WM_DESTROY:
DeleteObject(Pen);
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
x = last_x = LOWORD(lParam);
y = last_y = HIWORD(lParam);
isDown = true;
InvalidateRect(hwnd, NULL, TRUE);
break;
case WM_MOUSEMOVE:
if (isDown)
{
x = LOWORD(lParam);
y = HIWORD(lParam);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_LBUTTONUP:
isDown = false;
InvalidateRect(hwnd, NULL, TRUE);
break;
/* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
case WM_ERASEBKGND:
{
HDC hdc = (HDC) wParam;
draw a background on the hdc as needed...
return 1;
}
*/
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (last_x != x) || (last_y != y)
{
HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
MoveToEx(hdc, last_x, last_y, NULL);
LineTo(hdc, x, y);
SelectObject(hdc, OldPen);
}
EndPaint(hwnd, &ps);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
That will draw a single line that starts at the point where the mouse was first held down, and then follow the mouse as it moves around.
Or, if you want to draw multiple lines end-to-end that follow the mouse while it is held down, try this:
std::vector<POINT> points;
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
points.clear();
break;
case WM_DESTROY:
DeleteObject(Pen);
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
{
points.clear();
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
points.push_back(pt);
isDown = true;
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case WM_MOUSEMOVE:
if (isDown)
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
points.push_back(pt);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_LBUTTONUP:
isDown = false;
InvalidateRect(hwnd, NULL, TRUE);
break;
/* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
case WM_ERASEBKGND:
{
HDC hdc = (HDC) wParam;
draw a background on the hdc as needed...
return 1;
}
*/
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (points.size() > 1)
{
HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
MoveToEx(hdc, points[0].x, points[0].y, NULL);
for (size_t i = 1; i < points.size(); ++i) {
LineTo(hdc, points[i].x, points[i].y);
}
SelectObject(hdc, OldPen);
}
EndPaint(hwnd, &ps);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
I am late to party here and it may be obvious to experienced Win32 programmers, but I wanted to add an important note to Remy Lebeau example:
both the boolean isDown and the 4 ints x,y,last_x and last_y have to be static, or else the entire solution won't work.
I'm developing a Win32 application to capture video using Windows Media Foundation. I have to display Play/Pause/Stop bitmap image on video window with transparent. I have created a modeless dialog box and shown the transparent image on the dialog using below code.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmID;
switch (unMessage)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_BTN_VIDEOSTART:
InitModelessDialog();
break;
}
break;
}
return CallWindowProc ( g_lpfnWndProc, hWnd, unMessage, wParam, lParam );
}
void InitModelessDialog()
{
DIBSECTION ds;
DWORD dwFlag = MID_EXT_STYLES | MID_CLR_KEY | MID_ALPHA_VAL;
g_hWndVideoPause = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_DLG_VIDEORECORD), g_hwndPreview, DlgProc_VideoPause);
g_hPauseBmp = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP_PAUSE), IMAGE_BITMAP, 0, 0, 0);
GetObject(g_hPauseBmp, sizeof(ds), &ds);
g_hPauseDC = CreateCompatibleDC(NULL);
SelectObject(g_hPauseDC, g_hPauseBmp);
if(dwFlag & MID_EXT_STYLES)
SetWindowLong(g_hWndVideoPause, GWL_EXSTYLE, GetWindowLong(g_hWndVideoPause, GWL_EXSTYLE) | (WS_EX_LAYERED | WS_EX_TRANSPARENT));
if(dwFlag & (MID_CLR_KEY | MID_ALPHA_VAL))
{
DWORD dwTemp = 0;
if(dwFlag & MID_ALPHA_VAL)
dwTemp = LWA_ALPHA;
if(dwFlag & MID_CLR_KEY)
dwTemp |= LWA_COLORKEY;
SetLayeredWindowAttributes(g_hWndVideoPause, UI_COLOR_KEY, (255 * UI_TRANSPARENCY_PERCENT) / 100, dwTemp);
}
SetParent(g_hWndVideoPause,g_hwndPreview);
dlgWidth = ds.dsBm.bmWidth;
dlgHeight = ds.dsBm.bmHeight;
SetWindowPos(g_hWndVideoPause, HWND_TOP, 0, 0, ds.dsBm.bmWidth, ds.dsBm.bmHeight, SWP_SHOWWINDOW | SWP_NOMOVE);
HDC hDlgDC = GetDC(g_hWndVideoPause);
StretchBlt(hDlgDC, 0, 0, dlgWidth, dlgHeight, g_hPauseDC, 0, 0, ds.dsBm.bmWidth, ds.dsBm.bmHeight, SRCCOPY);
ReleaseDC(g_hWndVideoPause, hDlgDC);
if(g_hPauseDC)
{
DeleteDC(g_hPauseDC);
g_hPauseDC = NULL;
}
if(g_hPauseBmp)
{
DeleteObject(g_hPauseBmp);
g_hPauseBmp = NULL;
}
}
INT_PTR CALLBACK DlgProc_VideoPause(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
PAINTSTRUCT ps;
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_PAINT:
{
HDC hDC = GetDC(hDlg);
if(g_hPauseDC)
{
StretchBlt(hDC, 0, 0, dlgWidth, dlgHeight, g_hPauseDC, 0, 0, dlgWidth, dlgHeight, SRCCOPY);
}
ReleaseDC(hDlg, hDC);
}
break;
case WM_ERASEBKGND:
{
return 1;
}
break;
case WM_LBUTTONDOWN:
OutputDebugString(L"DlgProc_VideoPause WM_LBUTTONDOWN pressed\r\n");
break;
case WM_KEYDOWN:
OutputDebugString(L"DlgProc_VideoPause WM_KEYDOWN pressed\r\n");
break;
}
return (INT_PTR)FALSE;
}
When clicking on the drawn image, WM_KEYDOWN event is going to parent window. I wanna receive the WM_LBUTTONDOWN and WM_KEYDOWN event in my dialog proc window.What I have to do to receive this notification in my dialog proc?
I didn't create my dialog as Child window since I couldn't apply transparent for my child dialog.So only I'm creating a modeless dialog box.
Am I missing anything here?Please help me to solve this issue.
Thanks in advance.
I am attempting to write a slot machine Win32 App that uses images to display the result of the spins. I know how to display an image on a normal LRESULT CALLBACK frame, but i'm lost when it comes to displaying images on a dialog. Could anyone help me by explaining(in-depth) how i would go about displaying images? I really appreciate it.
My current Dialog callback:
BOOL CALLBACK DlgProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG: //dialog created
g_hbmObject = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_OBJECT));
//initialize slotmachine class
Machine.Initialize(time(0));
if(g_hbmObject == NULL) //test if object is loaded correctly
std::cerr << "Could not load ball..";
break;
case WM_COMMAND: //switch command
switch(LOWORD(wParam))
{
case IDC_SPIN: //slot machine spins
//put spin function, decide what to display
//do i put the display image command here? or where?
break;
case IDC_EXIT: //exit button
DeleteObject(g_hbmObject);
EndDialog(hwnd, 0);
break;
}
break;
case WM_CLOSE:
case WM_DESTROY: //case program is exited
DeleteObject(g_hbmObject);
PostQuitMessage(0);
break;
default:
return FALSE;
}
return TRUE;
}
The following code registers a "REEL" window class: a control that displays a spinning reel for a slot machine. You can create multiple instances of it on a dialog using resource statements like:
CONTROL "",IDC_REEL1,"REEL",0,14,50,40,40
CONTROL "",IDC_REEL2,"REEL",0,70,50,40,40
The reel spins endlessly, but you can easily add private messages to start and stop it.
As explained in the comments, it expects a bitmap representing a reel with resource ID IDB_REEL.
HBITMAP g_hbmpReel;
LRESULT CALLBACK ReelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
const int ReelTimerId = 5;
switch (message)
{
case WM_CREATE:
SetTimer(hWnd, ReelTimerId, 10, 0);
break;
case WM_DESTROY:
KillTimer(hWnd, ReelTimerId);
break;
case WM_SIZE:
SetWindowPos(hWnd, 0, 0, 0, 40, 40, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
break;
case WM_TIMER:
if (wParam == ReelTimerId)
{
int offset = GetWindowLong(hWnd, 0);
offset = (offset + 5) % 120;
SetWindowLong(hWnd, 0, offset);
InvalidateRect(hWnd, 0, FALSE);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC hdcReel = CreateCompatibleDC(hdc);
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcReel, g_hbmpReel);
int offset = GetWindowLong(hWnd, 0);
BitBlt(hdc, 0, 0, 40, 40, hdcReel, 0, offset, SRCCOPY);
SelectObject(hdcReel, hbmpOld);
DeleteDC(hdcReel);
EndPaint(hWnd, &ps);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ATOM RegisterReel(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = ReelWndProc;
// Window data used to hold the position of the reel
wcex.cbWndExtra = sizeof(int);
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszClassName = L"REEL";
// IDB_REEL is a 40x160 bitmap representing a reel with THREE 40x40 symbols.
// The bottom 40x40 square should be the same as the topmost.
g_hbmpReel = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_REEL));
return RegisterClassEx(&wcex);
}