使用MVVM DataTemplate在WPF XAML视图之间切换

原文 使用MVVM DataTemplate在WPF XAML视图之间切换

更新:这个技术的改进版本,一个不创建视图,可以在以下链接找到:

http://www.technical-recipes.com/2018/navigating-between-views-in-wpf-mvvm/

已经在许多博客/网站论坛上讨论过这种技术,包括:

https://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/ 
http://stackoverflow.com/questions/19654295/wpf-mvvm-navigate-views 
http:// stackoverflow .COM /问题/ 10993385 /改变-视图- buttonclick

我认为分享这种技术的工作版本的实现是有用的。

完整的Visual Studio项目可以从这里下载:

 

http://www.technical-recipes.com/Downloads/MvvmSwitchViews.zip

总而言之,您的应用程序至少应该实现以下内容:

一个ViewModel,它包含一个定义当前视图的属性,以便更改视图以切换ViewModel的属性。
ViewModel需要实现INotifyPropertyChanged,否则在属性更改时不会通知视图。
一个ContentControl,其内容绑定到当前视图。
您希望切换的每个视图的一些DataTemplates。

要开始,请在Visual Studio中创建一个新的WPF项目:

使用MVVM DataTemplate在WPF XAML视图之间切换

在我们的项目中创建2个新的WPF用户控件,View1.xaml和View2.xaml:

使用MVVM DataTemplate在WPF XAML视图之间切换

View1.xaml

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
<UserControl x:Class="MvvmSwitchViews.View1"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Button
            Content="Goto View 2"
            Command="{Binding GotoView2Command}"          
            HorizontalAlignment="Center"              
            Margin="10,10,0,0"
            VerticalAlignment="Center"
            Width="75">
        </Button>
    </Grid>
</UserControl>

View2.xaml

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
<UserControl x:Class="MvvmSwitchViews.View2"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Button
            Content="Goto View 1"
            Command="{Binding GotoView1Command}"          
            HorizontalAlignment="Center"              
            Margin="10,10,0,0"
            VerticalAlignment="Center"
            Width="75">
        </Button>
    </Grid>
</UserControl>

为每个视图创建ViewModel:View1ViewModel和View2ViewModel。这些只是我们极简主义实现的空类:

使用MVVM DataTemplate在WPF XAML视图之间切换

View1ViewModel.cs

1
2
3
4
6
namespace MvvmSwitchViews
{
   public class View1ViewModel
   {
   }
}

View2ViewModel.cs

1
2
3
4
6
namespace MvvmSwitchViews
{
   public class View2ViewModel
   {
   }
}

修改MainWindow.xaml以包含DataTemplate和CurrentView绑定:

MainWindow.xaml

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Window x:Class="MvvmSwitchViews.MainWindow"
        xmlns:local="clr-namespace:MvvmSwitchViews" 
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:View1ViewModel}">
            <local:View1/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:View2ViewModel}">
            <local:View2/>
        </DataTemplate>
    </Window.Resources>
 
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
 
    <Grid>
        <ContentControl Content="{Binding CurrentView}" />
    </Grid>
</Window>

然后,我们为MainWindow.xaml创建一个名为MainWindowViewModel的新ViewModel:

MainWindowViewModel.cs

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System.Windows.Input;
 
namespace MvvmSwitchViews
{
   public class MainWindowViewModel : ViewModelBase
   {
      private ICommand _gotoView1Command;
      private ICommand _gotoView2Command;
      private object _currentView;
      private object _view1;
      private object _view2;
 
      public MainWindowViewModel()
      {
         _view1 = new View1();
         _view2 = new View2();
 
         CurrentView = _view2;
      }
 
      public object GotoView1Command
      {
         get
         {
            return _gotoView1Command ?? (_gotoView1Command = new RelayCommand(
               x =>
               {
                  GotoView1();
               }));
         }
      }
 
