MVVM Переключение между представлениями - PullRequest
1 голос
/ 28 сентября 2019

Я новичок в WPF, так что терпите меня.У меня есть приложение WinForms, которое я пытаюсь повторить в WPF.В моем текущем приложении WinForms я помещаю все свои элементы управления в одну форму и скрываю / показываю их в зависимости от нажатия кнопок, а также использую вторую форму.

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

В настоящее время у меня есть представление MainWindow (Мое начальное окно запуска), где с помощью кнопки я переключаюсь намое представление CreateAccount.У меня проблемы с тем, как сделать так, чтобы моя кнопка в моей учетной записи CreateAccount «возвращалась» к моему главному окну?

Моя конечная цель - переключаться между 4 видами на основе нажатий кнопок.

Вот мой MainWindow.xaml

<Window x:Class="MusicPlayer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MusicPlayer"
        xmlns:Views="clr-namespace:MusicPlayer.Views"
        xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
            <Views:CreateAccountView DataContext="{Binding}"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
        <PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
        <ContentPresenter Content="{Binding}"/>
    </Grid>
</Window>

Мой MainWindow.xaml.cs

using System;
using System.Windows;
using MusicPlayer.ViewModels;

namespace MusicPlayer {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e) {
            base.OnClosed(e);

            Application.Current.Shutdown();
        } //end of onClosed

        private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
            DataContext = new CreateAccountViewModel();
        } //end of CreateAccountView_Clicked
    }
}

А вот мой CreateAccount.xaml

<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:Views="clr-namespace:MusicPlayer.Views"
             xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
    </UserControl.Resources>
    <Grid Background="White">
        <Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
    </Grid>
</UserControl>

И мой CreateAccountView.xaml.cs

using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;

namespace MusicPlayer.Views {
    public partial class CreateAccountView : UserControl {
        //public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));

        public CreateAccountView() {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e) {
            DataContext = new MainWindowViewModel();
        }
    }
}

Current Structure of my Project

Ответы [ 2 ]

1 голос
/ 29 сентября 2019

Мне кажется, что ваша текущая попытка находится на правильном пути.Основная проблема с кодом, который вы разместили, заключается в том, что обработчик CreateAccountView.Button_Click() не имеет доступа к свойству DataContext, которое он должен устанавливать:

private void Button_Click(object sender, RoutedEventArgs e) {
    DataContext = new MainWindowViewModel();
}

Это свойство DataContext принадлежит CreateAccountView пользовательский контроль.Однако это не контролирующий контекст для того, что отображается.Таким образом, изменение значения этого свойства DataContext не имеет никакого полезного эффекта.(Действительно, пользовательский элемент управления вообще не должен устанавливать свое собственное свойство DataContext, поскольку при этом отбрасывается любой контекст, который был установлен клиентским кодом, использующим этот пользовательский элемент управления.)

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

  • Создание "основной" модели представления, которая управляет общим поведением приложения
  • Создание индивидуумамодели представлений, относящиеся к различным состояниям пользовательского интерфейса
  • Попросите модель основного вида сконфигурировать отдельные модели представлений для переключения соответствующей модели текущего представления с учетом пользовательского ввода (например, нажатия кнопок)

Перевод этого в код выглядит примерно так…

Во-первых, модели представлений:

class MainViewModel : NotifyPropertyChangedBase
{
    private object _currentViewModel;
    public object CurrentViewModel
    {
        get => _currentViewModel;
        set => _UpdateField(ref _currentViewModel, value);
    }

    private readonly HomeViewModel _homeViewModel;
    private readonly Sub1ViewModel _sub1ViewModel;
    private readonly Sub2ViewModel _sub2ViewModel;

    public MainViewModel()
    {
        _sub1ViewModel = new Sub1ViewModel
        {
            BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
        };

        _sub2ViewModel = new Sub2ViewModel
        {
            BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
        };

        _homeViewModel = new HomeViewModel
        {
            ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
            ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
        };

        CurrentViewModel = _homeViewModel;
    }
}

class HomeViewModel : NotifyPropertyChangedBase
{
    private ICommand _showSub1Command;
    public ICommand ShowSub1Command
    {
        get => _showSub1Command;
        set => _UpdateField(ref _showSub1Command, value);
    }

    private ICommand _showSub2Command;
    public ICommand ShowSub2Command
    {
        get => _showSub2Command;
        set => _UpdateField(ref _showSub2Command, value);
    }
}

class Sub1ViewModel : NotifyPropertyChangedBase
{
    private ICommand _backCommand;
    public ICommand BackCommand
    {
        get => _backCommand;
        set => _UpdateField(ref _backCommand, value);
    }
}

class Sub2ViewModel : NotifyPropertyChangedBase
{
    private ICommand _backCommand;
    public ICommand BackCommand
    {
        get => _backCommand;
        set => _UpdateField(ref _backCommand, value);
    }
}

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

В моем небольшом примере «домашний» вид содержит пару кнопок, используемых для выбора отдельных доступных подвидов.:

<UserControl x:Class="WpfApp1.HomeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
  <StackPanel Orientation="Horizontal">
    <TextBlock Text="Home: "/>
    <Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
    <Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
  </StackPanel>
</UserControl>

Вспомогательные виды просто содержат кнопку, необходимую для возврата к исходному виду:

<UserControl x:Class="WpfApp1.Sub1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
  <StackPanel Orientation="Horizontal">
    <TextBlock Text="Sub1 View: "/>
    <Button Content="Back" Command="{Binding BackCommand}"/>
  </StackPanel>
</UserControl>

<UserControl x:Class="WpfApp1.Sub2View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
  <StackPanel Orientation="Horizontal">
    <TextBlock Text="Sub2 View: "/>
    <Button Content="Back" Command="{Binding BackCommand}"/>
  </StackPanel>
</UserControl>

Наконец, главное окно устанавливает модель основного вида и объявляет шаблоны дляиспользуйте для каждого из определенных вложенных представлений:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.DataContext>
    <l:MainViewModel/>
  </Window.DataContext>
  <Window.Resources>
    <DataTemplate DataType="{x:Type l:HomeViewModel}">
      <l:HomeView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type l:Sub1ViewModel}">
      <l:Sub1View/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type l:Sub2ViewModel}">
      <l:Sub2View/>
    </DataTemplate>
  </Window.Resources>
  <StackPanel>
    <ContentControl Content="{Binding CurrentViewModel}"/>
  </StackPanel>
</Window>

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

Используя этот подход, вы позволяете WPF выполнять как можно больше тяжелой работы.Он также отделяет все объекты модели представления друг от друга.Существует четкая иерархия: только модель основного уровня верхнего уровня знает даже о других моделях представления.Это позволяет повторно использовать модели подвидов ("home", "sub1" и "sub2") при необходимости в других сценариях без каких-либо изменений или обработки в особых случаях.

Вот вспомогательные классы, которые я использовал выше:

class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void _UpdateField<T>(ref T field, T newValue,
        Action<T> onChangedCallback = null,
        [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        T oldValue = field;

        field = newValue;
        onChangedCallback?.Invoke(oldValue);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
class DelegateCommand : ICommand
{
    private readonly Action _execute;

    public DelegateCommand(Action execute)
    {
        _execute = execute;
    }

#pragma warning disable 67
    public event EventHandler CanExecuteChanged;
#pragma warning restore

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter) => _execute();
}
0 голосов
/ 28 сентября 2019

обычно ... у вас было бы одно главное окно и подокна или пользовательские элементы управления ... давайте предположим, что ваш MainWindow.xaml является главным окном и у вас есть UserControl1 и UserControl2 ... и вы хотите переключаться между ними... вот и вы: вы можете связать свойство «Content» в ContentPresenter со свойством в вашем MainWindow.xaml.cs И это свойство будет изменено в обработчике кликов вашими кнопками (или списком…) Предположим, вы назвали это свойство"SelectedUserControl" Вот ваш MainWindow.xaml.cs:

public partial class MainWindow : Window,INotifyPropertyChanged
{
private UserControl1 myUserControl1= new UserControl1(){DataContext= new VM1()};
private UserControl2 myUserControl2= new UserControl2(){DataContext= new VM2()};
private UserControl _selectedUserControl;
public UserControl SelectedUserControl
{
get => _selectedUserControl;
set
{
_selectedUserControl = value;
OnPropertyChanged();
}
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
SelectedUserControl = myUserControl1;
}
private void Button2_OnClick(object sender, RoutedEventArgs e)
{
SelectedUserControl = myUserControl2;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

А для вас MainWindow.xaml: вы можете изменить его на:

<ContentPresenter Content="{Binding  SelectedUserControl,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"></ContentPresenter>
<Button  Click="Button1_OnClick" >change to usercontrol 1</Button>
<Button  Click="Button2_OnClick" >change to usercontrol 2</Button>

Вы не должны обновлять свою модель представленияпри выборе изменено…. Ваши viewModels должны сохранять там значения, вы просто переключаете userControls

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