【问题标题】:Bind MenuItem.IsEnabled to property of a view model将 MenuItem.IsEnabled 绑定到视图模型的属性
【发布时间】:2023-03-20 00:53:02
【问题描述】:

我有一个包含以下代码的 MVVM WPF 项目:
MultiplexerVM.cs

public class MultiplexerVM : BaseViewModel
{
    public ObservableCollection<MultiplexVM> Multiplexes { get; set; } = new();
    public MultiplexVM SelectedMultiplex { get; set; }
    public ICommand CheckAll => new CheckBoxCommand(Multiplexes);
}

MultiplexVM.cs

public class MultiplexVM : BaseViewModel
{
    public bool IsChecked { get; set; }
}

MultiplexerV.xaml

<UserControl x:Class="MKVStudio.Views.MultiplexerV"
             xmlns:vm="clr-namespace:MKVStudio.ViewModels"
             xmlns:s="clr-namespace:System;assembly=mscorlib">
    <UserControl.Resources>
        <s:Boolean x:Key="True">True</s:Boolean>
        <s:Boolean x:Key="False">False</s:Boolean>
    </UserControl.Resources>
    <Grid>
        <ListView ItemsSource="{Binding Multiplexes}"
                  SelectedItem="{Binding SelectedMultiplex}">
            <ListView.View>
                <GridView>
                    <GridViewColumn>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox IsChecked="{Binding IsChecked}"Margin="3"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                           ...
                </GridView>
            </ListView.View>                
            <ListView.ContextMenu>
                <ContextMenu>
                    <MenuItem Command="{Binding CheckAll}"
                              CommandParameter="{StaticResource True}">
                        <MenuItem.Header>
                            <TextBlock Text="Check all"/>
                        </MenuItem.Header>
                    </MenuItem>
                    <MenuItem Command="{Binding CheckAll}"
                              CommandParameter="{StaticResource False}">
                        <MenuItem.Header>
                            <TextBlock Text="Uncheck all"/>
                        </MenuItem.Header>
                    </MenuItem>
                </ContextMenu>
            </ListView.ContextMenu>
        </ListView>
    </Grid>
</UserControl>

我的目标是将上下文菜单项的IsEnabled 绑定到MultiplexVM.cs 的属性IsChecked。这个想法是实现IValueConverter(将Multiplexes 传递为value 并将布尔值传递为parameter)。转换器返回value.Where(m =&gt; m.IsChecked == parameter).Count &gt; 0。本质上,当所有Multiplexes 都未选中时,菜单项Check all 被启用,菜单项Uncheck all 被禁用。当检查所有Multiplexes 时,会发生相反的情况。这里的问题是转换器基本上在声明时只被调用一次,并且检查和取消选中项目并不会触发转换器查看发生了什么。

我尝试实现IMultiValueConverter(但未能正确使用它)并传递三个这样的值:

<MenuItem.IsEnabled>
    <MultiBinding>
        <Binding Source="{Binding Multiplexes.Count}" />
        <Binding Source="{Binding Multiplexes}" />
        <Binding Source="{StaticResource True}" /> <!--respectivly False to the other menu item-->
    </MultiBinding>
</MenuItem.IsEnabled>

这不起作用。我试过&lt;Binding Path="Multiplexes.Count" /&gt;&lt;Binding Path="Multiplexes" /&gt;,但还是不行(传递给转换器的值是Unset)。

我使用MultiBinding 的想法是否可行?使用它时我做错了什么?