      public ICommand GotoView2Command
      {
         get
         {
            return _gotoView2Command ?? (_gotoView2Command = new RelayCommand(
               x =>
               {
                  GotoView2();
               }));
         }
      }
 
      public object CurrentView
      {
         get { return _currentView; }
         set
         {
            _currentView = value;
            OnPropertyChanged("CurrentView");
         }
      }
 
      private void GotoView1()
      {
         CurrentView = _view1;
      }
 
      private void GotoView2()
      {
         CurrentView =  _view2;
      }
   }
}

我们还需要实现INotifyPropertyChanged或创建一个实现INotifyPropertyChanged的类。为此,我们创建了一个名为ViewModelBase的新类:

使用MVVM DataTemplate在WPF XAML视图之间切换

ViewModelBase.cs

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.ComponentModel;
using System.Windows.Input;
 
namespace MvvmSwitchViews
{
   public class ViewModelBase : INotifyPropertyChanged
   {     
      public event PropertyChangedEventHandler PropertyChanged;    
      protected void OnPropertyChanged(string propertyName)
      {
         
         var handler = PropertyChanged;
         if (handler != null)
         {
            handler(this, new PropertyChangedEventArgs(propertyName));
         }
      }
   }
}

为我们的事件处理创建另外三个类来实现RelayCommand,EventArgs和EventRaiser:

RelayCommand.cs

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using System;
using System.Windows.Input;
 
namespace MvvmSwitchViews
{
   public class RelayCommand<T> : ICommand
   {
      private readonly Predicate<T> _canExecute;
      private readonly Action<T> _execute;
 
      public RelayCommand(Action<T> execute)
         : this(execute, null)
      {
         _execute = execute;
      }
 
      public RelayCommand(Action<T> execute, Predicate<T> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }
 
      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute((T) parameter);
      }
 
      public void Execute(object parameter)
      {
         _execute((T) parameter);
      }
 
      public event EventHandler CanExecuteChanged
      {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }
   }
 
   public class RelayCommand : ICommand
   {
      private readonly Predicate<object> _canExecute;
      private readonly Action<object> _execute;
 
      public RelayCommand(Action<object> execute)
         : this(execute, null)
      {
         _execute = execute;
      }
 
      public RelayCommand(Action<object> execute, Predicate<object> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }
 
      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute(parameter);
      }
 
      public void Execute(object parameter)
      {
         _execute(parameter);
      }
 
      // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
      // associated views should be enabled whenever a command is invoked
      public event EventHandler CanExecuteChanged
      {
         add
         {
            CommandManager.RequerySuggested += value;
            CanExecuteChangedInternal += value;
         }
         remove
         {
            CommandManager.RequerySuggested -= value;
            CanExecuteChangedInternal -= value;
         }
      }
 
      private event EventHandler CanExecuteChangedInternal;
 
      public void RaiseCanExecuteChanged()
      {
         CanExecuteChangedInternal.Raise(this);
      }
   }
}

EventArgs.cs

1
2
3
4
6
7
8
9
10
11
12
13
14
using System;
 
namespace MvvmSwitchViews
{
   public class EventArgs<T> : EventArgs
   {
      public EventArgs(T value)
      {
         Value = value;
      }
 
      public T Value { get; private set; }
   }
}

EventRaiser.cs

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
using System;
 
namespace MvvmSwitchViews
{
   public static class EventRaiser
   {
      public static void Raise(this EventHandler handler, object sender)
      {
         if (handler != null)
         {
            handler(sender, EventArgs.Empty);
         }
      }
 
      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
      {
         if (handler != null)
         {
            handler(sender, new EventArgs<T>(value));
         }
      }
 
      public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }
 
      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }
   }
}

我们现在可以运行该应用程序来演示如何通过按下按钮来实现视图切换:

使用MVVM DataTemplate在WPF XAML视图之间切换

然后切换到下一个视图:

使用MVVM DataTemplate在WPF XAML视图之间切换