点击按钮更改视图

9
我现在正在学习WPF和MVVM(或者至少我在尝试...)。
我创建了一个小的示例应用程序,显示一个带有2个按钮的窗口,每个按钮应该在单击时显示一个新视图。因此我创建了3个用户控件(带有2个按钮的DecisonMaker,以及每个“clicktarget”的一个用户控件)。
因此,我将MainWindow的CotentControl绑定到MainWindowViewModel中名为“CurrentView”的属性。
MainWindow.xaml代码如下:
<Window x:Class="WpfTestApplication.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfTestApplication"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Grid>
    <ContentControl Content="{Binding CurrentView, Mode=OneWay}" />
</Grid>
</Window>

MainWindowViewModel 的代码:

class MainWindowViewModel
{      
    private UserControl _currentView = new DecisionMaker();
    public UserControl CurrentView
    {
        get { return _currentView; }
        set { _currentView = value; }
    }

    public ICommand MausCommand
    {
        get { return new RelayCommand(LoadMouseView); }
    }

    public ICommand TouchCommand
    {
        get { return new RelayCommand(LoadTouchView); }
    }

    private void LoadMouseView()
    {
        CurrentView = new UserControlMouse();
    }

    private void LoadTouchView()
    {
        CurrentView = new UserControlTouch();
    }
}

最初的UserControl(DecisionMaker)显示正常。也调用了LoadMouseView方法。但是视图没有改变。我错过了什么?

更新:非常感谢!我错过了INotifyPropertyChanged接口。你们所有人的回答都非常好,准确和有帮助!我不知道该接受哪一个 - 我认为接受“第一个”答案是最公平的方式吗?

我接受了blindmeis的答案,因为它解决了问题并帮助我更好地理解了MVVM。但每个答案都非常棒,感谢你们所有人!


1
Viewmodel应该没有对视图/用户控件的引用,因此您应该从您的viewmodel中删除它。这是一个很好的起点:http://msdn.microsoft.com/en-us/magazine/dd419663.aspx - blindmeis
5个回答

7
如果您想要使用MVVM架构,那么您的视图/用户控件在视图模型中不应有任何引用。您必须实现INotifyPropertyChanged接口!顺便说一句:如果您需要在视图模型中使用System.Windows命名空间,那么就出了些问题。
在您的情况下,您需要:
  • 1个主视图模型
  • 1个用于UserControlMouse的视图模型
  • 1个用于UserControlTouch的视图模型
  • 1个用于UserControlMouse的视图/用户控件
  • 1个用于UserControlTouch的视图/用户控件
您的主视图模型应该至少有2个命令来切换视图和1个当前视图属性。在您的命令中,您只需将CurrentView设置为正确的视图模型实例。至少,您需要为每个视图模型定义正确的视图的两个数据模板。
public object CurrentView
{
    get { return _currentView; }
    set {
        _currentView = value; this.RaiseNotifyPropertyChanged("CurrentView");}
}

xaml

<Window x:Class="WpfTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestApplication"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
 <DataTemplate DataType="{x:Type local:MyMouseViewModel}">
   <local:MyMouseUserControlView/>
  </DataTemplate>
 <DataTemplate DataType="{x:Type local:MyTouchViewModel}">
   <local:MyTouchUserControlView/>
  </DataTemplate>
</Window.Resources>
<Window.DataContext>
 <local:MainWindowViewModel />
</Window.DataContext>
<Grid>

 <!-- here your buttons with command binding, i'm too lazy to write this. -->

 <!-- you content control -->
 <ContentControl Content="{Binding CurrentView, Mode=OneWay}" />
</Grid>
</Window>

