Что не так с моей попыткой создать подкласс CButton?

Я пытался создать элемент управления подклассом в первый раз, но чувствую, что сделал что-то не так. Элемент управления — это кнопка, которую я разместил в дизайнере. Это его класс:

class TTTField : public CButton
{
public:
    BEGIN_MSG_MAP_EX(TTTField)
        MSG_WM_INITDIALOG(OnInitDialog);
    END_MSG_MAP()

    TTTField operator=(const CWindow& btn);

private:

    const BOOL OnInitDialog(const CWindow wndFocus, const LPARAM lInitParam);

};

Пока ничего особенного.

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

Как видите, я реализовал карту сообщений, но сообщения просто не приходят.

Вот как я пытался настроить экземпляр этого класса:

TTTField fld;

является переменной-членом моего основного диалогового класса. В этом классе я добавил следующий DDX_MAP:

BEGIN_DDX_MAP(TTTMainDialog)
    DDX_CONTROL_HANDLE(IDC_BTN, fld)
END_DDX_MAP()

где IDC_BTN является идентификатором кнопки в дизайнере.

В перегрузке оператора присваивания для TTTField у меня есть следующее:

TTTField TTTField::operator=(const CWindow& btn)
{
    Attach(btn);
    return *this;
}

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

Что я здесь делаю неправильно? Я действительно потерян прямо сейчас.


person Sossenbinder    schedule 10.09.2016    source источник
comment
Мне любопытно, где вы прочитали информацию, которая предполагает, что кнопка когда-либо получит сообщение WM_INITDIALOG? WM_NCCREATE и WM_CREATE сообщения, конечно. Но сообщение, предназначенное для диалогов? Хмммммм. В CodeProject есть миллион статей о подклассах класса CButton, если я не ошибаюсь. (Я не MFC - я предпочитаю кататься без седла)   -  person enhzflep    schedule 10.09.2016
comment
@enhzflep да, насчет сообщений правда. Я только что изменил его на WM_CREATE, но все равно ничего не получаю   -  person Sossenbinder    schedule 10.09.2016
comment
Вы смотрели что-нибудь из CP? Это первое, что я нашел в Google CButton CodeProject codeproject.com/Articles/1911. /CButton-with-icon — обратите внимание на комментарий, в котором упоминается изменение возвращаемого значения в VC6 с UINT на LRESULT в VS2010.   -  person enhzflep    schedule 10.09.2016
comment
@Sossenbinder Немного не по теме, это шиба-ину на аватарке?   -  person    schedule 10.09.2016
comment
@enhzflep Спасибо за ссылку. Сейчас я собираюсь просмотреть несколько фрагментов кода из CodeProject, но ни один из них не показывает никакой разницы с моим пользовательским классом Button. 90% кода обычно скорее объясняют их рисунок и т. д., что мне не нужно. Все они просто считают само собой разумеющимся, что сообщения поступают в класс пользовательского элемента управления.   -  person Sossenbinder    schedule 10.09.2016
comment
@RawN Да, я уже прошел через это. Однако я не думаю, что эти статьи могут мне помочь. Я предполагаю, что проблема скорее в процедуре DDX или вызове подкласса.   -  person Sossenbinder    schedule 10.09.2016
comment
Это не MFC, почему вы используете тег MFC?   -  person Barmak Shemirani    schedule 10.09.2016
comment
@BarmakShemirani удалил это, мой плохой   -  person Sossenbinder    schedule 10.09.2016
comment
@enhzflep: Несмотря на ваши комментарии к WM_INITDIALOG, это не MFC. Это WTL, и что касается чистого кода, сгенерированный код в значительной степени таков. (Вы не можете сказать это по коду на самом деле, потому что это неправильно. Однако правильный код выдал бы это. См. ответы.)   -  person IInspectable    schedule 11.09.2016
comment
@IInspectable - Аааа, спасибо за это, я бы не возвращался к этому вопросу, если бы не ваш комментарий. Другие ответы дают мне интересную и полезную информацию. С нетерпением жду того времени, когда я перестану съеживаться каждый раз, когда вижу ваше имя пользователя и вспоминаю, как плохо и грубо я вел себя с вами некоторое время назад. Я полагаю, что вы двинулись дальше - я еще нет. Я сожалею о том роковом дне.   -  person enhzflep    schedule 11.09.2016


Ответы (2)


Класс кнопки должен быть определен следующим образом:

class TTTField : public CWindowImpl<TTTField, CButton>
{
protected:
    BEGIN_MSG_MAP_EX(TTTField)
        MSG_WM_LBUTTONDOWN(OnLButtonDown)
    END_MSG_MAP()

protected:
    LRESULT OnLButtonDown(UINT, CPoint)
    {
        //Edit: this override is meant for testing the subclass only
        //it's insufficient for handling button clicks
        MessageBox(L"Testing override...");
        return 0;
    }
};

Переопределите диалоговое окно OnInitDialog, вызовите SubclassWindow, чтобы создать подкласс кнопки:

class TTTMainDialog: public CDialogImpl<CMainDialog>
{
public:
    enum { IDD = IDD_MYDIALOG };
    BEGIN_MSG_MAP(TTTMainDialog)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    END_MSG_MAP()

    TTTField fld;
    LRESULT OnInitDialog(UINT nMessage, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        fld.SubclassWindow(GetDlgItem(IDC_BTN));
        return 0;
    }
};

Изменить, для инициализации

class TTTField : public CWindowImpl<TTTField , CButton>
{
public:
    void Create(CWindow *wnd, int id)
    {
        SubclassWindow(wnd->GetDlgItem(id));
        //add initialization here
    }
    ...
}

Затем, чтобы создать кнопку:

//fld.SubclassWindow(GetDlgItem(IDC_BTN));
fld.Create(this, IDC_BTN); //<== use this instead
person Barmak Shemirani    schedule 10.09.2016
comment
Действия кнопки обычно выполняются при нажатии кнопки мыши, а не при ее нажатии. Также помните, что кнопки захватывают мышь; вы хотите убедиться, что мышь находится внутри кнопки на кнопке вверх. - person andlabs; 10.09.2016
comment
@andlabs: я не знаю ответа, но я предполагаю, что WTL уже обрабатывает это, т. Е. Член OnLButtonUp не будет вызываться, если только WM_LBUTTONUP не находится внутри области окна элемента управления. - person IInspectable; 11.09.2016
comment
Да, я просто использовал это для тестирования подкласса. Но это плохой пример. Обработка щелчка мыши более сложная. Также кнопка должна реагировать на нажатия клавиш. - person Barmak Shemirani; 11.09.2016
comment
Это может быть долгий путь, но я вернулся к аналогичной проблеме и подумал, что смогу спросить вас. В настоящее время мне нужно получить какое-то сообщение инициализации, чтобы внести некоторые изменения в мою кнопку подкласса сразу после выполнения подкласса. Есть ли подходящее сообщение Windows или я должен просто реализовать метод и вызвать его после создания подкласса? - person Sossenbinder; 01.10.2016
comment
Я не думаю, что есть какое-то специальное сообщение для этой цели. Вам просто нужно вызвать инициализацию сразу после вызова SubclassWindow. Его легко поместить в одну функцию, чтобы сделать его автономным (см. редактирование). - person Barmak Shemirani; 01.10.2016

Возможно, лучший пример или по крайней мере один из подклассов кнопки находится прямо в исходниках WTL, в верхней части atlctrlx.h:

template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
    DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())
...

Вы также будете использовать внешние ресурсы для этого класса: Использование CBitmapButton WTL.

Это не говоря уже о комментарии WTL о выполнении элементов управления:

// These are wrapper classes for Windows standard and common controls.
// To implement a window based on a control, use following:
// Example: Implementing a window based on a list box
//
// class CMyListBox : CWindowImpl<CMyListBox, CListBox>
// {
// public:
//      BEGIN_MSG_MAP(CMyListBox)
//          // put your message handler entries here
//      END_MSG_MAP()
// };

Дополнительные примеры простых и сложных настраиваемых элементов управления WTL можно найти по адресу viksoe.dk.

В расширении элементов управления WTL сбивает с толку то, что базовые классы, такие как CButton, CComboBox, представляют собой тонкие оболочки над стандартными элементами управления. В основном они переводят методы в сообщения для отправки. Вы часто можете легко привести экземпляры таких классов к HWND и обратно.

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

Когда вы создаете подкласс элемента управления, вы добавляете функциональность на своей стороне, которая каким-то образом должна взаимодействовать со стандартной реализацией, а классы элементов управления больше не являются тонкими оболочками. Следовательно, вы наследуете от CWindowImpl, а не CButton напрямую. Следующая задача заключается в специальном подклассе: вам нужно создать исходное окно, а после этого, имея дескриптор HWND, вы модифицируете его, чтобы направлять сообщения через вашу карту сообщений. Здесь вам нужен метод SubclassWindow. То есть у вас есть созданный элемент управления, вы просматриваете его дескриптор, например. с GetDlgItem, а затем вы подклассифицируете окно, используя вызов экземпляра класса SubclassWindow. Или, в качестве альтернативы, вы можете создать элемент управления, используя новый метод класса Create, и в этом случае CreateWindow и связь с вашей картой сообщений будут выполнены за вас.

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

person Roman R.    schedule 10.09.2016