Существует ли какая-то систематическая стратегия для разработки и реализации GUI?

18

Я использую Visual Studio для создания приложения с графическим интерфейсом в C #. Панель инструментов служит изящной палитрой компонентов, которая позволяет мне легко перетаскивать кнопки и другие элементы (для ясности я скажу кнопку, когда я имею в виду «контроль») на мою форму, что делает статические формы довольно простыми для выполнения. Однако я сталкиваюсь с двумя проблемами:

  • Создание кнопок в первую очередь это большая работа. Когда у меня есть форма, которая не является статичной (т.е. кнопки или другие элементы управления создаются во время выполнения в соответствии с тем, что делает пользователь), я вообще не могу использовать палитру. Вместо этого мне приходится создавать каждую кнопку вручную, вызывая конструктор в любом методе, который я использую, а затем вручную инициализировать, указав высоту кнопки, ширину, положение, метку, обработчик события и так далее. Это очень утомительно, потому что я должен угадать все эти косметические параметры, не имея возможности увидеть, как будет выглядеть форма, а также генерировать много строк повторяющегося кода для каждой кнопки.
  • Заставить кнопки сделать что-то - тоже много работы. Работа с событиями в полнофункциональном приложении - огромная боль. Единственный способ, которым я знаю, как это сделать, - это выбрать кнопку, перейти на вкладку событий в ее свойствах, щелкнуть OnClickсобытие, чтобы оно сгенерировало событие в Formкоде, а затем заполнить тело события. Поскольку я хочу разделить логику и представление, все мои обработчики событий заканчиваются однострочными вызовами соответствующей функции бизнес-логики. Но использование этого для многих кнопок (например, представьте количество кнопок, присутствующих в приложении, подобном MS Word) загрязняет мой код Formдесятками стандартных методов обработки событий, и это трудно поддерживать.

Из-за этого любая программа с графическим интерфейсом, более сложная, чем Hello World, очень непрактична для меня. Чтобы было ясно, у меня нет проблем со сложностью в программах, которые я пишу с минимальным пользовательским интерфейсом - я чувствую, что могу использовать ООП с достаточной степенью компетентности для аккуратной структуризации своего кода бизнес-логики. Но при разработке GUI я застрял. Это кажется таким утомительным, что я чувствую, что заново изобретаю колесо, и где-то там есть книга, объясняющая, как правильно делать GUI, которую я не читал.

Я что-то пропустил? Или все разработчики C # просто принимают бесконечные списки повторяющихся обработчиков событий и кода создания кнопок?


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

  • Использование методов ООП (например, заводской шаблон) для упрощения повторного создания кнопок
  • Объединение множества обработчиков событий в один метод, который проверяет Sender, какая кнопка вызывала ее, и ведет себя соответственно
  • XAML и использование WPF вместо Windows Forms

Вы не должны упоминать ни одного из них, конечно. Это просто мое лучшее предположение относительно того, какой ответ я ищу.

Superbest
источник
На мой взгляд, да, Factory был бы хорошим паттерном, но я вижу гораздо больше паттерна Model-View-Controller с командами, привязанными к элементам управления, а не к событиям напрямую. WPF обычно MVVM, но MVC вполне возможен. В настоящее время у нас есть огромное программное обеспечение в WPF, использующее MVC на 90%. Все команды передаются контроллеру, и он обрабатывает все
Franck
Разве вы не можете создать динамическую форму со всеми возможными кнопками с помощью графического редактора и просто сделать вещи, которые вам не нужны, динамически невидимыми? Похоже, гораздо меньше работы.
Ханс-Петер Стёрр
@hstoerr Но было бы сложно заставить работать макет. Многие кнопки будут перекрываться.
Супербест

Ответы:

22

Существует несколько методологий, которые развивались на протяжении многих лет для решения упомянутых вами проблем, которые, я согласен, являются двумя основными проблемами, которые платформы пользовательского интерфейса должны были решать в последние годы. Исходя из опыта WPF, они рассматриваются следующим образом:

Декларативный дизайн, а не императив

Когда вы описываете кропотливое написание кода для создания экземпляров элементов управления и установки их свойств, вы описываете императивную модель дизайна пользовательского интерфейса. Даже с конструктором WinForms вы просто используете обертку над этой моделью - откройте файл Form1.Designer.cs, и вы увидите весь этот код, находящийся там.

С WPF и XAML - и аналогичными моделями в других средах, конечно же, начиная с HTML, - вы должны описать свой макет и позволить платформе выполнить тяжелую работу по его реализации. Вы используете более умные элементы управления, такие как Panels (Grid или WrapPanel и т. Д.), Чтобы описать взаимосвязь между элементами пользовательского интерфейса, но ожидается, что вы не разместите их вручную. Гибкие концепции, такие как повторяемые элементы управления, такие как Repeater ASP.NET или ItemsControl WPF, помогают создавать динамически масштабируемый пользовательский интерфейс без написания повторяющегося кода, что позволяет динамически представлять объекты данных динамически в виде элементов управления.

