WPF Перемещение по представлениям с использованием шаблона MVVM
Я создаю свой первый WPF с использованием шаблона MVVM. С помощью этого сообщества мне удалось создать мою модель, мою первую ViewModel и представление. Теперь я хочу добавить некоторую сложность в приложение, разрабатывающее базовый интерфейс макета приложения. Моя идея состоит в том, чтобы иметь как минимум 2 дочерних вида и один основной вид и разделить их на несколько XAML:
- Main.XAML
- Products.XAML
- Clients.XAML
В главном меню есть меню и пространство для загрузки дочерних представлений (Продукты и Клиенты). Теперь, следуя шаблону MVVM, вся логика навигации между представлениями должна записываться в ViewModel. Итак, идея mi состоит в том, чтобы иметь 4 ViewModels:
- MainViewModel
- ProductsViewModel
- ClientsViewModel
- NavigationViewModel
Итак, NavigationViewModel должен содержать набор дочерних моделей просмотра? и активная модель просмотра - это правильно?
Итак, мои вопросы:
1) Как загрузить различные представления (Продукты, Клиенты) в главном представлении с использованием шаблона MVVM?
2) Как реализовать навигацию viewModel?
3) Как я могу контролировать максимальное количество открытых или активных просмотров?
4) Как я могу переключаться между открытыми представлениями?
Я делал много поиска и чтения и не мог найти простой рабочий пример навигации MVVM с WPF, который загружает несколько видов внутри основного вида. Многие из них:
1) Используйте внешний инструментарий, который я не хочу использовать прямо сейчас.
2) Поместите весь код для создания всех представлений в одном файле XAML, что не кажется хорошей идеей, потому что мне нужно реализовать около 80 просмотров!
Я на правильном пути здесь? Любая помощь, особенно с некоторым кодом, будет оценена.
UPDATE
Итак, я создаю тестовый проект, следующий за советами @LordTakkera, но застрял. Вот как выглядит мое решение:
![Solution]()
Я создаю:
-
Две модели (клиенты и продукты)
-
Один MainWindow и два пользовательских элемента управления wpf (клиенты и продукты) XAML.
-
Три модели ViewModels (клиенты, продукты и основная модель просмотра)
Затем я устанавливаю dataContext для каждого представления в соответствующий viewModel. После этого я создаю MainWindow с ContentPresenter, как это, и привязываю его к свойству viewmodel.
MainWindow.xaml
<Window x:Class="PruevaMVVMNavNew.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="519" Width="890">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
<Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
<Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>
<ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1">
<Button>Clients</Button>
<Button>Products</Button>
</StackPanel>
</Grid>
А также это viewmodel из MainWindow:
class Main_ViewModel : BaseViewModel
{
public Main_ViewModel()
{
CurrentView = new Clients();
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
Таким образом, эта загрузка по умолчанию открывается клиентами и выглядит так (это правильно!):
![Current state]()
Итак, я полагаю, мне нужен способ связать кнопки слева, с определенной viemodel, а затем привязать их к CurrentView Property Main viewModel. Как я могу это сделать?
UPDATE2
В соответствии с советом @LordTakkera я изменяю свой основной видModel следующим образом:
class Main_ViewModel : BaseViewModel
{
public ICommand SwitchViewsCommand { get; private set; }
public Main_ViewModel()
{
//CurrentView = new Clients();
SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
Я использую RelayCommand вместо DelegateCommand, но я думаю, что он работает одинаково. Команда выполняется, когда я нажимаю кнопки, а строка параметров типа - в порядке, но я получаю эту ошибку:
![Error]()
Перевод: Значение не может быть нулевым. Имя параметра: тип. Предложение использовать Новое ключевое слово для создания экземпляра объекта
Я не знаю, где поставить ключевое слово New. Я попробовал CommandParameter, но он не будет работать. Есть идеи? Благодаря
ОБНОВЛЕНИЕ 3
После всех советов и помощи, полученных здесь, и большой работы, вот мое окончательное меню навигации и база для моего интерфейса приложения.
![Capture 1]()
![Capture 2]()
Ответы
Ответ 1
Я не уверен, что вам нужна отдельная модель просмотра "Навигация", вы можете легко включить ее в основное. В любом случае:
Чтобы отделить ваши "дочерние" представления, я бы использовал простой ContentPresenter в вашем "основном" представлении:
<ContentPresenter Content="{Binding CurrentView}"/>
Самый простой способ реализовать свойство backing - сделать его UserControl
, хотя некоторые утверждают, что это нарушает MVVM (поскольку ViewModel теперь зависит от класса "Вид" ). Вы можете сделать это объектом, но вы потеряете некоторую безопасность. В этом случае каждое представление будет UserControl.
Чтобы переключаться между ними, вам понадобится какой-то контроль выбора. Я сделал это с помощью переключателей раньше, вы связываете их так:
<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>
Конвертер довольно прост, в "Конвертировать" он просто проверяет, является ли текущий элемент управления типом параметра, в "ConvertBack" он возвращает новый экземпляр параметра.
public class InstanceEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (parameter as Type).IsInstanceOfType(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
}
}
Привязка к выпадающему знаку или другому элементу управления выбором будет следовать аналогичной схеме.
Конечно, вы также можете использовать DataTemplates (с селектором, к сожалению, не так, как я делал раньше) и загружать их в ваши ресурсы с помощью объединенных словарей (позволяя отдельный XAML). Я лично предпочитаю маршрут управления пользователями, выбираю, что лучше для вас!
Этот подход является "одним взглядом за раз". Было бы относительно легко преобразовать в несколько видов (ваш UserControl станет набором пользовательских элементов управления, используйте .Contains в конвертере и т.д.).
Чтобы сделать это с помощью кнопок, я бы использовал команды и воспользовался CommandParameter.
Кнопка XAML будет выглядеть так:
<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>
Затем у вас есть команда делегата (учебник здесь), который запускает код активатора из конвертера:
public ICommand SwitchViewsCommand {get; private set;}
public MainViewModel()
{
SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}
Это не в моей голове, но должно быть довольно близко. Дайте мне знать, как это происходит!
Сообщите мне, если я предоставил дополнительную информацию!
Update:
Чтобы ответить на ваши вопросы:
-
Да, каждый раз, когда вы нажимаете кнопку, создается новый экземпляр представления. Вы можете легко исправить это, удерживая Dictionary<Type, UserControl>
, который имеет предварительно созданные представления и индексирует их. В этом случае вы можете использовать Dictonary<String, UserControl>
и использовать простые строки в качестве параметров преобразователя. Недостатком является то, что ваш ViewModel тесно связан с видами представлений, которые он может представить (поскольку он должен заполнять словарь).
-
Класс должен быть удален, если никто другой не ссылается на него (подумайте о обработчиках событий, которые он зарегистрировал).
-
Как вы указываете, за один раз создается только один вид, поэтому вам не нужно беспокоиться о памяти. Вы, конечно, называете конструктора, но это не так дорого, особенно на современных компьютерах, где у нас, как правило, много времени на процессор. Как всегда, ответ на вопросы о производительности - "Benchmark it", потому что только у вас есть доступ к намеченным целям развертывания и всему источнику, чтобы увидеть, что на самом деле работает лучше всего.
Ответ 2
ИМХО лучше всего выбрать для вас использование инфраструктуры MVVM (PRISM, MMVM Light, Chinch и т.д.), потому что навигация уже реализована. Если вы хотите создать свою собственную навигацию - попробуйте DataTemplate.