Создание профессионально выглядящего дизайнера форм
Когда я начал программировать (около 10 лет назад), меня поразили три вещи:
- Компиляторы / интерпретаторы (тогда я знал их как "программы, которые заставляют мои программы работать", за которыми часто следовал квалификатор "кем бы они ни были")
- Редакторы кода
- Дизайнеры форм
Тогда я принял их всех как факты жизни. Я мог создавать свои собственные специализированные программы, но "программы, которые заставляли мои программы работать", редакторы кода и редакторы форм были созданы богами, и я никак не мог связываться с ними.
Затем я поступил в университет и прошел курс по обработке формального языка. После изучения формальных грамматик, парсеров, абстрактных синтаксических деревьев и т. Д.; вся магия о компиляторах, интерпретаторах и редакторах кода вскоре исчезла. Компиляторы и интерпретаторы могут быть написаны в здравом и простом виде, и единственной несвойственной вещью, которая может потребоваться редактору кода с подсветкой синтаксиса, были хаки Windows API.
Однако по сей день редакторы форм остаются для меня загадкой. Либо мне не хватает технических знаний, необходимых для создания дизайнера форм, либо у меня есть такие знания, но я не могу найти способ использовать их для реализации дизайнера форм.
Используя Visual C++ и MFC, я хотел бы реализовать дизайнер форм, вдохновленный лучшим дизайнером форм:
В частности, я хотел бы имитировать две его особенности, которые мне нравятся больше всего:
Разрабатываемая форма находится внутри контейнера. Таким образом, произвольно большая форма может быть разработана, не тратя слишком много места на экране, просто изменив размер контейнера до соответствующего размера.
Опция "Выровнять по сетке" делает разработку профессионально выглядящих пользовательских интерфейсов намного менее сложной. На самом деле, я бы сказал, что создание профессионально выглядящих пользовательских интерфейсов с помощью дизайнера форм Visual Basic на самом деле просто, весело и приятно. Даже для таких программистов, как я.
Итак, у меня есть следующие вопросы:
Как создать конструктор форм, в котором разрабатываемая форма находится внутри контейнера? Является ли проектируемая форма фактическим окном, содержащимся внутри другого окна? Или это просто макет "вручную", нарисованный дизайнером форм?
Содержат ли Windows API и / или MFC функции, классы, что угодно, что позволяет легко создавать "выбираемые" элементы (окруженные маленькими белыми или синими прямоугольниками, когда они выделены, с изменяемым размером, когда они "захвачены" одним из них " края ")?
Как реализовать функцию "Выровнять по сетке"?
3 ответа
Оба ответа здесь хороши, но не учли того, что я считаю действительно интересными (включая пару, которую вы не спрашивали напрямую, но вы могли бы найти интерес в любом случае), так что вот мой 2c:
Рисование элементов управления
В идеале вы просто создаете регулярный экземпляр элемента управления. Вы хотите что-то похожее на кнопку? Создать настоящую кнопку. Хитрость заключается в том, что он перестает вести себя как кнопка: вы хотите, чтобы щелчки активировали его для перемещения, а не "нажимали" на него.
Один из способов справиться с этим - предполагая, что рассматриваемые элементы управления основаны на HWND (например, стандартный набор окон кнопки, edit, static, listbox, treeview и т. Д.) - создать элемент управления, а затем создать подкласс это - то есть. переопределите wndproc с помощью SetWindowLongPtr(GWLP_WNDPROC, ...), чтобы код конструктора мог перехватывать ввод с помощью мыши и клавиатуры и использовать его, например, для инициирования перемещения, вместо того, чтобы ввод мышью проходил до действительного кода кнопки, который вместо этого будет интерпретировать его как событие "щелчка".
Альтернативный подход к созданию подклассов заключается в размещении невидимого окна над кнопкой для ввода ввода. Та же идея перехвата ввода, просто другая реализация.
Вышесказанное относится как к управляемым (VB.Net, C#), так и к неуправляемым (C/C++) элементам управления; они оба по сути стоковые окна HWND; у управляемых версий просто есть код управляемой оболочки, передаваемый базовому неуправляемому элементу управления.
Старые элементы управления ActiveX (предварительно управляемый код), используемые в pre-.Net VB, представляли собой совершенно другую игру с мячом. Существует довольно сложная взаимосвязь между контейнером ActiveX и элементами управления ActiveX внутри него, причем многие COM-интерфейсы обрабатывают такие вещи, как согласование свойств, события, рисование и т. Д. (Существует набор интерфейсов, который позволяет элементу управления ActiveX получать входные данные и рисовать себя, не имея собственного HWND.) Однако одно преимущество, которое вы получаете от этой сложности, заключается в том, что элементы управления ActiveX имеют явный "режим разработки"; Таким образом, элемент управления знает, как правильно реагировать в этом случае, и может взаимодействовать со всей процедурой.
Сама форма...
Так что в основном элементы управления - это просто обычные элементы управления. Таким образом, вы ожидаете, что сама форма будет обычной формой? - Почти. Насколько я знаю, это просто еще одно окно, основанное на HWND, это дочерний элемент дизайнера (поэтому оно обрезается и может прокручиваться внутри него); но я думаю, что дизайнер немного обманывает, потому что обычно Windows рисует только такие кадры, как - с заголовками кнопок и мин / макс, которые используются для реальных окон верхнего уровня. Я не знаю от руки точную технику, которую они используют здесь, но некоторые варианты могут включать: нарисовать это вручную, чтобы подражать взгляду Windows; использование API-интерфейсов Windows "theme", которые позволяют получить доступ к графическим элементам, используемым для фрагментов заголовков и рисовать их там, где вы хотите; или, возможно, менее вероятно, установка окна в качестве "дочернего окна MDI" - это один из исключительных случаев, когда окна будут рисовать рамку вокруг вложенного окна.
Перетаскиваемые ручки
Простейший подход для дизайнера - создать восемь маленьких квадратных всплывающих окон без строки заголовка, которые располагаются над всеми остальными элементами - которые инициируют соответствующий код изменения размера при нажатии. Когда пользователь щелкает от элемента управления к элементу управления, просто переместите окна маркера перетаскивания в текущий активный элемент управления. (Обратите внимание, что во всем вышеперечисленном Windows сама выясняет, на кого щелкнули, вам никогда не придется сравнивать координаты мыши с координатами прямоугольника элемента и самостоятельно их обрабатывать.)
Сохранение и воссоздание
Для системных элементов управления с обычными окнами, которые используются неуправляемым C/C++, это относительно просто: существует хорошо известный текстовый формат файла -.rc -, который описывает элементы управления и местоположения. Пусть дизайнер выплюнет это (и, вероятно, файл resource.h), и все готово: любой проект C / C++ может взять эти файлы и скомпилировать их. Управляемый код (C#, VB.Net) имеет несколько больше сложная схема, но это все та же основная идея: напишите описание в стиле, которого ожидают управляемые инструменты, и они с радостью скомпилируют его и будут его использовать.
(Элементы управления ActiveX - как вы уже догадались - совсем другая история. Я не знаю стандартного формата, поэтому редактор форм и среда выполнения, которая потребляет данные, будут тесно связаны друг с другом - например, Редактор форм из pre-.Net VB6 создает формы, которые может использовать только VB. - Я думаю. Это было некоторое время назад...)
Что касается воссоздания формы: если у вас есть файл.rc, он компилируется в ресурс диалога, в Windows имеется встроенная поддержка для его воссоздания. Аналогично, библиотеки поддержки управляемого кода знают, как воссоздать форму из ее определенного формата. Оба в основном анализируют описание и для каждого элемента создают элементы соответствующих классов и задают соответствующий стиль, текст и другие свойства, как указано. Он не делает ничего, что вы не можете сделать сам, это всего лишь вспомогательный служебный код.
Обработка Фокуса
Для коллекции HWND в любом контейнере, будь то в "тестовом" режиме или фактически работающей в реальном приложении, и независимо от того, позволяете ли вы Windows или Winforms обрабатывать создание формы или вы сами создали каждый HWND, вы можете добавить поддержку табуляции с помощью вызов IsDialogMessage в вашем цикле сообщений: подробности смотрите в разделе замечаний на странице MSDN. (Хотя WinForms и могут это делать, я думаю, что на самом деле она выполняет свою собственную обработку фокуса, так что она может иметь порядок табуляции независимо от визуального стекового Z-порядка.)
Другие вещи для изучения...
Подружитесь с приложением Spy++ (входит в состав SDK, устанавливается вместе с Visual Studio). Если вы собираетесь что-то делать с HWND, управляемыми или неуправляемыми, очень полезно знать, как использовать этот инструмент: вы можете указать его на любой элемент пользовательского интерфейса в Windows и посмотреть, как он построен из дерева различные типы HWND. Направьте его на дизайнера VB и посмотрите, что на самом деле происходит для вас. (Щелкните значок "бинокль" на панели инструментов, затем перетащите перекрестие в интересующее вас окно.)
Также взгляните на файлы ресурсов, которые выкладывает дизайнер. Все, что вы можете настроить, переместить или отредактировать в конструкторе форм, соответствует некоторому элементу в одном из этих файлов ресурсов. Сделайте их копию, настройте некоторые параметры, а затем сравните файлы двух наборов и посмотрите, что изменилось. Попробуйте изменить некоторые вещи в файлах вручную (я думаю, что они почти все текстовые), перезагрузите компьютер и посмотрите, подхватил ли дизайнер ваши изменения.
Другие вещи, чтобы отметить...
Многое из вышеперечисленного относится только к Windows, в частности к тому, что, поскольку мы используем собственные строительные блоки Window - HWND - мы можем заставить саму Windows выполнить некоторую тяжелую работу за нас: это дает нам возможность повторно использовать сами элементы управления во время разработки, поэтому нам не нужно рисовать макеты; перехватывать ввод данных в других элементах управления, чтобы мы могли превратить щелчок в движение или в любое другое действие, которое мы хотим, или выяснить, какой элемент управления был нажат, без необходимости самим определять местоположение. Если бы это был дизайнер для какой-то другой инфраструктуры пользовательского интерфейса, скажем, Flash, которая не использует HWND для внутреннего использования, она, скорее всего, вместо этого использовала бы собственные внутренние средства этой инфраструктуры для выполнения аналогичной работы.Кроме того, гораздо проще, если вы ограничите количество элементов управления в палитре небольшим конечным набором, по крайней мере, сначала. Если вы хотите разрешить перетаскивание любого элемента управления, например. сторонний или тот, который вы использовали в другом проекте; Обычно вам сначала нужно каким-то образом зарегистрировать этот элемент управления, чтобы разработчик знал, что он доступен в первую очередь. И вам также может понадобиться какой-то способ узнать, какой значок он использует на панели инструментов, как его имя, какие свойства он поддерживает - и так далее.
Веселитесь, исследуя!
Вы реализуете конструктор форм почти как обычный графический интерфейс. У вас есть вещи, которые вы можете перетащить (ваши виджеты), у вас есть вещи, которые вы можете нажать (ваши кнопки), и у вас есть вещи, которые вы можете выбрать (ваши размещенные виджеты), и это действительно так.
Q: Теперь, как вы отображаете окно в графическом интерфейсе?
A: Вы рисуете это так просто.
Q: А как вы храните вещи в этом окне?
A: Вы проверяете границы "родительского" объекта. Вы могли бы почти сказать, что дизайнер форм похож на маленькую игру, и у вас есть граф сцены, содержащий все ваши виджеты, связанные отношениями родитель-ребенок.
Q: Тогда, как вы выбираете вещи в графическом интерфейсе?
A: Проверьте текущую позицию мыши при щелчке мышью по границам всех (близких) виджетов (здесь помогает только график сцены, например, дерево квадрантов).
Q: Как вы выравниваете виджеты по сетке?
A: Для выравнивания сетки, давайте приведем простой пример: скажем, ваше реальное разрешение 100px
на оси х, но вы хотите, чтобы ваша сетка имела только разрешение 10px
на х. Теперь скажите, что вы перемещаете свой виджет на 28px
в реальном разрешении. Чтобы получить разрешение сетки, вы просто делите на 10
, получить 2.8
, вокруг этого, и, наконец, переместить виджет 3
плитки на х. Округление является ключевым здесь. только если движение сетки >= ?.5
Привязываетесь к следующей плитке. Иначе ты просто останься у старого.
Надеюсь, что это может дать вам общий совет о том, как начать дизайнера форм. Повеселись.:)
(PS: не знаю ни о каких конкретных функциях / классах WinAPI/MFC, которые могли бы вам помочь, извините.)
Просто добавлю пару слов к тому, что @Xeo уже сказал:
Прежде всего, нет, вы не всегда рисуете весь контент самостоятельно. На нормальном этапе проектирования вы просто рисуете что-то похожее на элемент управления, но (по крайней мере, IIRC) оно также позволяет вам "запускать" форму в тестовом режиме (конечно, это делает дизайнер диалогов VC++, и хотя VB был более примитивным Я думаю, у него тоже есть такая возможность). В тестовом режиме вы могли "запустить" форму до того, как вы (обязательно) прикрепили к ней какой-либо код - даже если нажатие кнопки (например) ничего не делает в окружающей программе, сам элемент управления работает как обычно - кнопка щелкает нормально, элемент управления редактирования позволяет вам редактировать и т. д.
Это делается путем создания экземпляра элемента управления, сообщая ему правильное положение, размер и свойства. Элементы управления ActiveX делают немало для поддержки этого, как и предыдущие "пользовательские элементы управления Windows", хотя и с гораздо меньшей сложностью. С точки зрения элемента управления, он работает примерно так же, как обычно, получая входные данные, отправляя уведомления своему родителю и т. Д. Единственное, что изменилось, это то, что родительский элемент больше всего игнорирует большинство получаемых им уведомлений, но элемент управления не действительно не знаю это.
Есть два основных способа сделать это. Одним из них является создание коллекции элементов управления самостоятельно, вместе с их позициями, размерами и т. Д., И использование CreateWindow
(или же CreateWindowEx
и т. д.), чтобы создать окно правильного класса. Несмотря на то, что с ним относительно легко работать, у него есть недостаток, заключающийся в том, что он оставляет вам всю обработку вкладок.
Другая возможность заключается в создании DLGTEMPLATE
структура для хранения данных о диалоговом окне, а некоторые DLGITEMTEMPLATES
для отдельных элементов управления, и, наконец, использовать CreateDialogIndirect
создать диалоговое окно с этими характеристиками, удерживая эти элементы управления. Это утомительно, но работает, и когда вы закончите, оно автоматически обрабатывает вкладки между элементами управления (и работает так же, как любой другой диалог, так как это тот же код Windows, создающий его в любом случае).
Во-вторых, поскольку вы пометили этот C++, вы можете взглянуть на некоторый код в CodeProject, который фактически реализует редактор диалогов. Хотя он не такой сложный, как некоторые из коммерческих, он представляет собой достаточно полный редактор форм / диалогов, в котором собрана большая часть того, о чем вы спрашивали.