Шаблоны данных WPF позволяют вам определять - опять же декларативно - небольшие слепки совершенства пользовательского интерфейса и сопоставлять их с вашими данными; например, список объектов данных Customer может быть привязан к ItemsControl, а другой шаблон данных вызывается в зависимости от того, является ли он обычным сотрудником (использует стандартный шаблон строки сетки с именем и адресом) или основным клиентом, тогда как другой шаблон , с изображением и удобными для доступа кнопками. Опять же, без написания кода в конкретном окне, просто более разумные элементы управления, которые осведомлены о своем контексте данных, и, таким образом, позволяют просто привязать их к вашим данным и позволить им выполнять соответствующие операции.

Привязка данных и разделение команд

Касаясь привязки данных, привязка данных WPF (и, опять же, в других средах, таких как AngularJS) позволяет вам заявить о своем намерении, связав элемент управления (скажем, текстовое поле) с сущностью данных (скажем, именем клиента) и разрешив рамки обрамляют сантехнику. Логика обрабатывается аналогично. Вместо того, чтобы вручную подключать обработчики событий выделенного кода к бизнес-логике на основе контроллера, вы используете механизм привязки данных, чтобы связать поведение контроллера (скажем, свойство Command кнопки) с объектом Command, который представляет самородок действия.

Это позволяет этой Команде быть разделенной между окнами, не переписывая обработчики событий каждый раз.

Более высокий уровень абстракции

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

Идея состоит в том, что вы не хотите определять в коде каждое отдельное свойство, которым обладает элемент управления, и каждое отдельное поведение, начиная с нажатия кнопки и далее. Вы хотите, чтобы ваши элементы управления и базовая структура выполняли за вас больше работы и позволяли вам думать о более абстрактных понятиях привязки данных (которая существует в WinForms, но далеко не так полезна, как в WPF) и шаблоне Command для определения связей между пользовательским интерфейсом. и поведение, которое не требует спуска к металлу.

MVVM шаблон является подход Microsoft к этой парадигме. Я предлагаю прочитать об этом.

Это грубый пример того, как эта модель будет выглядеть и как она сэкономит вам время и количество строк кода. Это не скомпилируется, но это псевдо-WPF. :)

Где-то в ресурсах вашего приложения вы определяете шаблоны данных:

<DataTemplate x:DataType="Customer">
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

<DataTemplate x:DataType="PrimeCustomer">
   <Image Source="GoldStar.png"/>
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

Теперь вы связываете свой главный экран с ViewModel, классом, который предоставляет ваши данные. Скажем, коллекция Customers (просто a List<Customer>) и объект Command (опять же, простое публичное свойство типа ICommand). Эта ссылка позволяет связать:

public class CustomersnViewModel
{
     public List<Customer> Customers {get;}
     public ICommand RefreshCustomerListCommand {get;}  
}

и пользовательский интерфейс:

<ListBox ItemsSource="{Binding Customers}"/>
<Button Command="{Binding RefreshCustomerListCommand}">Refresh</Button>

Вот и все. Синтаксис ListBox извлекает список клиентов из ViewModel и пытается отобразить их в пользовательском интерфейсе. Из-за двух DataTemplates, которые мы определили ранее, он импортирует соответствующий шаблон (на основе DataType, предполагая, что PrimeCustomer наследует от Customer) и поместит его в качестве содержимого ListBox. Нет циклов, нет динамической генерации элементов управления с помощью кода.

Аналогично, у Button есть уже существующий синтаксис, который связывает его поведение с реализацией ICommand, которая, по-видимому, знает, как обновить Customersсвойство, - предлагая инфраструктуре привязки данных автоматически обновлять пользовательский интерфейс снова.

Конечно, я взял несколько ярлыков, но в этом суть.

Авнер Шахар-Каштан
источник
5

Во-первых, я рекомендую эту серию статей: http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-of-contents/ - здесь не рассматриваются проблемы с деталями. с вашим кодом кнопки, но он дает вам систематическую стратегию для разработки и реализации вашей собственной прикладной среды, особенно для приложений с графическим интерфейсом, показывая, как применять MVC, MVP, MVVM к вашему типичному приложению Forms.

Отвечая на ваш вопрос о «работе с событиями»: если у вас много форм с кнопками, работающими аналогичным образом, вероятно, вы окупите себя, если самостоятельно внедрите назначение обработчика событий в вашей «прикладной среде». Вместо назначения событий щелчка с помощью дизайнера GUI создайте соглашение об именах, как должен называться обработчик «щелчка» для кнопки с конкретным именем. Например - для кнопки MyNiftyButton_ClickHandlerдля кнопки MyNiftyButton. Тогда на самом деле нетрудно написать подпрограмму многократного использования (использующую отражение), которая перебирает все кнопки формы, проверяет, содержит ли форма связанный обработчик щелчков, и автоматически назначает обработчик событию. Смотрите здесь для примера.

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

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

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

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