Лучший дизайн для форм Windows, которые будут иметь общую функциональность


20

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

  1. Элементы управления могут находиться только в одном контейнере за раз, поэтому любые статические элементы управления будут хитрыми. Например: предположим, у вас есть базовая форма BaseForm, которая содержит TreeView, который вы делаете защищенным и статическим, чтобы все другие (производные) экземпляры этого класса могли изменять и отображать один и тот же TreeView. Это не будет работать для нескольких классов, наследуемых от BaseForm, потому что это TreeView может находиться только в одном контейнере за раз. Скорее всего, это будет в последней инициализированной форме. Хотя каждый экземпляр может редактировать элемент управления, он будет отображаться только по одному в данный момент времени. Конечно, есть обходные пути, но все они безобразны. (Мне кажется, это действительно плохой дизайн. Почему несколько контейнеров не могут хранить указатели на один и тот же объект? В любом случае, так оно и есть.)

  2. Состояние между формами, то есть состояния кнопок, текст метки и т. Д., Я должен использовать глобальные переменные и сбросить состояния при загрузке.

  3. Это не очень хорошо поддерживается дизайнером Visual Studio.

Есть ли лучший, но все же легко обслуживаемый дизайн? Или наследование форм все еще лучший подход?

Обновление Я перешел от просмотра MVC к MVP, к шаблону наблюдателя и к шаблону события. Вот что я думаю на данный момент, пожалуйста, критикуйте:

Мой класс BaseForm будет содержать только элементы управления и события, связанные с этими элементами управления. Все события, для обработки которых требуется какая-либо логика, немедленно передаются в класс BaseFormPresenter. Этот класс будет обрабатывать данные из пользовательского интерфейса, выполнять любые логические операции, а затем обновлять BaseFormModel. Модель будет предоставлять события, которые будут срабатывать при изменении состояния, для класса Presenter, на который она будет подписываться (или наблюдать). Когда докладчик получает уведомление о событии, он выполняет любую логику, а затем докладчик соответствующим образом изменяет представление.

В памяти будет только один из каждого класса Model, но потенциально может быть много экземпляров BaseForm и, следовательно, BaseFormPresenter. Это решило бы мою проблему синхронизации каждого экземпляра BaseForm с той же моделью данных.

Вопросов:

В каком слое должны храниться такие вещи, как последняя нажатая кнопка, чтобы я мог держать ее выделенной для пользователя (как в меню CSS) между формами?

Пожалуйста, критикуйте этот дизайн. Спасибо за вашу помощь!


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

Весь ваш дизайн имеет недостатки. Если вы уже понимаете, что вы хотите сделать, не поддерживается Visual Studio, которая должна вам кое-что сказать.
Ramhound

1
@Ramhound Это поддерживается Visual Studio, но не очень хорошо. Вот как Microsoft говорит вам сделать это. Я просто нахожу это болью в A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspx В любом случае, если у вас есть идея получше, у меня все глаза.
Джонатан Хенсон

@stijn Я полагаю, у меня может быть метод в каждой базовой форме, который будет загружать состояния управления в другой экземпляр. т.е. LoadStatesToNewInstance (экземпляр BaseForm). Я мог бы назвать это в любое время, когда я хочу показать новую форму этого базового типа. form_I_am_about_to_hide.LoadStatesToNewInstance (это);
Джонатан Хенсон

Я не являюсь разработчиком .net, можете ли вы рассказать о пункте 1? У меня может быть решение
Имран Омар Бухш

Ответы:


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

  2. Совместное использование состояния управления (в отличие от данных) между формами также является необычным требованием. Вы уверены, что FormB действительно нужно знать о состоянии кнопок в FormA? Рассмотрим дизайн MVP или MVC. Думайте о каждой форме как о тупом «представлении», которое ничего не знает о других представлениях или даже самом приложении. Контролируйте каждый вид с помощью умного докладчика / контроллера. Если это имеет смысл, один докладчик может контролировать несколько просмотров. Свяжите объект состояния с каждым представлением. Если у вас есть какое-то состояние, которое необходимо разделить между представлениями, позвольте выступающим (ей) выступить посредником (и рассмотрите возможность привязки данных - см. Ниже)

  3. Согласен, Visual Studio доставит вам головную боль. При рассмотрении наследования формы или пользовательского контроля вам необходимо тщательно сопоставить преимущества с потенциальной (и вероятной) стоимостью борьбы с разочаровывающими излишествами и ограничениями дизайнера форм. Я предлагаю свести к минимуму наследование форм - используйте его только при высокой отдаче. Имейте в виду, что в качестве альтернативы подклассам вы можете создать общую «базовую» форму и просто создать ее экземпляр один раз для каждого потенциального «ребенка», а затем настроить ее на лету. Это имеет смысл, когда различия между каждой версией формы незначительны по сравнению с общими аспектами. (IOW: сложная базовая форма, только чуть-чуть более сложные дочерние формы)

Используйте пользовательские контроли, когда это помогает предотвратить значительное дублирование разработки пользовательского интерфейса. Рассмотрите наследование usercontrol, но примените те же соображения, что и для наследования форм.

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

ответ на ваше обновление

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

