如何使用 mvvm 模式从不同的 ViewModel 获取属性和调用命令

Posted

技术标签:

【中文标题】如何使用 mvvm 模式从不同的 ViewModel 获取属性和调用命令【英文标题】:How to get property and call command from different ViewModel with mvvm pattern 【发布时间】:2020-11-12 11:06:14 【问题描述】:

我有一个 ViewModel,其中包含我在每个子 ViewModel 中需要的所有属性。 这是我第一次尝试将命令和视图模型拆分为多个文件。上次一切都在同一个 ViewModel 中,使用它很痛苦。一切都按预期显示,但我想找到一种方法在每个视图模型中传递相同的数据。

从我的 GetOrdersCommand 中,我想获取 HeaderViewModel.SelectedSource 属性。如果没有返回空值或丢失属性数据,我没有找到任何方法...... 我也想从 HeaderView 按钮调用我的 GetOrdersCommand。

任何提示我可以如何做到这一点?也许,我的设计不适合我想做的事情?

MainWindow.xaml

        <views:HeaderView Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" DataContext="Binding HeaderViewModel" LoadHeaderViewCommand="Binding LoadHeaderViewCommand"/>
        <TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
            <TabItem Header="General">
            </TabItem>
            <TabItem Header="Orders">
                <views:OrderView DataContext="Binding OrderViewModel" GetOrdersCommand="Binding GetOrdersCommand"/>
            </TabItem>
        </TabControl>

HeaderView.xaml

        <DockPanel>
            <ComboBox DockPanel.Dock="Left" Width="120" Margin="4" VerticalContentAlignment="Center" ItemsSource="Binding SourceList" SelectedItem="Binding SelectedSource" DisplayMemberPath="SourceName"/>
            <Button x:Name="btnTest" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="4" Content="Test"/>
        </DockPanel>

HeaderView.xaml.cs

    public partial class OrderView : UserControl
    
        public ICommand GetOrdersCommand
        
            get  return (ICommand)GetValue(GetOrdersCommandProperty); 
            set  SetValue(GetOrdersCommandProperty, value); 
        
        public static readonly DependencyProperty GetOrdersCommandProperty =
            DependencyProperty.Register("GetOrdersCommand", typeof(ICommand), typeof(OrderView), new PropertyMetadata(null));

        public OrderView()
        
            InitializeComponent();
        

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        
            if (GetOrdersCommand != null)
            
                GetOrdersCommand.Execute(this);
            
        
    

MainViewModel.cs

        private OrderViewModel orderViewModel;
        public OrderViewModel OrderViewModel  get; set;  // Getter, setter with OnPropertyChanged

        private HeaderViewModel headerViewModel;
        public HeaderViewModel HeaderViewModel  get; set;  //  Getter, setter with OnPropertyChanged

        public MainViewModel()
        
            HeaderViewModel = new HeaderViewModel();
            OrderViewModel = new OrderViewModel();
        

HeaderViewModel.cs

public ICommand LoadHeaderViewCommand  get; set; 
public HeaderViewModel()

   LoadHeaderViewCommand = new LoadHeaderViewCommand(this);

GetOrdersCommand.cs

    public class GetOrdersCommand : ICommand
    
        public event EventHandler CanExecuteChanged;
        private readonly OrderViewModel _orderViewModel;

        public GetOrdersCommand(OrderViewModel orderViewModel)
        
            _orderViewModel = orderViewModel;
        

        public bool CanExecute(object parameter)
        
            return true;
        

        public void Execute(object parameter)
        
            /* Build Order List according to HeaderViewModel.SelectedSource */
            _orderViewModel.Orders = new ObservableCollection<Order>()
            
                new Order  ID = 1, IsReleased = false, Name = "Test1",
                new Order  ID = 2, IsReleased = true, Name = "Test2",
            ;
        
    

【问题讨论】:

对我来说,GetOrdersCommand 类并在 OrderVM 中为其创建 DP 比它应该的要复杂一些。怎么样,在 HeaderVM 中注入 OrderVM。并编辑 HeaderVM.SelectedSource 的 setter 以更新 OrderVM 中的相关信息。 您应该使用 prism delegatecommand 或(最好)mvvmlight relaycommand 并在其拥有的视图模型中定义命令。这使您的视图模型更易于理解,并且您可以在命令中捕获变量。 GetOrdersCommand.cs .... 糟糕的代码!更好的是使用在构造函数中接受方法的通用 ICommand 接口实现。初始化命令时,将所需的方法传递给它。 我应该看到 OrderViewModel 类的源代码。 HeaderViewModel.SelectedSource 必须作为命令参数传递。 【参考方案1】:

谢谢大家!我按照建议将我的命令移到了他们拥有的 ViewModel 中。 我尝试了 MVVVM Light Tools,发现关于 Messenger Class。

我使用它将我的 SelectedSource(来自 HeaderView 的组合框)从 HeaderViewModel 发送到 OrderViewModel。我想像那样使用 Messenger 类吗?我不知道,但它成功了!!!

我考虑过将 GetOrdersCommand 移动到 OrderViewModel,将我的按钮命令绑定到 OrderViewModel,将 SelectedSource 绑定为 CommandParameter,但我不知道当 HeaderViewModel.SelectedSource 更改时我应该如何 RaiseCanExecuteChanged...有什么建议吗?

MainWindow.xaml

<views:HeaderView DataContext="Binding Source=StaticResource Locator, Path=HeaderVM" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
    <TabItem Header="General">
    </TabItem>
    <TabItem Header="Orders">
        <views:OrderView DataContext="Binding Source=StaticResource Locator, Path=OrderVM"/>
    </TabItem>
</TabControl>

OrderViewModel.cs

private ObservableCollection<Order> _orders;
public ObservableCollection<Order> Orders

    get  return _orders; 
    set
    
        if (_orders != value)
        
            _orders = value;
            RaisePropertyChanged(nameof(Orders));
        
    

public OrderViewModel()

    Messenger.Default.Register<Source>(this, source => GetOrders(source));


private void GetOrders(Source source)

    if (source.SourceName == "Production")
    
        Orders = new ObservableCollection<Order>()
            new Order  ID = 1, IsReleased = false, Name = "Production 1" 
        ;
    
    else
    
        Orders = new ObservableCollection<Order>()
            new Order  ID = 2, IsReleased = true, Name = "Test 1" 
        ;
    

HeaderViewModel.cs 的一部分

private Source _SelectedSource;
public Source SelectedSource

    get  return _SelectedSource; 
    set
    
        if (_SelectedSource != value)
        
            _SelectedSource = value;
            RaisePropertyChanged(nameof(SelectedSource));
            GetOrdersCommand.RaiseCanExecuteChanged();
        
    


private RelayCommand _GetOrdersCommand;
public RelayCommand GetOrdersCommand

    get
    
        if (_GetOrdersCommand == null)
        
            _GetOrdersCommand = new RelayCommand(GetOrders_Execute, GetOrders_CanExecute);
        
        return _GetOrdersCommand;
    


private void GetOrders_Execute()

        Messenger.Default.Send(SelectedSource);


private bool GetOrders_CanExecute()

    return SelectedSource != null ? true : false;

【讨论】:

以上是关于如何使用 mvvm 模式从不同的 ViewModel 获取属性和调用命令的主要内容,如果未能解决你的问题,请参考以下文章

为啥要避免 WPF MVVM 模式中的代码隐藏?

如何在MVVM中使用相同的ViewModel拥有多个视图?

wpf MVVM Viewmodel之间传值

mvvm

如何在 MVVM 模式中从页面导航到 WPF 中的页面?没有棱镜的概念[重复]

如何从 mvvm 中的视图模型关闭视图?