【问题讨论】:

    标签: c# wpf mvvm binding multibinding


    【解决方案1】:

    为什么需要同时将 IsChecked 绑定到 IsChecked 和 IsEnabled?如果从单一职责原则来看,这很奇怪。如果你确定你做对了,你可以这样做:

    <CheckBox IsChecked="{Binding IsChecked}"
              IsEnabled="{Binding IsEnabled}" />
    

    让你的班级看起来像这样:

    public class MultiplexVM : BaseViewModel
    {
        public bool IsChecked 
        { 
            get => isChecked; 
            set
            {
                isChecked = value;
                isEnabled = value;
                RaisePropertyChanged(nameof(IsChecked));
                RaisePropertyChanged(nameof(IsEnabled));
            }; 
        }
    
        private bool isChecked;
    
        public bool IsEnabled
        { 
            get => isEnabled; 
            set
            {
                isChecked = value;
                isEnabled = value;
                RaisePropertyChanged(nameof(IsChecked));
                RaisePropertyChanged(nameof(IsEnabled));
            }; 
        }
    
        private bool isChecked;
    }
    

    【讨论】:

      【解决方案2】:

      据我了解,您想让绑定到“父”(MenuItem =&gt; MultiplexerVM) 的对象依赖于其子集合的属性(CheckBox =&gt; MultiplexVM.IsChecked,这是MultiplexerVM.Multiplexes 中的一个项目)

      在这种情况下,孩子必须以某种方式了解其父母(当孩子发生变化时,它必须将变化“推送”给父母;换句话说,必须在发生变化时通知父母)。

      我可以想到两种方法:

      • 在VM级别:在每个MultiplexVM中,设置对父视图模型或集合的引用,然后您可以在每次子IsChecked更改时更新CanCheckAll / CanUncheckAll功能(无论您如何实现它)(繁琐; 我想你也可以通过事件来做到这一点,但是将PropertyChanged 处理程序附加到每个子项也有点多)

      • 使用 GUI 级别作弊:只要单击 CheckBox,您就可以更新 CanCheckAll / CanUncheckAll 功能

      以下是如何实施第二版的示例。

      在你的MultiplexerVM:

      public bool CanCheckAll => Multiplexes.Any(a => !a.IsChecked);
      public bool CanUncheckAll => Multiplexes.Any(a => a.IsChecked);
      
      public void RefreshCheckUncheckAll()
      {
          NotifyPropertyChanged(nameof(CanCheckAll));
          NotifyPropertyChanged(nameof(CanUncheckAll));
      }
      

      然后,在CheckAll命令实现中调用RefreshCheckUncheckAll()

      private void CheckBox_Click(object sender, RoutedEventArgs e)
      {
          ((MultiplexerVM)this.DataContext).RefreshCheckUncheckAll();
      }
      

      然后,xaml 将如下所示:

      <ListView ItemsSource="{Binding Multiplexes}" SelectedItem="{Binding SelectedMultiplex}">
              <ListView.ContextMenu>
                  <ContextMenu>
                      <MenuItem
                          Command="{Binding CheckAll}"
                          CommandParameter="{StaticResource True}"
                          IsEnabled="{Binding CanCheck}">
                          <MenuItem.Header>
                              <TextBlock Text="Check all" />
                          </MenuItem.Header>
                      </MenuItem>
                      <MenuItem
                          Command="{Binding CheckAll}"
                          CommandParameter="{StaticResource False}"
                          IsEnabled="{Binding CanUncheck}">
                          <MenuItem.Header>
                              <TextBlock Text="Uncheck all" />
                          </MenuItem.Header>
                      </MenuItem>
                  </ContextMenu>
              </ListView.ContextMenu>
              <ListView.View>
                  <GridView>
                      <GridViewColumn>
                          <GridViewColumn.CellTemplate>
                              <DataTemplate>
                                  <TextBlock Margin="3" Text="{Binding Name}" />
                              </DataTemplate>
                          </GridViewColumn.CellTemplate>
                      </GridViewColumn>
                      <GridViewColumn>
                          <GridViewColumn.CellTemplate>
                              <DataTemplate>
                                  <CheckBox Margin="3" IsChecked="{Binding IsChecked}" Click="CheckBox_Click" />
                              </DataTemplate>
                          </GridViewColumn.CellTemplate>
                      </GridViewColumn>
                  </GridView>
              </ListView.View>
      
          </ListView>
      

      【讨论】:

      • 谢谢你,@Arie。我之前考虑过这个解决方案,它完全有意义。这是我最后的手段。如果我必须完全诚实,我做这个项目纯粹是为了锻炼和丰富我的技能。我想尝试一些复杂的东西来学习新的东西。无论如何,谢谢你的回答!
      猜你喜欢
      • 2010-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多