Home > database >  How to paint a text control on mouse hover?
How to paint a text control on mouse hover?

Time:10-20

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:

  1. 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?

  2. 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.

Update:

Thank God and also huge thanks to Remy Lebeau, 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;
}

CodePudding user response:

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;
}
  • Related