Подтвердить что ты не робот

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 1Capture 2

4b9b3361

Ответ 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.