如何在MVVM应用程序中执行子视图模型中声明的命令?

时间:2014-01-05 19:03:43

标签: c# wpf mvvm

我有一个MainWindowVM和从中继承的多个子视图模型。 MainWindowVM继承自ViewModelBase,后者实现了INotifyPropertychanged。

每个视图都将DataContext设置为MainWindowVM中定义的CurrentViewModel和每个按钮 已经绑定了命令。

如果我将命令(以及构造函数中的其他命令处理代码)放在MainWindowVM中, 每个视图中的按钮单击按预期工作。我在MainWindowVM的构造函数中将MainControlVM设置为CurrentViewModel。 除MainControlVM和MainWindowVM外,在任何其他VM中设置命令意味着它们不会执行。

但是,我想只在他们使用的虚拟机中拥有命令。

我在MVVM上发现了许多只有一个或两个视图模型的教程,所以这种情况对他们来说不是问题。

编辑包含代码: 这是相关的代码:

XAML中具有绑定的其中一个子视图的一部分:

<Grid  DataContext="{Binding CurrentViewModel}" Margin="0,0,-186,0">
    <Button Content="Add" HorizontalAlignment="Left" Margin="25,249,0,0" VerticalAlignment="Top" Width="62" Height="32" 
Command="{Binding AddCategoryVMCommand}" />

MainWindowVM类包含:

   public ICommand AddCategoryVMCommand { get; private set; }

,在构造函数中:

AddCategoryVMCommand = new RelayCommand(() => ExecuteAddCategoryVMCommand());                   

    protected void ExecuteAddCategoryVMCommand()
    {
        CurrentViewModel = new AddCategoryVM();
    }

....和每个命令的相同类型的代码。 Aso,CurrentViewModel在MainWindowVM类中设置。这是MainWindow视图用于确定要与datatemplate一起显示的视图的属性:

        public ViewModelBase CurrentViewModel
    {
        get { return _currentViewModel; }
        set
        {
            if (_currentViewModel == value)
                           return;
            _currentViewModel = value; 
            this.RaiseNotifyPropertyChanged("CurrentViewModel");
        }
    }

如何在子视图模型中声明时执行命令?

3 个答案:

答案 0 :(得分:4)

有很多评论正在进行,所有评论都不同步,它们似乎使问题卷曲,所以我想我会尝试用一个基本的例子来解决你的问题。该示例仅处理您看起来具有的命令绑定问题。

我创建了3个ViewModel,MyViewModel1和MyViewModel2是MyViewModel的派生。在ViewModel基础中定义了一个命令,用于加载CurrentViewModel。其他2个ViewModels包含自己的命令。

public class MyViewModel : INotifyPropertyChanged
{
    private MyViewModel currentViewModel;

    public RelayCommand<object> MyCommand { get; set; } 

    public MyViewModel()
    {
        MyCommand = new RelayCommand<object>(MyCommandExecute);
    }

    public MyViewModel CurrentViewModel
    {
        get { return currentViewModel; }
        set
        {
            if (value != currentViewModel)
            {
                currentViewModel = value;
                OnPropertyChanged();
            }
        }
    }

    protected virtual void MyCommandExecute(object obj)
    {
        switch (int.Parse(obj.ToString()))
        {
            case 1:
                CurrentViewModel = new MyViewModel1();
                break;
            case 2:
                CurrentViewModel = new MyViewModel2();
                break;
        }
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class MyViewModel1 : MyViewModel
{

    public RelayCommand<object> MyCommand1 { get; set; }

    public MyViewModel1()
    {
        MyCommand1 = new RelayCommand<object>(MyCommand1Execute);
    }

    private void MyCommand1Execute(object obj)
    {
        Debug.WriteLine("MyCommand1");
    }
}

public class MyViewModel2 : MyViewModel
{

    public RelayCommand<object> MyCommand2 { get; set; }

    public MyViewModel2()
    {
        MyCommand2 = new RelayCommand<object>(MyCommand2Execute);
    }

    private void MyCommand2Execute(object obj)
    {
        Debug.WriteLine("MyCommand2");
    }
}

UserControl1背后的代码是

public partial class UserControl1 : UserControl
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel1), typeof(UserControl1));

    public UserControl1()
    {
        InitializeComponent();
    }

    public MyViewModel1 ViewModel
    {
        get { return GetValue(ViewModelProperty) as MyViewModel1; }
        set { SetValue(ViewModelProperty, value); }
    }
}

