好的,我已经为您制作了一个简单的示例,以展示您如何使用MVVM(Model-View-ViewModel)方法和数据绑定动态更改ContentControl的内容。
我建议您创建一个新项目并加载这些文件,以查看它们是如何工作的。
首先,我们需要实现INotifyPropertyChanged接口。这将允许您定义自己的类,并在属性发生更改时通知UI。我们创建一个提供此功能的抽象类。
ViewModelBase.cs
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
现在我们需要有数据模型。为了简单起见,我创建了两个模型 - HomePage 和 SettingsPage。这两个模型只有一个属性,您可以根据需要添加更多属性。
HomePage.cs
public class HomePage
{
public string PageTitle { get; set; }
}
设置页面.cs
public class SettingsPage
{
public string PageTitle { get; set; }
}
我随后创建了对应的ViewModel来封装每个模型。请注意,ViewModels继承自我的ViewModelBase抽象类。
HomePageViewModel.cs
public class HomePageViewModel : ViewModelBase
{
public HomePageViewModel(HomePage model)
{
this.Model = model;
}
public HomePage Model { get; private set; }
public string PageTitle
{
get
{
return this.Model.PageTitle;
}
set
{
this.Model.PageTitle = value;
this.OnPropertyChanged("PageTitle");
}
}
}
设置页面视图模型.cs
public class SettingsPageViewModel : ViewModelBase
{
public SettingsPageViewModel(SettingsPage model)
{
this.Model = model;
}
public SettingsPage Model { get; private set; }
public string PageTitle
{
get
{
return this.Model.PageTitle;
}
set
{
this.Model.PageTitle = value;
this.OnPropertyChanged("PageTitle");
}
}
}
现在我们需要为每个ViewModel提供视图,即HomePageView和SettingsPageView。我为此创建了2个UserControl。
HomePageView.xaml
<UserControl x:Class="WpfApplication3.HomePageView"
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="300" d:DesignWidth="300">
<Grid>
<TextBlock FontSize="20" Text="{Binding Path=PageTitle}" />
</Grid>
设置页面视图.xaml
<UserControl x:Class="WpfApplication3.SettingsPageView"
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="300" d:DesignWidth="300">
<Grid>
<TextBlock FontSize="20" Text="{Binding Path=PageTitle}" />
</Grid>
现在我们需要为MainWindow定义xaml。我添加了两个按钮,以帮助在这两个“页面”之间导航。
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http:
xmlns:x="http:
xmlns:local="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="">
<local:HomePageView />
</DataTemplate>
<DataTemplate DataType="">
<local:SettingsPageView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Left">
<Button Content="Home Page" Command="" />
<Button Content="Settings Page" Command=""/>
</StackPanel>
<ContentControl Content=""></ContentControl>
</DockPanel>
我们还需要一个用于MainWindow的ViewModel。但在此之前,我们需要创建另一个类,以便将按钮绑定到命令。
DelegateCommand.cs
public class DelegateCommand : ICommand
{
private Action<object> executionAction;
private Predicate<object> canExecutePredicate;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.executionAction = execute;
this.canExecutePredicate = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return this.canExecutePredicate == null ? true : this.canExecutePredicate(parameter);
}
public void Execute(object parameter)
{
if (!this.CanExecute(parameter))
{
throw new InvalidOperationException("The command is not valid for execution, check the CanExecute method before attempting to execute.");
}
this.executionAction(parameter);
}
}
现在我们可以定义MainWindowViewModel。CurrentViewModel是绑定到MainWindow上的ContentControl的属性。当我们通过点击按钮更改此属性时,MainWindow上的屏幕会发生变化。MainWindow知道要加载哪个屏幕(usercontrol),因为我在Window.Resources部分中定义了DataTemplates。
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.LoadHomePage();
this.LoadHomePageCommand = new DelegateCommand(o => this.LoadHomePage());
this.LoadSettingsPageCommand = new DelegateCommand(o => this.LoadSettingsPage());
}
public ICommand LoadHomePageCommand { get; private set; }
public ICommand LoadSettingsPageCommand { get; private set; }
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
this.OnPropertyChanged("CurrentViewModel");
}
}
private void LoadHomePage()
{
CurrentViewModel = new HomePageViewModel(
new HomePage() { PageTitle = "This is the Home Page."});
}
private void LoadSettingsPage()
{
CurrentViewModel = new SettingsPageViewModel(
new SettingsPage(){PageTitle = "This is the Settings Page."});
}
}
最终,我们需要覆盖应用程序的启动,以便将MainWindowViewModel类加载到MainWindow的DataContext属性中。
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow() { DataContext = new MainWindowViewModel() };
window.Show();
}
}
在App.xaml应用程序标签中删除StartupUri="MainWindow.xaml"
代码是个好主意,这样我们就不会在启动时得到两个MainWindows。
请注意,DelegateCommand和ViewModelBase类可以复制到新项目中并使用。这只是一个非常简单的例子。您可以从这里和这里获得更好的想法。
编辑
在您的评论中,您想知道是否有可能不必为每个视图和相关的样板文件代码编写一个类。据我所知,答案是否定的。是的,你可以有一个单独的巨大的类,但你仍然需要为每个属性设置器调用OnPropertyChanged。这也有相当多的缺点。首先,产生的类将非常难以维护。那里会有很多代码和依赖关系。其次,使用DataTemplates来“切换”视图将很难。通过在您的DataTemplates中使用x:Key并在usercontrol中硬编码一个模板绑定,这仍然是可能的。实质上,你并没有真正缩短你的代码,但你会让自己更加困难。
我猜你的主要抱怨是在你的viewmodel中写这么多的代码来包装你的model属性。看看T4 templates。一些开发人员使用它来自动生成他们的样板文件代码(即ViewModel类)。我个人不使用这个,我使用一个自定义的code snippet来快速生成一个viewmodel属性。
另一个选项是使用MVVM框架,如Prism或MVVMLight。我没有用过其中任何一个,但我听说它们中的一些已经内置了使样板代码变得容易的功能。
另一个要注意的问题是:
如果您将设置存储在数据库中,可能可以使用ORM框架(如Entity Framework)从数据库生成模型,这意味着您只需要创建viewmodels和views。