簡體   English   中英

MVVM 在視圖之間切換

[英]MVVM Switching Between Views

我是 WPF 的新手,所以請耐心等待。 我有一個 WinForms 應用程序,我試圖在 WPF 中重做。 在我當前的 WinForms 應用程序中,我將所有控件粘貼到一個表單中,並根據點擊的按鈕隱藏/顯示它們,以及使用第二個表單。

我的目標:創建不同的視圖以根據按下的按鈕平滑切換,而不是隱藏控件或制作單獨的 Forms 然后隱藏它們。

我目前有一個 MainWindow 視圖(我的初始啟動窗口),通過一個按鈕,我切換到我的 CreateAccount 視圖。 我遇到的問題是,如何將 CreateAccount go 中的按鈕“返回”到主窗口?

我的最終目標是能夠根據按鈕點擊在 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();
        }
    }
}

我的項目的當前結構

在我看來,你目前的嘗試是在正確的軌道上。 您發布的代碼的主要問題是CreateAccountView.Button_Click()處理程序無權訪問它應該設置的DataContext屬性:

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

DataContext屬性屬於CreateAccountView用戶控件。 但是,這不是顯示內容的控制上下文。 因此,更改該DataContext屬性的值沒有任何有用的效果。 (實際上,用戶控件根本不應該設置它自己的DataContext屬性,因為這樣做會丟棄使用該用戶控件的客戶端代碼設置的任何上下文。)

沒有足夠的上下文來確切地知道您執行此操作最佳方法是什么。 我認為在 Stack Overflow 上無法提供足夠的上下文。 整體架構將取決於有關您的程序的太多小細節。 但是,我認為一種很好的解決方法是:

  • 創建管理應用程序整體行為的“主”視圖 model
  • 創建與 UI 的不同狀態相關的單個視圖模型
  • 讓主視圖 model 配置各個視圖模型以根據用戶輸入(例如單擊按鈕)切換當前視圖 model

把它翻譯成代碼,看起來像這樣……

首先,視圖模型:

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);
    }
}

當然,這些視圖模型包含處理 UI 切換所需的實現細節。 在您的程序中,每個還包括特定於您需要的每個視圖 state 的內容。

在我的小示例中,“主頁”視圖包含幾個按鈕,用於 select 可用的各個子視圖:

<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>

子視圖只包含 go 返回主視圖所需的按鈕:

<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 設置主視圖 model,並聲明要用於每個特定子視圖的模板:

<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>

重要的是,您會看到沒有任何視圖對象包含任何代碼隱藏。 當您以這種方式處理問題時,沒有必要這樣做,至少不是為了控制代碼中的基本行為。 (您可能仍會使用視圖對象的代碼隱藏,但這通常僅用於實現該視圖 object 獨有的特定用戶界面行為,而不是用於處理視圖 model Z9ED39E2EA9314286B6A985A673E)

使用這種方法,您可以讓 WPF 盡可能多地完成繁重的工作。 它還將所有視圖 model 對象彼此分離。 有一個清晰的層次結構:只有頂級“主”視圖 model 甚至知道其他視圖模型。 這允許在其他場景中根據需要重用子視圖模型(“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();
}

usually... you would have one main window and sub windows or user controls... let's suppose your MainWindow.xaml is the main window and you have a UserControl1 and UserControl2... and you want to switch between them... here您 go:您可以將 ContentPresenter 上的“Content”屬性綁定到 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>

選擇更改時,您不能更新您的視圖模型......您的視圖模型必須保留那里的值,您只需切換 userControls

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM