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

Как бы мне не хотелось ответить на этот вопрос... Но ответа я не знаю. На рабочем проекте была задача написать редактор на 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

Комментарии (34)


  1. Nikeware
    07.09.2022 17:37
    +5

    Теперешний MFC - это старый MFC на стероидах. Когда-то Microsoft взяла библиотеку BCG Control Bar от https://bcgsoft.com/, которая была по сути была просто MFC Extension, и добавила её в саму MFC. Там у большинства классов просто префикс в названиях поменяли с CBCG* на CMFC* :-)


  1. Zwelenewskiy
    07.09.2022 16:39
    +3

    Вот поэтому для работы с интерфейсом Windows использую C# =)


    1. ForserX Автор
      07.09.2022 17:05

      Не знаю, насколько всё хорошо на данный момент, но пару лет назад WinForm умели хорошенько подвисать при большом количестве элементов.


      1. Zwelenewskiy
        07.09.2022 17:12
        +1

        Может быть, WinForms и не самый лучший вариант, но ведь есть и WPF.


        1. ForserX Автор
          07.09.2022 17:15

          (del)


        1. Siemargl
          07.09.2022 23:03
          +2

          Видимо нет, потому что дальше продолжили метания UWP, Avalonia, MAUI, WinUI, ... или может Веб =)


        1. Alexey2005
          08.09.2022 10:01
          +1

          Пока что самое крутое из всего, что когда-либо существовало для разработки GUI под винду — это VCL (например, в связке с C++ Builder).
          Возможности не ниже, чем у WinForms, но полностью нативно, позволяет создавать собственные библиотеки кастомных компонент, которые нормально обрабатываются линкером (а не как в C#, когда при использовании одного-единственного виджета в сборку добавляется вся библиотека целиком), очень быстро работает и не требует монструозного фреймворка.
          Всё остальное по сравнению с VCL выглядит какими-то убогими студенческими поделками.


          1. Siemargl
            08.09.2022 10:26
            +3

            Возможности не ниже, чем у WinForms

            Но ниже, чем у XAML, QML фреймворков


  1. Nikeware
    07.09.2022 17:37
    +5

    Теперешний MFC - это старый MFC на стероидах. Когда-то Microsoft взяла библиотеку BCG Control Bar от https://bcgsoft.com/, которая была по сути была просто MFC Extension, и добавила её в саму MFC. Там у большинства классов просто префикс в названиях поменяли с CBCG* на CMFC* :-)


    1. ForserX Автор
      07.09.2022 18:23

      BGC куда лучше выглядит по сей день, но и требует монету.


      1. SaNNy32
        08.09.2022 11:53

        Там не самая большая цена, особенно для компании. При этом большой плюс, что библиотека поставляется с исходниками.


        1. ForserX Автор
          08.09.2022 15:29

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


    1. screwer
      07.09.2022 23:52

      А ещё была (есть?) конкурирующая библиотека CodeJock Extreme Toolkit


  1. Lebets_VI
    07.09.2022 20:53
    -4

    MFC - самая крутая штука для нейтивного MS С++.
    Чтобы понять ее философию, нужно начать хотя бы вот с этой книги: "Ю. Тихомиров Visual C++ и MFC".
    Лично я начинал вот с этой: "Мэтт Питрек. Таинства Windows 95." После нее и Тихомирова стало понятно что это за зверь "MFC".

    P.S. Не умаляя возможности других библиотек для найтивного С/рр.


    1. Lebets_VI
      08.09.2022 13:42

      По оценке моего комментария сразу видно кто тут сидит :) :
      Упоминание VCL тоже заминусили.
      Вы просто не умеете в MFC и VCL ;)


      1. ForserX Автор
        08.09.2022 15:22
        +2

        Потому что Borland как был помойкой для олдов, так ей и остаётся.


        1. Lebets_VI
          08.09.2022 15:40
          -1

          Борланд и мне не нравился. Но это это не умаляет современное отношение к...
          Ключевое слово "олдов" или, по простому, "старперов", это и был мой посыл.
          ТС сетует на MFC, в частности: "Нет документации" - Да ее нет. а просто нормальная документация из MSDN осталась только у старпереов на дисках.
          Лично у меня до сих пор установлен MSDN help 2008 года, хотя я уже "забыл" что такое десктопное программирование, но иногда нужно и тут приходит помощь.
          Так что не все так однозначно и ставить минусы за то, что какой-то старпер попытался защитить то, с чем он работал в свое время -

          :)

          .


          1. ForserX Автор
            08.09.2022 15:43
            +3

            Ну, политика минусов на хабре - это норма. Пора уже просто свыкнуться. А в плане визуальной составляющей.. VCL всё равно проигрывает тому же ImGui. Так что...


            1. Lebets_VI
              08.09.2022 15:52
              +1

              плюсанул :)


  1. TheCalligrapher
    07.09.2022 22:06

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

    Несколько раз перечитал раздел, но не нашел никакого объяснения...


    1. ForserX Автор
      08.09.2022 09:05

      Когда-нибудь я перестану использовать псевдо "твитовый" сленг. В общем, Ribbon верстается визуально, однако набор компонентов для этого дела сильно ограничен. Есть несколько вариантов - добавлять ручками (что тоже запарно), либо делать хак с забиванием поля "Keys" юзердатой и верстать по ней элементы, перегружаю функцию констракта.


  1. screwer
    07.09.2022 23:53

    Никогда офис не использовал MFC для GUI. Подозреваю что Vusual Studio - тоже


    1. ForserX Автор
      08.09.2022 01:45
      +1

      После этих слов мне захотелось пореверсить Office


  1. fire64
    08.09.2022 09:50

    На самом деле MFC удобен тем, что это решение из коробки. Не нужно заморачиваться со всякими сторонними решениями, тем более, что его редактор уже по умолчанию встроен в MSVC, да и динамическая линковка позволяет добиться минимального размера программы. А все нужные библиотеки уже стоят у большинства клиентов, в крайнем случае поставят redistributable c++ и все должно заработать.


    1. ForserX Автор
      08.09.2022 10:07
      +1

      Привет от ImGUI в 1 файл


      1. fire64
        08.09.2022 11:13

        Да согласен, но повторюсь тут вариант из коробки. Так то всегда можно QT, или MXToolkit или ещё множество других вариантов.


        1. monah_tuk
          10.09.2022 04:29

          Qt разнесёт размер приложения до неприличия. Хотя писать да, удобно.


          1. ForserX Автор
            10.09.2022 06:53

            Плюс Qt из-за его pep архитектуры отлаживать не удобно. Но это уже так, личные заморочки.


  1. Emelian
    08.09.2022 10:50

    На рабочем проекте была задача написать редактор на MFC.

    По-моему, именно здесь источник проблем. Нужен ли сам редактор, это отдельный вопрос, а насчет MFC, мнение сложилось давно. Его нужно, по возможности, избегать.

    В свое время, MFC мне нравился, и я писал на нем свои проекты и даже публиковал статьи на codeproject.com и sql.ru. Но, к моему удивлению, всегда встречал комментарии, почему MFC? Ибо он такой, сякой и т.п. Главные обвинения – сложный и непрозрачный, особенно, если хочешь отклониться от генеральной линии. На что я отвечал, да, но зато мозги тренирует, заставляет изворачиваться и изощряться. В то время не были опубликованы исходники MFC, думаю, мне бы тогда они сильно помогли. А сейчас они уже не интересны.

    Однако, «капля камень точит» и я тоже, постепенно, пришел к мнению, что в MFC что-то не так. Начал искать альтернативу: Qt, wxWidgets, Win32xx, WTL, WinApi. Все эти фрейморки весьма хороши для своих целей, но лично мне более всего подходят последние два.

    Поэтому, главный вопрос это как уйти от MFC? Ну, или «колоться и продолжать есть кактус».


    1. Siemargl
      08.09.2022 11:40
      +4

      MFC простой и прозрачный для тех, кто учил WinAPI. Всё


      1. ForserX Автор
        08.09.2022 12:49
        +1

        Он прозрачный, пока не пытаешься в его модернизацию. Я только что перегрузил 4 класса, ради возможности изменить Ribbon Category.


        1. Siemargl
          08.09.2022 12:57

          Так риббон уже не WinAPI, а COM. И появился гораааздо позже. Потому прилеблен сбоку бантиком к MFC в т.ч.


          1. ForserX Автор
            08.09.2022 13:06

            Сути не меняет. Входит в стандартный пакет. Так что...


  1. malishich
    09.09.2022 07:54
    -1

    До сих пор крупнейшие проекты пишутся на MFC


    1. ForserX Автор
      09.09.2022 11:18
      +1

      Примеры будут?