MFC В 2022

от автора

Зачем и почему

Как бы мне не хотелось ответить на этот вопрос… Но ответа я не знаю. На рабочем проекте была задача написать редактор на MFC. Да, да… На MFC. Для тех, кто не знает, MFC — графическая библиотека от Microsoft, на которой стояли Microsoft Office и Visual Studio до 2010 года и выглядит примерно вот так:

Документация

Хоть и компания Microsoft любит делать подробную документацию для своих творений, но с MFC ситуация оказалась иная. Хоть он и имеет в районе 100 страниц на msdn (я знаю, что сайт переехал, но привычки не меняются), но это только на первый взгляд. Как только вам приходится делать что-то серьёзное, касаемо самих контролов, можете о ней забыть. Придётся выкачивать символы и исходный код данной библиотеки и разбираться в отладчике, вникая в аспекты UI библиотеки построенной на событиях. Ладно, у меня слишком много накипело и вводную я могу писать вечно, так что перейдём к сути.

Убийца #1 — Visual Manager

Если кто-то заходил дальше диалоговых окон, то знает что есть режимы SDI и MDI, которые представляют собой полноценное окно с табами(и без них). И их визуализацией занимается как раз таки Visual Manager (нет), у которого даже есть множество тем, начиная с WinXP и заканчивая Windows 7. (+VS и MSOffice)

На самом деле VisualManager выполняет весьма скудную роль в этом деле. Он контролирует Ribbon, PropertyGrid, MDI Tabs, Toolbar (который CMFCToolbar. Их там несколько, ребят), Header и Caption Bar. Но если же вы хотите сделать нечто следующее:

То вам придётся знакомится с событиями WM_PAINT, WM_CLRCTL (а ещё и иногда с *_NC_ аналогами этих событий) и перегружать все наши любимые кнопки, текстбоксы и статики. Помимо этого, добавлять в DocablePane функции, которые будут указывать для DC цвет текста и фона для CTreeCtrl.

Убийца #2 — PropertyGrid

Думаю, всем известно, что пропы это весьма обширная UI часть и везде нам не нравится их реализация. Помните, я говорил, что Visual Manager отвечает за их отрисовку? В данном случае, лучше бы он этого не делал. Почему? Представьте что вам нужен кастомный проп, в котором должна быть кнопка, или несколько… Но… У пропа всего 1 hwnd, что не позволяет вам быстрой перегрузкой докинуть кнопку в его содержимое. И тут уже идёт магия с созданием алгоритмов просчёта позиции.

Ситуация 2: Диалоговые окна.
К несчастью, при использовании CMFCPropertyGridCtrl в диалоговых окнах, можно встретить неправильный width:

Данная проблема решается перегрузкой класса следующим образом:

 class CDialogPropertyGridControl : public CMFCPropertyGridCtrl { public:     CDialogPropertyGridControl()     {         m_nLeftColumnWidth = 100;     }      void make_fixed_header()     {         HDITEM hdItem = { 0 };         hdItem.mask = HDI_FORMAT;         GetHeaderCtrl().GetItem(0, &hdItem);         hdItem.fmt |= HDF_FIXEDWIDTH;         GetHeaderCtrl().SetItem(0, &hdItem);     }      void SetLeftColumnWidth(int cx)     {         m_nLeftColumnWidth = cx;         AdjustLayout();     }      void OnSize(UINT f, int cx, int cy)     {         EndEditItem();         if (cx > 50)             m_nLeftColumnWidth = cx - 50; //<- 2nd column will be 50 pixels         AdjustLayout();     }      DECLARE_MESSAGE_MAP() };

Честно говоря, быстрее выйдет написать свой PropertyGrid, если Вам придётся часто с ним работать. Т.к. если не брать в расчёт предыдущие аспекты, сам по себе он вызывает очень громоздкий код, который придётся обвешивать get/set на каждый чих. За два вечера я смог написать базовый проп под свои нужды, который работает напрямую с передаваемой переменной и имеет тот же функционал:

Убийца #3 — RibbonBar

Честно говоря, Ribbon в MFC весьма прогрессивнее в плане работы, чем все остальные компоненты. Даже его внутренность построенная на xml, в отличии от тех же диалоговых окон. Но, я так думал, пока не пришлось добавлять на него кастомный элемент. И как было бы не смешно, им оказался RadioBtn. Да, забавный факт, но там нет понятия всеми любимых радио-кнопок.

Шаг 1 — Новый класс для Ribbon

Для начала надо объяснить MFC, что мы будем использовать кастомый конструктор для Ribbon. Перегружаем функцию LoadFromResource

BOOL XRibbonBar::LoadFromResource(LPCTSTR lpszXMLResID, LPCTSTR lpszResType /*= RT_RIBBON*/, HINSTANCE hInstance /*= NULL*/) { ASSERT_VALID(this);  CMFCRibbonInfo info; CMFCRibbonInfoLoader loader(info);  if (!loader.Load(lpszXMLResID, lpszResType, hInstance)) { TRACE0("Cannot load ribbon from resource\n"); return FALSE; }  XRibbonConstructor constr(info); constr.ConstructRibbonBar(*this);  return TRUE; }

Шаг 2 — Конструктор

А тут уже объясняем, в чём не прав стандартный конструктор MFC

  CMFCRibbonBaseElement* XRibbonConstructor::CreateElement(const CMFCRibbonInfo::XElement& info) const { if (info.GetElementType() == CMFCRibbonInfo::e_TypeButton_Check) { const CMFCRibbonInfo::XElementButtonCheck& infoElement = (const CMFCRibbonInfo::XElementButtonCheck&)info;  // RadioBox if (strstr(info.m_strKeys, "RB")) { // Make friends list string TryStr = info.m_strKeys.operator LPCSTR(); TryStr = TryStr.substr(2); int ID = atoi_17(TryStr);  XRibbonRadioBox* pNewElement = new XRibbonRadioBox(infoElement.m_ID.m_Value, infoElement.m_strText);  ConstructBaseElement(*pNewElement, info); return pNewElement; } }  return CMFCRibbonConstructor::CreateElement(info); } 

strstr(info.m_strKeys, «RB») — Это флаг в визуальном редакторе, благодаря которому мы можем впихнуть кучу UserInfo

P.S.

Хочется сказать большое спасибо людям с CodeProject и StackOverflow за огромное количество подсказок при глубокой работе с данным UI API.

Большую часть кода по компонентам MFC я перевёл в общий стандарт и опубликовал по ссылке ниже. Скорее всего, данный репозиторий будет со временем пополняться. Надеюсь, кому-то данная библиотека решений сократит пару десятков часов.

https://github.com/ForserX/XMFC


ссылка на оригинал статьи https://habr.com/ru/post/686980/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *