Архитектурные советы по реализации логики GUI - PullRequest
6 голосов
/ 23 ноября 2011

Итак, я реализую графический интерфейс, похожий на svg-редактор, в одном приложении, которое я здесь работаю.Вот некоторые примеры логики, которая потребуется для этого:

  • Если пользователь щелкает правой кнопкой мыши на холсте, должен быть создан новый узел, и последующие узлы должны быть «связаны»с линией, образующей многоугольник
  • Если пользователь щелкает левой кнопкой мыши на узле, я должен переместить весь набор многоугольников в соответствии с положением мыши
  • Пользователь может удалитьузлы
  • Выбранные узлы должны быть окрашены по-разному
  • Пользователь может выбрать несколько узлов, нажав SHIFT и щелкнув по узлам

И т. д.

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

  • Конвейерный паттерн: я бы создал классы, которые обрабатывали бы каждое логическое событие отдельно, и использовал бы приоритетчтобы указать, что делать / что будет передано первым, и как событие будет распространяться на последующие элементы конвейера.

  • MVC: это наиболее распространенный ответ, но как я могу использоватьдля того, чтобы сделать код более чистым, на данный момент для меня это очень размыто.

  • State Machine: Было бы неплохо, но управление гранулярностью конечного автомата было бы сложным

Поэтому я спрашиваю у SO гуру о том, как создать лучший и более счастливый код.

Ответы [ 4 ]

6 голосов
/ 23 ноября 2011

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

OperationSensor
+ OnKeyDown
+ OnKeyPress
+ OnKeyUp
+ OnLeftMouseDown
+ OnLeftMouseUp
+ OnNodeSelect
+ OnNodeDeselect
+ OnDragStart
+ OnDragStop

Допустим, у вас есть центральный класс, который объединяет все входы пользовательского интерфейса UiInputManager. Он использует специфичные для языка механизмы для прослушивания ввода с клавиатуры и мыши. Он также обнаруживает основные операции, такие как обнаружение того, что если мышь нажать и затем переместить, это логическое «перетаскивание».

UiInputManager
// event listeners
+ keyboard_keydownHandler
+ keyboard_keyupHandler
+ mouse_leftdownHandler
+ mouse_rightdownHandler
// active sensor list, can be added to or removed from
+ Sensors

UiInputManager НЕ несет ответственности за знание того, какие операции вызывают эти входные данные. Он просто уведомляет свои датчики определенным языком.

foreach sensor in Sensors
    sensor.OnDragStarted

или, если датчики прослушивают логические события, выданные UiInputManager

RaiseEvent DragStarted

Теперь у вас есть соединение для маршрутизации ввода в подклассы OperationSensor. Каждый OperationSensor имеет логику, относящуюся только к одной операции. Если он обнаруживает, что критерии операции выполнены, он создает соответствующий объект Command и передает его обратно.

// Ctrl + zooms in, Ctrl - zooms out
ZoomSensor : OperationSensor

   override OnKeyDown
   {
      if keyDown.Char = '+' && keyDown.IsCtrlDepressed
         base.IssueCommand(new ZoomCommand(changeZoomBy:=10)
      elseif keyDown.Char = '-' && keyDown.IsCtrlDepressed
         base.IssueCommand(new ZoomCommand(changeZoomBy:=-10)                                 
   }

Я бы порекомендовал передавать объекты команд из датчиков в UiInputManager. Затем менеджер может передать их в вашу подсистему обработки команд. Это дает менеджеру возможность, возможно, уведомить датчики о завершении операции, что позволяет им при необходимости сбросить свое внутреннее состояние.

Многошаговые операции могут выполняться двумя различными способами. Вы можете реализовать внутренние конечные автоматы внутри SensorOperation, или вы можете заставить датчик «шаг 1» создать датчик «шаг 2» и добавить его в список активных датчиков, возможно, даже удалив себя из списка. По завершении «шага 2» он может повторно добавить датчик «шага 1» и удалить себя.

3 голосов
/ 21 января 2013

a bit опоздал на шоу, но я хотел бы добавить, что общим шаблоном для этого является шаблон-посредник, где вы перемещаете сложность взаимодействий между различными узлами в отдельный класс, посредник (скажем, класс ConnectionCreator). см. шаблоны дизайна от Gamma и коллег: http://ebookbrowse.com/addison-wesley-gamma-helm-johnson-vlissides-design-patterns-elements-of-reusable-object-oriented-pdf-d11349017

2 голосов
/ 23 ноября 2011

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

0 голосов
/ 23 ноября 2011

Для пользовательских интерфейсов MVC в наши дни довольно универсален. Вкратце, M (модель) содержит состояние, V (представление) показывает визуальные элементы, C (контроллер) отправляет входящие пользовательские действия, такие как щелчки мышью. Цель состоит в том, чтобы модель не заботилась непосредственно о представлении, за исключением, возможно, событий стрельбы.

Я бы, возможно, поместил «ум» в модели. Модель будет знать, когда выбран узел, его соседние узлы образуют многоугольник, конечные автоматы и т. Д. Этот дизайн дает вам несколько преимуществ. Это не зависит от деталей рендеринга пользовательского интерфейса; так что вы можете вносить основные изменения в вид, не нарушая функциональность ядра. Это также значительно облегчает юнит-тестирование.

...