Создание профессионально выглядящего (и работающего!) конструктора форм

Когда я начал программировать (около 10+ лет назад), меня поразили три вещи:

  • Компиляторы/интерпретаторы (тогда я знал их как «программы, которые заставляют мои программы работать», за которыми часто следовал определитель «чем бы они ни были»)
  • Редакторы кода
  • Дизайнеры форм

Тогда я принял все это как факты жизни. Я мог создавать свои собственные специальные программы, но «программы, которые заставляли мои программы работать», редакторы кода и редакторы форм были созданы богами, и я не мог с ними связываться.

Затем я поступил в университет и прошел курс формальной обработки языка. После изучения формальных грамматик, синтаксических анализаторов, абстрактных синтаксических деревьев и т. д.; вся магия компиляторов, интерпретаторов и редакторов кода вскоре исчезла. Компиляторы и интерпретаторы могут быть написаны разумными и простыми способами, и единственная неразумная вещь, которую может потребовать редактор кода с подсветкой синтаксиса, — это хаки Windows API.

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

Используя Visual C++ и MFC, я хотел бы реализовать дизайнер форм, вдохновленный лучшим дизайнером форм:

Конструктор форм Visual Basic 6

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

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

  • Параметр «Выровнять по сетке» значительно упрощает разработку профессионально выглядящих пользовательских интерфейсов. На самом деле, я бы даже сказал, что создание профессионально выглядящих пользовательских интерфейсов с помощью дизайнера форм Visual Basic на самом деле просто, весело и приятно. Даже для таких левополушарных программистов, как я.

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

  1. Как сделать конструктор форм, в котором проектируемая форма находится внутри контейнера? Является ли разрабатываемая форма фактическим окном, содержащимся внутри другого окна? Или это просто макет "вручную" нарисованный дизайнером формы?

  2. Содержат ли Windows API и/или MFC функции, классы и т. д., которые упрощают создание «выбираемых» элементов (окруженных маленькими белыми или синими квадратиками, когда они выбраны, изменяемого размера, когда они «захватываются» одним из этих « края")?

  3. Как реализовать функцию «Выровнять по сетке»?


person pyon    schedule 03.04.2011    source источник
comment
Лучший? Вы когда-нибудь использовали Delphi? Дизайнер форм Borlands был лучше в 95-м, чем многие сегодня, просто жаль, что остальная часть их набора инструментов настолько ужасна.   -  person Erik    schedule 04.04.2011
comment
@Erik: я использовал Delphi, и, хотя система классов полностью надрала VB задницу, ее конструктор форм был хуже. Как сказал Брюс МакКинни: VB делает простые вещи простыми, что хорошо в 95% ситуаций.   -  person pyon    schedule 04.04.2011
comment
@Erik - согласен, за исключением остальной части набора инструментов. ИМО, Delphi был/есть великолепен! Что делает его «ужасным»?   -  person David    schedule 04.04.2011
comment
@David M: Начиная с D3-4: ошибки компилятора и нестабильность IDE. Но текущими версиями не пользовался.   -  person Erik    schedule 04.04.2011
comment
@Erik: Ты понимаешь, что это двенадцать лет назад? Давно уже продолжать такие заявления, правда. (D2010 и XE должны быть отличными — я использовал только 2010, но это определенно так.)   -  person David    schedule 05.04.2011


Ответы (3)


Оба ответа здесь хороши, но я упустил то, что я считаю действительно интересным (включая пару, которые вы не спрашивали напрямую, но вы все равно можете найти интерес), так что вот мой 2c:

Рисование элементов управления

В идеале вы просто создаете обычный экземпляр элемента управления. Вы хотите что-то похожее на кнопку? Создайте настоящую кнопку. Хитрость заключается в том, чтобы помешать ему вести себя как кнопка: вы хотите, чтобы щелчки активировали его для перемещения, а не «нажимали» на него.

Один из способов справиться с этим - предполагая, что рассматриваемые элементы управления основаны на HWND (например, стандартный набор окон для кнопок, редактирования, статики, списка, дерева и т. д.) - создать элемент управления, а затем подкласс это - т.е. переопределить wndproc с помощью SetWindowLongPtr(GWLP_WNDPROC, ...), чтобы код дизайнера мог перехватывать ввод с мыши и клавиатуры и использовать его для инициации перемещения, например, вместо того, чтобы ввод мыши передавался фактическому коду кнопки, который вместо этого будет интерпретировать это как событие «щелчка».

Альтернативный подход к созданию подклассов — разместить над кнопкой невидимое окно для захвата ввода. Та же идея перехвата ввода, просто другая реализация.

Вышеизложенное относится как к управляемым (VB.Net, C#), так и к неуправляемым (C/C++) элементам управления; они оба, по сути, стандартные HWND для окон; управляемые версии просто имеют управляемый код-оболочку, передаваемый базовому неуправляемому элементу управления.

Старые (с предварительно управляемым кодом) элементы управления ActiveX, использовавшиеся в VB до .Net, представляли собой совершенно другую игру. Существует довольно сложная взаимосвязь между контейнером ActiveX и элементами управления ActiveX внутри него, при этом многие COM-интерфейсы обрабатывают такие вещи, как согласование свойств, событий, рисование и т.д. (Существует набор интерфейсов, который позволяет элементу управления ActiveX получать входные данные и рисовать себя, не имея собственного HWND.) Одна из выгод, которую вы получаете от этой сложности, заключается в том, что элементы управления ActiveX имеют явный «режим разработки»; таким образом, элемент управления знает, как правильно реагировать в этом случае, и может сотрудничать со всей процедурой.

Сама форма...

Так что в основном элементы управления - это обычные элементы управления. Итак, вы ожидаете, что сама форма будет обычной формой? - Почти. Насколько я знаю, это просто еще одно окно на основе HWND, которое является дочерним элементом дизайнера (поэтому оно обрезается и может прокручиваться внутри него); но я думаю, что дизайнер немного «обманывает» здесь, потому что обычно Windows рисует только такие кадры, как - с заголовком и кнопками min / max, которые для реальных окон верхнего уровня. Я не знаю навскидку точную технику, которую они здесь используют, но некоторые варианты могут включать: раскрашивание вручную, чтобы имитировать внешний вид Windows; использование API-интерфейсов «темы» Windows, которые позволяют вам получать доступ к графическим элементам, используемым для фрагментов заголовков, и рисовать их там, где вы хотите; или, что менее вероятно, настроить окно как «дочернее окно MDI» — это единственный случай исключения, когда окна рисуют рамку вокруг вложенного окна.

Перетаскиваемые ручки

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

Сохранение и воссоздание

Для простых системных элементов управления 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 и посмотрите, что на самом деле происходит. (Нажмите значок «бинокль» на панели инструментов, затем перетащите перекрестие в интересующее вас окно.)

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

Другие вещи, чтобы отметить...

Much of the above is specific to Windows - notably the fact that since we're using Window's own building blocks - HWNDs - we can get Windows itself to do some of the hard work for us: it gives us the facilities to reuse the controls themselves at design time so we don't have to draw mock-ups; to intercept input on other controls so we can make a click into a move or whatever other action we want, or figure out which control is clicked without having to do the location math ourselves. If this was a designer for some other UI framework - say Flash - which doesn't use HWNDs internally, it would likely instead use that framework's own internal facilities to do similar work.

Кроме того, будет намного проще, если вы ограничите количество элементов управления в палитре небольшим конечным набором, по крайней мере, на первых порах. Если вы хотите разрешить перетаскивание любого элемента управления - например. сторонний или тот, который вы использовали в другом проекте; обычно вам сначала нужно каким-то образом «зарегистрировать» этот элемент управления, чтобы дизайнер знал, что он доступен в первую очередь. И вам также может понадобиться какой-то способ узнать, какой значок он использует на панели инструментов, как его зовут, какие свойства он поддерживает и так далее.

Получайте удовольствие от изучения!

person BrendanMcK    schedule 04.04.2011
comment
Вау, этот ответ такой классный. - person pyon; 04.04.2011

Вы реализуете конструктор форм почти как обычный графический интерфейс. У вас есть вещи, которые вы можете перетаскивать (ваши виджеты), у вас есть вещи, на которые вы можете нажимать (ваши кнопки), и у вас есть вещи, которые вы можете выбирать (ваши размещенные виджеты), и это действительно все.


Вопрос: Теперь, как вы отображаете окно в графическом интерфейсе?
О: Вы просто рисуете его.

Вопрос: А как вы храните данные внутри этого окна?
О: Вы проверяете границы "родительского" объекта. Вы могли бы почти сказать, что дизайнер форм похож на небольшую игру, и у вас есть граф сцены, содержащий все ваши виджеты, связанные отношениями родитель-потомок.

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

В: Как вы выравниваете виджеты по сетке?
О: Для выравнивания по сетке рассмотрим простой пример. Допустим, ваше реальное разрешение 100px по оси X, но вы хотите, сетка должна иметь разрешение 10px только на x. Теперь предположим, что вы перемещаете свой виджет на 28px в реальном разрешении. Чтобы получить разрешение сетки, вы просто делите на 10, получаете 2.8, округляете это число и, наконец, перемещаете плитки виджета 3 на x. Здесь главное округление. только если движение сетки равно >= ?.5, вы привязываетесь к следующей плитке. В противном случае вы просто остаетесь на старом.


Надеюсь, это даст вам общее представление о том, как начать работу с дизайнером форм. Развлекайся. :)
(PS: не знаю о каких-либо конкретных функциях/классах WinAPI/MFC, которые могли бы вам помочь, извините.)

person Xeo    schedule 03.04.2011
comment
Я знаю, что могу обнаружить сообщения WM_LBUTTONDOWN и WM_LBUTTONUP для выбора объектов; и делите, округляйте и умножайте обратно, чтобы реализовать привязку к сетке. Мне просто интересно, существует ли API, который делает создание выбираемых объектов менее утомительным. В противном случае мне придется анализировать края, окружающие автофигуры Office, пиксель за пикселем, чтобы иметь возможность воссоздать их. - person pyon; 04.04.2011

Просто чтобы добавить один или два пункта к тому, что уже сказал @Xeo:

Во-первых, нет, вы не всегда рисуете все содержимое самостоятельно. На обычном этапе проектирования вы в основном просто рисуете что-то похожее на элемент управления, но (по крайней мере, IIRC) он также позволяет вам «запускать» форму в тестовом режиме (конечно, это делает дизайнер диалогов VC++, и хотя VB был более примитивным , я думаю, что у него тоже была такая возможность). В тестовом режиме вы можете «запустить» форму до того, как (обязательно) прикрепите к ней какой-либо код — хотя нажатие кнопки (например) ничего не делает в окружающей программе, сам элемент управления работает как обычно. -- кнопка нажимается нормально, элемент управления редактированием позволяет редактировать и т. д.

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

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

Другая возможность состоит в том, чтобы создать структуру DLGTEMPLATE для хранения данных о диалоговом окне и некоторую структуру DLGITEMTEMPLATES для отдельных элементов управления, и, наконец, использовать CreateDialogIndirect для создания диалогового окна с этими спецификациями, содержащими эти элементы управления. Это утомительно, но это работает, и когда вы закончите, оно автоматически обрабатывает переходы между элементами управления (и работает так же, как и любой другой диалог, поскольку в любом случае его создает один и тот же код Windows).

Во-вторых, поскольку вы пометили этот код C++, возможно, вы захотите взглянуть на некоторый код в CodeProject, который фактически реализует диалоговый редактор. Хотя он не такой сложный, как некоторые коммерческие, это достаточно полноценный редактор форм/диалогов, в котором есть большая часть того, о чем вы спрашивали.

person Jerry Coffin    schedule 04.04.2011