原文 使用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项目:
在我们的项目中创建2个新的WPF用户控件,View1.xaml和View2.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"
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"
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。这些只是我们极简主义实现的空类:
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的新类:
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);
}
}
}
}
|
我们现在可以运行该应用程序来演示如何通过按下按钮来实现视图切换:
然后切换到下一个视图: