Как обнаружить зависание над статическим элементом управления Win32? - PullRequest
0 голосов
/ 04 июля 2018

У меня проблемы с обнаружением зависания над статическим элементом управления Win32.

Это не повторяющаяся проблема, потому что здесь ищет несколько статических элементов управления, а не просто ищет один известный дескриптор статического элемента управления во время компиляции.

Хотя это можно сделать за несколько секунд на другом языке, я немного расстроился после того, как попробовал несколько часов. Я надеюсь получить ответ здесь.

Сначала я создал класс с именем Label. Я создаю статическое окно управления внутри него. Сейчас я буду называть статическую метку.

// Create the label's handle.
m_handle = CreateWindowEx(NULL, "static", m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (m_handle == NULL)
    return false;

Когда указатель мыши находится над этой меткой, должен вызываться следующий метод:

void Label::invokeOnMouseHover()
{
    if (m_onMouseOver)
        m_onMouseOver();
}

Это вызовет мой метод:

void lblName_onMouseOver()
{
    MessageBox::show("Hovering!", "My Console",
        MessageBoxButtons::Ok, MessageBoxIcon::Information);
}

Вот как я создаю его с верхнего уровня:

Label lblName("This is a label.", 0, 0);
lblName.setVisible(true);
lblName.OnMouseOver(lblName_onMouseOver); 
frm.add(lblName);

Признайся, этот тонкий слой прекрасен.

В то время как мои обратные вызовы нормально работают для моих элементов управления Button и Checkbox, я заметил, что статика немного отличается.

Итак, давайте пройдемся по нескольким уровням:

Это в процедуре главного окна:

case WM_MOUSEMOVE:
{  
    int xPos = LOWORD(lParam);
    int yPos = HIWORD(lParam);

    frm.setMousePos(xPos, yPos);  

    // Get the static's id
    int id = // ?? Which static control id is it out of several?

                // Obtain the control associated with the id.
        X3D::Windows::Control *ctrl = frm.getControls().find(id)->second;
    if (ctrl == NULL)
        return 0;

    // Check if this is a X3D Label control.
    if (typeid(*ctrl) == typeid(X3D::Windows::Label))
    {
        Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);

        int xPos = GET_X_LPARAM(lParam);
        int yPos = GET_Y_LPARAM(lParam);

        if (xPos >= lbl->getX() &&
            yPos >= lbl->getY() &&
            (xPos < (lbl->getX() + lbl->getWidth())) &&
            (yPos < (lbl->getY() + lbl->getHeight())))
        {
            if (lbl != NULL)
                lbl->invokeOnMouseHover();
        }
    }
}
break; 

То, что я пытаюсь сделать здесь, - это определить, какой идентификатор метки был обнаружен, а затем вызвать Label :: invokeOnMouseOver ().

Несмотря на то, что я понимаю, что в какой-то момент мне нужно использовать TRACKMOUSEEVENT, его член поля 'HWND' требует дескриптора метки. Но я не могу легко сказать, какая ручка будет, потому что коллекция может содержать одну или несколько меток.

В целом, я ищу предложения о том, как реструктурировать это или посмотреть, есть ли здесь простое решение. Я думаю, что я обдумываю это.

Спасибо.

Обновление:

Вот обновление кода после прочтения первого ответа с первым решением. Хотя это решает проблему наведения, я не вижу текст метки при исполнении.

case WM_MOUSEMOVE:
{  
    int xPos = LOWORD(lParam);
    int yPos = HIWORD(lParam);

    // Get the mouse position
    frm.setMousePos(xPos, yPos);  

    // Check for labels
    X3D::Windows::Control *ctrl = (X3D::Windows::Control*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

    if (ctrl)
    {
        // Check if this is a X3D Label control.
        Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
        if (lbl)
            lbl->invokeOnMouseHover();

        return CallWindowProc(lbl->getOldProc(), hWnd, msg, wParam, lParam);
    } 
}
break; 

И управление созданием:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;

SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldProc = (WNDPROC)SetWindowLongPtr(m_handle, GWLP_WNDPROC, (LONG_PTR)&wndProc);

Если это проблема с краской, это то, что у меня есть в WndProc.

    case WM_PAINT:
    {
        hDC = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);

        return 0;
    }
    break;

Обновление № 2:

Альтернативное решение исправило проблему.

Если у кого-то возникнут проблемы с неразрешенными внешними символами с помощью SetWindowSubclass () в будущем, не забудьте добавить в свой проект следующее или просто #pragma it:

#pragma comment(lib, "comctl32.lib")

Ответы [ 2 ]

0 голосов
/ 04 июля 2018

Это сообщение не отвечает на вопрос, который вы задали, но вы должны прочитать его. Это важно, и это опубликовано в духе полезности после моего довольно кислотного комментария выше. Я надеюсь, что вы найдете это так.

Эта библиотека классов столкнется с проблемами. Код такой (используя dynamic_cast):

case WM_MOUSEMOVE:
{
    X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;

    // Check if this is a X3D Label control.
    Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
    if (lbl)
        lbl->invokeOnMouseHover();

    break;
}

Почти всегда неправильно.

Почему? Ну, предположим, вы хотите навести курсор на какой-то другой вид контроля? Что ты будешь делать сейчас? И эти парни просто будут размножаться как кролики, так что не делайте этого.

Вместо этого для сообщения, которое библиотека понимает (такого как это), объявите соответствующий виртуальный метод в базовом классе, который производные классы могут переопределить, если они заинтересованы в обработке этого сообщения. Тогда у вас есть основа твердого дизайна (и это довольно простые вещи).

Итак, в этом случае вы бы получили:

class Control                  // Base class
{
    ...
    virtual void onMouseHover (...) { }
    ...
};

А потом:

class Label : public Control   // Derived class
{
    ...
    virtual void onMouseHover (...) override { ...  }
    ...
};

Теперь о моем втором замечании: вы обнаружите, что, особенно для диалогов, ваше приложение будет хотеть обрабатывать много сообщений, которые базовый класс не понимает (или не заботится).

Как вы собираетесь это сделать? Собираетесь ли вы добавлять код в базовый класс для каждого нового сообщения, в котором заинтересовано ваше приложение (или в этом отношении конкретные типы элементов управления, реализованные в библиотеке классов)? Это не очень привлекательная перспектива.

Теперь MFC обрабатывает это с помощью того, что он называет картой сообщений , которая, по сути, является таблицей идентификаторов сообщений и соответствующих им обработчиков команд, которые вы можете связать (в вашем случае) с любым объектом, в конечном счете полученным из Control и я рекомендую вам сделать что-то подобное.

Но благодаря магии STL вы можете добиться большего. У меня есть что-то вроде этого в моей библиотеке классов (мой базовый класс на самом деле называется Window, как я бы предположил, ваш должен быть):

typedef INT_PTR (Window::*MESSAGE_HANDLER)
    (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Register a message handler
void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler);

На самом деле RegisterMessageHandler() добавляет handler к std::unordered_map, связанному с объектом Window, используя uMsg в качестве ключа. Затем, когда это сообщение впоследствии приходит, оно может быть отправлено правому обработчику , при этом базовый класс ничего не знает о значении сообщения , и это то, что вам нужно.

Итак, вы можете объявить следующее в классе Control (код не проверен, написан в Блокноте):

class Control                  // Base class
{
    ...
    std::unordered_map <UINT, MESSAGE_HANDLER> m_message_map;
    ...
};

И тогда RegisterMessageHandler() может выглядеть так:

void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler)
{
    m_message_map.emplace (uMsg, handler);
}

И MySubclassProc() может выглядеть так:

LRESULT CALLBACK MySubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
    auto handler ctrl->m_message_map.find (uMsg);
    if (handler != ctrl->m_message_map.end ())
        return handler.second (hWnd, uMsg, wParam, lParam);
    ...
}

Моя собственная библиотека классов на самом деле более сложная, чем эта (я начал с чего-то простого, но со временем украсил его), но это основная идея. Возможно, вам придется овладеть некоторыми навыками C ++, чтобы справиться с этим, но поверьте мне, если вы реализуете что-то подобное, вы будете очень рады, что сделали это.

0 голосов
/ 04 июля 2018

Единственная идентифицирующая информация, которую WM_MOUSEMOVE дает вам, это HWND, над которым движется мышь (или HWND, у которого захватило мышь ). Сообщенные координаты X / Y относятся к этому HWND. Это HWND, который вы ищете, поэтому вам не нужно охотиться за ним, сообщение дает вам.

Если вам нужен доступ к контрольному идентификатору HWND, вы можете использовать для этого GetDlgCtrlID(). Но обратите внимание, что каждый HWND имеет свою собственную оконную процедуру , поэтому идентификаторы элементов управления обычно полезны только для уведомлений, таких как WM_COMMAND и WM_NOTIFY, которые отправляются в родительское окно элемента управления (и даже затем такие уведомления также содержат HWND ребенка).

Когда мышь перемещается над определенным HWND, WM_MOUSEMOVE отправляется в процедуру сообщения , которая только HWND (или в HWND, который захватил мышь). Похоже, вы ожидаете, что он будет отправлен в окно parent элемента управления, а это просто не тот случай. Вот почему ваш WM_MOUSEMOVE обработчик не вызывается. Вы обрабатываете сообщение на неправильном уровне. Вместо этого вы должны быть готовы обрабатывать сообщения для каждого элемента управления, используя собственную процедуру сообщения элемента управления.

Было бы более эффективно хранить указатель Control объекта *1033* внутри самого ассоциированного с ним HWND через SetWindowLongPtr(GWLP_USERDATA), SetWindowSubClass() или SetProp(), и тогда обработчики сообщений смогут получить доступ к Control* указатель сообщаемого сообщения HWND при необходимости, например:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldproc = (WNDPROC) SetWindowLongPtr(m_handle, GWL_WNDPROC, (LONG_PTR)&MyWndProc);

...

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_MOUSEMOVE:
        {
            ...

            X3D::Windows::Control *ctrl = (X3D::Windows::Control*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
            if (ctrl)
            {
                // Check if this is a X3D Label control.
                Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
                if (lbl)
                    lbl->invokeOnMouseHover();
            }

            break;
        }

        ...
    }

    return CallWindowProc(m_oldproc, hWnd, uMsg, wParam, lParam);
}

В качестве альтернативы:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;
SetWindowSubclass(m_handle, &MySubclassProc, 1, (DWORD_PTR)(X3D::Windows::Control*)this);

...

LRESULT CALLBACK MySubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
        case WM_NCDESTROY:
            RemoveWindowSubclass(hWnd, &MySubclassProc, uIdSubclass);
            break;

        case WM_MOUSEMOVE:
        {
            X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;

            // Check if this is a X3D Label control.
            Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
            if (lbl)
                lbl->invokeOnMouseHover();

            break;
        }

        ...
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...