这对我的学习进展来说非常有帮助和信息。我昨天刚开始学习WPF/MVVM,所以我已经很高兴能让ICommand-Behaviour工作了;) - basti
如果您想要纯粹的MVVM,那么视图也不应该引用视图模型。在应用程序启动时实例化视图和视图模型,并在启动方法中将视图模型绑定到数据上下文。 - Merlyn Morgan-Graham
如果你需要在你的ViewModel中使用System.Windows命名空间,那么肯定有什么地方出了问题。我非常赞同这一点;不幸的是,许多MVVM文章和框架建议从ICommand派生命令,这会在ViewModel中引入不必要的依赖项,从而导致对WPF程序集的依赖。 - user128300
@blindmeis:只有一个问题->我真的需要在这里使用两个视图模型吗?我的意思是说,两个视图肯定需要。难道不能只使用一个视图模型吗? - basti
你可以在一个ViewModel中完成所有操作,但是为每个View/UserControl使用一个ViewModel会更容易/松散耦合。尝试一下并看看优缺点。如果您使用DataTemplate,则需要一个对象类型。这就是为什么我将我的关注点分离到不同的ViewModel中的原因。 - blindmeis
很抱歉我这么迟钝,但被省略的那部分是我不理解的部分。在处理按钮点击时,似乎需要为每个DataTemplate提供一个键,但似乎没有提供。 - Kevin Burton

3
我会这样做来选择所需的输入方式,我已经在MainWindow中添加了一个属性,让我可以选择输入模式。
public enum UserInterfaceModes
{
    Mouse,
    Touch,
}

public UserInterfaceModes UserInterfaceMode
{
   get { return (UserInterfaceModes)GetValue(UserInterfaceModeProperty); }
   set { SetValue(UserInterfaceModeProperty, value); }
}

public static readonly DependencyProperty UserInterfaceModeProperty = DependencyProperty.Register("UserInterfaceMode", typeof(UserInterfaceModes), typeof(MainWindow), new UIPropertyMetadata(UserInterfaceModes.Mouse));

对于 XAML 视图部分,您可以使用触发器选择正确的模板。

<Style TargetType="{x:Type local:MainWindow}">
   <Style.Triggers>
        <DataTrigger Binding="{Binding UserInterfaceMode}" Value="Mouse">
             <Setter Property="Template">
                  <Setter.Value>
                       <ControlTemplate TargetType="{x:Type local:MainWindow}">
                            <Grid Background="Red"/>
                       </ControlTemplate>
                  </Setter.Value>
             </Setter>
        </DataTrigger>
        <DataTrigger Binding="{Binding UserInterfaceMode}" Value="Touch">
             <Setter Property="Template">
                  <Setter.Value>
                       <ControlTemplate TargetType="{x:Type local:MainWindow}">
                           <Grid Background="Blue"/>
                       </ControlTemplate>
                   </Setter.Value>
              </Setter>
         </DataTrigger>
    </Style.Triggers>
</Style>

绝对是一个很棒的答案,这将帮助我在未来!对于我现在面临的测试/学习中的这个具体问题,它并不完全适用,因为它会否定我的整个按钮布局;但在未来的“真正”实现中,我一定会回来使用你的代码! - basti
啊,你会让我害羞的!:D - Andy

2
视图模型需要实现 INotifyPropertyChanged 接口,否则 在视图模型中的属性发生更改时,视图将不会被通知。
class MainWindowViewModel : INotifyPropertyChanged
{
    private UserControl _currentView = new DecisionMaker();

    public UserControl CurrentView
    {
        get { return _currentView; }
        set
        {
            _currentView = value;
            OnPropertyChanged("CurrentView");
        }
    } 

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

谢谢,我已经根据您的回答实现了它,并且它非常好用。但是为什么我需要在这里使用它,而不是在字符串类型的属性中使用呢? - basti

1
您需要在MainWindowViewModel上实现INotifyPropertyChanged,以便在更改CurrentView属性时通知视图。

1

听起来你想要的行为与使用 [TabControl][1] 控件得到的基本相同 - 为什么不使用这个内置控件,只需将两个选项卡的 DataContext 绑定到相同的视图模型。

这也有一个优点,即您的视图模型不会知道视图类(我假设 UserControlMouse 等是用户控件)。

注意:如果您需要视图模型知道它是在触摸还是鼠标模式下,则此方法不适用。


谢谢。这是总体目标,将用户控件分为鼠标模式和触摸模式。因此,在启动时,我需要“知道”起始用户控件。 - basti
@chiffre,如果你想使用MVVM,仍然不应该在视图模型中引用视图组件(用户控件)。 - Steve Greatrex
我想我会在未来的工作中遵循blindmeis的答案。感谢您在这个案例中的大力帮助! - basti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接