Вы можете разделить состояние между представлениями так же, как вы бы делили состояние между докладчиком и его представлением. Создайте специальный класс SharedViewState. Для простоты вы можете сделать его одноэлементным или создать его экземпляр в главном докладчике и передать его всем представлениям (через докладчиков) оттуда. Когда состояние связано с элементами управления, по возможности используйте привязку данных. Большинство Controlсвойств могут быть привязаны к данным. Например, свойство BackColor для Button может быть связано со свойством вашего класса SharedViewState. Если вы сделаете эту привязку на всех формах, имеющих одинаковые кнопки, вы можете выделить Button1 на всех формах, просто установив SharedViewState.Button1BackColor = someColor.

Если вы не знакомы с привязкой данных WinForms, нажмите MSDN и немного почитайте. Это не сложно. Узнайте, INotifyPropertyChangedи вы на полпути.

Вот типичная реализация класса viewstate со свойством Button1BackColor в качестве примера:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}

Спасибо за ваш вдумчивый ответ. Знаете ли вы хорошую ссылку на шаблон представления / контроллера?
Джонатан Хенсон

1
Я помню, что думал, что это было довольно хорошее вступление: codebetter.com/jeremymiller/2007/05/22/…
Игби Ларджман

пожалуйста, смотрите мое обновление.
Джонатан Хенсон

@JonathanHenson: ответ обновлен.
Игби Крупный человек

5

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

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

Поскольку я использую шаблон MVVM, я помещаю как можно больше программной логики в ViewModel (включая модель представления / навигации ) или в модель (в зависимости от того, связан ли код с пользовательским интерфейсом или нет), но так как тот же ViewModel используется как представлением WinForms, так и представлением WPF, ViewModel не разрешается содержать код, разработанный для WinForms или WPF или непосредственно взаимодействующий с пользовательским интерфейсом; такой код ДОЛЖЕН идти в поле зрения.

Также в шаблоне MVVM объекты пользовательского интерфейса должны избегать взаимодействия друг с другом! Вместо этого они взаимодействуют с моделями представления. Например, TextBox не будет спрашивать соседний ListBox, какой элемент выбран; вместо этого список сохранит ссылку на текущий выбранный элемент где-нибудь в слое viewmodel, а TextBox запросит слой viewmodel, чтобы узнать, что выбрано прямо сейчас. Это решает вашу проблему с выяснением, какая кнопка была нажата в одной форме из другой формы. Вы просто делите объект модели навигации (который является частью уровня viewmodel приложения) между двумя формами и помещаете свойство в этот объект, которое представляет, какая кнопка была нажата.

WinForms сама по себе не очень хорошо поддерживает шаблон MVVM, но Update Controls предоставляет свой собственный уникальный подход MVVM как библиотека, которая располагается поверх WinForms.

На мой взгляд, этот подход к разработке программ работает очень хорошо, и я планирую использовать его в будущих проектах. Причины, по которым он работает так хорошо: (1) что Update Controls автоматически управляет зависимостями, и (2) обычно очень ясно, как вы должны структурировать свой код: весь код, взаимодействующий с объектами пользовательского интерфейса, принадлежит представлению, весь код, который Связанный с пользовательским интерфейсом, но не ДОЛЖЕН взаимодействовать с объектами пользовательского интерфейса, принадлежит ViewModel. Довольно часто вы будете разбивать свой код на две части: одну часть для представления и другую часть для модели представления. У меня были некоторые трудности при разработке контекстных меню в моей системе, но в конце концов я тоже придумал дизайн для этого.

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


3

Я собираюсь ответить на это, даже если вы уже приняли ответ.

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

В любом случае, у меня была та же проблема, что и у вас: в моем приложении WinForms у меня есть несколько форм, которые разделяют некоторые функции и некоторые элементы управления. Кроме того, все мои формы уже являются производными от базовой формы (назовем ее «MyForm»), которая добавляет функциональные возможности уровня платформы (независимо от приложения). Конструктор форм Visual Studio предположительно поддерживает редактирование форм, которые наследуются от других форм, но в На практике это работает только до тех пор, пока ваши формы не делают ничего, кроме «Привет, мир! - ОК - Отмена».

В итоге я сделал следующее: я держу свой общий базовый класс «MyForm», который довольно сложный, и я продолжаю извлекать из него все формы своего приложения. Однако в этих формах я абсолютно ничего не делаю, поэтому у VS Forms Designer нет проблем с их редактированием. Эти формы состоят исключительно из кода, который конструктор форм создает для них. Затем у меня есть отдельная параллельная иерархия объектов, которые я называю «Суррогаты», которые содержат все функциональные возможности приложения, такие как инициализация элементов управления, обработка событий, сгенерированных формой, элементов управления и т. Д. Существует взаимно-однозначное отношение. соответствие между суррогатными классами и диалогами в моем приложении: есть базовый суррогатный класс, который соответствует «MyForm», затем выводится другой суррогатный класс, который соответствует «MyApplicationForm»,

Каждый суррогат принимает в качестве параметра времени конструирования определенный тип формы и присоединяется к нему, регистрируясь на его события. Он также делегирует базе, вплоть до «MySurrogate», который принимает «MyForm». Этот суррогат регистрируется в событии «Disposed» формы, поэтому, когда форма уничтожается, суррогат вызывает переопределение для себя, чтобы он и все его потомки могли выполнить очистку. (Снятие с учета событий и т. Д.)

До сих пор все работало нормально.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.