我已经将ViewModel属性创建为DependencyProperty,因此我可以从MainWindow绑定它。

用户控件的Xaml是

<UserControl x:Class="StackOverflow._20937791.UserControl1" 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:this="clr-namespace:StackOverflow._20937791"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
        <Button Content="View 1 Command" Command="{Binding Path=MyCommand1}" />
    </StackPanel>
</UserControl>

注意我已在控件的第一个内容元素上设置了DataContext。所有子元素的绑定都是针对UserControl的ViewModel,而任何传入的绑定(来自父控件)都将从该父控件的DataContext进行评估。

需要注意的另一点是,通过在Xaml中定义DataContext,您将在Binding表达式中获得自动完成功能,这将减少错误的表达式错误。

第二个UserControl是相同的,但ViewModel的类型是MyViewModel2。

最后,MainWindow的代码是

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public MyViewModel ViewModel { get; set; }
}

Xaml是

<Window x:Class="StackOverflow._20937791.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:StackOverflow._20937791"
        DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
        Title="MainWindow" Height="200" Width="300">
    <Window.Resources>
        <DataTemplate DataType="{x:Type this:MyViewModel1}">
            <this:UserControl1 ViewModel="{Binding}" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type this:MyViewModel2}">
            <this:UserControl2 ViewModel="{Binding}" />
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Button Content="Show View 1" Command="{Binding Path=MyCommand}" CommandParameter="1" Width="100" Margin="4" />
            <Button Content="Show View 2" Command="{Binding Path=MyCommand}" CommandParameter="2" Width="100" Margin="0 4" />
        </StackPanel>
        <ContentControl Content="{Binding Path=CurrentViewModel}" Margin="20" />
    </StackPanel>
</Window>

UserControl在主窗口中引用,并且传入了ViewModel。

该应用程序显示一个类似于

的窗口

enter image description here

我希望这会有所帮助。

答案 1 :(得分:0)

Firt,FYI - 您的方法被称为战略模式。 现在你正在做的事情听起来不错,但是看到你的xaml很难。 也许您需要在设置vm属性后引发propertychanged事件?

答案 2 :(得分:0)

如果您发布您的代码会很有帮助。但如果我没有误解您的问题,那么您可以试试这个

<Button Command="{Binding MainControlVM.ClickCommand}"

设置绑定 MainControlVM.ClickCommand 。这里是ClickCommand命令的名称。

  

<强>更新

我认为问题在于设置CurrentViewModel。您正在Action Of Command中设置CurrentViewModel。我想你想在Command的基础上设置CurrentViewModel。我认为CommandParameter可能会更好。就像将所有按钮绑定到相同的Base ViewModel命令一样,并从每个Command传递不同的CommandParameter,然后在Command上比较CommandParameter并相应地设置CurrentViewModel。

  

ViewModelBase,Child1ViewModel,Child2ViewModel

public class ViewModelBase:INotifyPropertyChanged
{
    private ICommand _clickCommand;

    public ICommand ClickCommand
    {
        get
        {
            return _clickCommand ?? (_clickCommand = new CommandHandler(MyAction,()=>true));
        }
    }

    public void MyAction(object obj)
    {
        if(obj == null )
            return;
        //if CommandParameter is Cild1VM
        if (obj.ToString() == "Child1VM")
            CurrentViewModel = new Child1ViewModel();
        //if CommandParameter is Cild1VM
        else if (obj.ToString() == "Child2VM")
            CurrentViewModel = new Child2ViewModel();
    }

    ViewModelBase _currentViewModel;

    public ViewModelBase CurrentViewModel
    {
        get { return _currentViewModel; }
        set
        {
            if (_currentViewModel == value)
                return;
            _currentViewModel = value;
            this.RaiseNotifyPropertyChanged("CurrentViewModel");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void RaiseNotifyPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

public class Child1ViewModel : ViewModelBase
{ }

public class Child2ViewModel : ViewModelBase
{ } 
  

XAML

  <StackPanel>
    <Button Content="Foo" Command="{Binding ClickCommand}" CommandParameter="Child1VM"/>
    <Button Content="Bar" Command="{Binding ClickCommand}" CommandParameter="Child2VM"/>
</StackPanel>
  

xaml.cs

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModelBase();
    }
}

我希望这会给你一个想法。