WPF - 使用 MVVM 根据模型的不同状态禁用和启用不同控件的优雅方法

发布于 2024-09-24 13:04:59 字数 1109 浏览 0 评论 0原文

我正在为以下问题寻找一个优雅的解决方案。

假设我们有一个具有以下布尔属性的(视图)模型:

  • Alpha
  • Beta
  • Gamma
  • Delta

接下来,我在表面上有 5 个控件,它们仅在满足基于这些属性的条件时才可见。当然,一旦这些属性之一被更新,更改就应该立即传播:

  • ControlA -> ControlA -> ControlA -> ControlA -> ControlA -> ControlA -> ControlA -> ControlA -> ControlA -> ControlA阿尔法&& ( Beta || Gamma )
  • ControlB ->台达
  • 控制C ->达美航空|| Beta
  • ControlD ->伽玛&&阿尔法&&台达
  • 控制E ->阿尔法 || Gamma

到目前为止我想到的唯一解决方案是使用 MultiValueConverters。

ControlA 示例:

<ControlA>
   <ControlA.Visibility>
      <MultiBinding Converter={StaticResource ControlAVisibilityConverter}>
          <Binding Path="Alpha"/>
          <Binding Path="Beta"/>
          <Binding Path="Gamma"/>
      </MultiBinding>
   </ControlA.Visibility>
</ControlA>

此 ControlAVsibilityConverter 检查条件“Alpha && ( Beta || Gamma )”并返回适当的值。

它确实有效..好吧..但也许你可以想出一个更优雅的解决方案?

谢谢你, 双生习惯

I am looking for an elegant solution for the following problem.

Let's assume we have a (View)Model with the following boolean properties:

  • Alpha
  • Beta
  • Gamma
  • Delta

Next I have 5 controls on the surface that shall only be visible when a condition based on those properties are met. Of course, as soon as one of those properties is updated the change should be propagated immediatelly:

  • ControlA -> Alpha && ( Beta || Gamma )
  • ControlB -> Delta
  • ControlC -> Delta || Beta
  • ControlD -> Gamma && Alpha && Delta
  • ControlE -> Alpha || Gamma

The only solution I came up with so far is using MultiValueConverters.

Example for ControlA:

<ControlA>
   <ControlA.Visibility>
      <MultiBinding Converter={StaticResource ControlAVisibilityConverter}>
          <Binding Path="Alpha"/>
          <Binding Path="Beta"/>
          <Binding Path="Gamma"/>
      </MultiBinding>
   </ControlA.Visibility>
</ControlA>

This ControlAVisibilityConverter checks for condition "Alpha && ( Beta || Gamma )" and returns the appropriate value.

It does work.. well.. but maybe you can come up with a more elegant solution?

Thank you,
TwinHabit

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

断舍离 2024-10-01 13:04:59

在这种情况下,为每个规则编写转换器会将您的业务逻辑置于两个位置(在转换器和视图模型中)。
我建议使用 INotifyPropertyChanged 事件为 ViewModel 中的每个控件创建一个属性/标志,以决定该控件是否可见(或其他行为)。

请注意,当您查看我的视图模型(如下)时,您将看到我公开了 bool 和 Visibilty 类型的属性。

如果您需要使用该属性作为一般规则,请使用 bool 和 DataTrigger。

public bool ControlD

如果您只需要控制可见性,可以直接绑定到 Visibility:

public Visibility ControlA

UPDATE
由于@Wallstreet Programmer 的评论,我添加了另一个使用 BooleanVisibilityConverter 的选项。我更新了下面的第五个控件以反映如何使用转换器。我在底部添加了转换器的代码。

这是 XAML 中的测试窗口:

<Window x:Class="ControlVisibleTrigger.Views.MainView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Main Window" Height="400" Width="800">
  <Window.Resources>
    <Style x:Key="DropDownStyle" TargetType="TextBox">
        <Setter Property="Visibility" Value="Hidden"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding ControlC}" Value="True">
                <Setter Property="Visibility" Value="Visible"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
  </Window.Resources>
  <DockPanel>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0">
            <CheckBox IsChecked="{Binding Path=Alpha,Mode=TwoWay}" Content="Alpha"/>
            <CheckBox IsChecked="{Binding Path=Beta,Mode=TwoWay}" Content="Beta"/>
            <CheckBox IsChecked="{Binding Path=Gamma,Mode=TwoWay}" Content="Gamma"/>
            <CheckBox IsChecked="{Binding Path=Delta,Mode=TwoWay}" Content="Delta"/>
        </StackPanel>
        <TextBox Grid.Row="1" Visibility="{Binding Path=ControlA}" Text="Binding to visibility"/>
        <Button Grid.Row="2" Visibility="{Binding Path=ControlB}" Content="Binding to visibility"/>
        <TextBox Grid.Row="3" Style="{StaticResource DropDownStyle}" Text="Using WindowResource DataTrigger"/>
        <TextBox Grid.Row="4" Text="Using Local DataTrigger">
            <TextBox.Style>
              <Style TargetType="TextBox">
                <Setter Property="Visibility" Value="Hidden"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ControlD}" Value="True">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
              </Style>
            </TextBox.Style>
        </TextBox>
        <Button Grid.Row="5" 
                Content="Press me" 
                Visibility="{Binding Path=ControlE, Converter={StaticResource booleanVisibilityConverter}, ConverterParameter=True, Mode=OneWay}">
    </Grid>
  </DockPanel>
</Window>

这是 ViewModel:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
  }

  private bool _alpha = true;
  public bool Alpha
  {
     get
     {
        return _alpha;
     }
     set
     {
        _alpha = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _beta = true;
  public bool Beta
  {
     get
     {
        return _beta;
     }
     set
     {
        _beta = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _gamma = true;
  public bool Gamma
  {
     get
     {
        return _gamma;
     }
     set
     {
        _gamma = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _delta = true;
  public bool Delta
  {
     get
     {
        return _delta;
     }
     set
     {
        _delta = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  public Visibility ControlA
  {
     get
     {
        Visibility result = Visibility.Hidden;
        if ( Alpha && (Beta || Gamma))
        {
           result = Visibility.Visible;
        }
        return result;
     }
  }

  public Visibility ControlB
  {
     get
     {
        Visibility result = Visibility.Hidden;
        if ( Delta )
        {
           result = Visibility.Visible;
        }
        return result;
     }
  }

  private bool _controlC = false;
  public bool ControlC
  {
     get
     {
        return Delta || Beta;
     }
  }

  private bool _controlD = false;
  public bool ControlD
  {
     get
     {
        return Gamma && Alpha && Delta;
     }
  }

  private bool _controlE = false;
  public bool ControlE
  {
     get
     {
        return Alpha || Gamma;
     }
  }
}

这是转换器:

public class BooleanVisibilityConverter : IValueConverter
{
  public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
  {
    if( ( value == null ) || ( !( value is bool ) ) )
      return Binding.DoNothing;

    Visibility elementVisibility;
    bool shouldCollapse = ( ( bool )value );

    if( parameter != null )
    {
      try
      {
        bool inverse = System.Convert.ToBoolean( parameter );

        if( inverse )
          shouldCollapse = !shouldCollapse;
      }
      catch
      {
      }
    }

    elementVisibility = shouldCollapse ? Visibility.Collapsed : Visibility.Visible;
    return elementVisibility;
  }

  public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
  {
    throw new NotImplementedException();
  }
}

Writing a converter for each rule puts your business logic two places in this case (in the converter and the view model).
I suggest creating a property/flag for each control in your ViewModel with INotifyPropertyChanged events to decide whether the control is visible (or other behaviour).

Note, that when you look at my viewmodel (below) you will see that I expose properties of type bool and Visibilty.

If you need to use the property as a general rule use bool and a DataTrigger.

public bool ControlD

If you only need to control visibility you can bind to Visibility directly:

public Visibility ControlA

UPDATE:
Because of the comment by @Wallstreet Programmer, I added another option to use a BooleanVisibilityConverter. I updated the fifth control below to reflect how to use a converter. I added the code for the converter at the bottom.

Here is a test Window in XAML:

<Window x:Class="ControlVisibleTrigger.Views.MainView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Main Window" Height="400" Width="800">
  <Window.Resources>
    <Style x:Key="DropDownStyle" TargetType="TextBox">
        <Setter Property="Visibility" Value="Hidden"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding ControlC}" Value="True">
                <Setter Property="Visibility" Value="Visible"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
  </Window.Resources>
  <DockPanel>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0">
            <CheckBox IsChecked="{Binding Path=Alpha,Mode=TwoWay}" Content="Alpha"/>
            <CheckBox IsChecked="{Binding Path=Beta,Mode=TwoWay}" Content="Beta"/>
            <CheckBox IsChecked="{Binding Path=Gamma,Mode=TwoWay}" Content="Gamma"/>
            <CheckBox IsChecked="{Binding Path=Delta,Mode=TwoWay}" Content="Delta"/>
        </StackPanel>
        <TextBox Grid.Row="1" Visibility="{Binding Path=ControlA}" Text="Binding to visibility"/>
        <Button Grid.Row="2" Visibility="{Binding Path=ControlB}" Content="Binding to visibility"/>
        <TextBox Grid.Row="3" Style="{StaticResource DropDownStyle}" Text="Using WindowResource DataTrigger"/>
        <TextBox Grid.Row="4" Text="Using Local DataTrigger">
            <TextBox.Style>
              <Style TargetType="TextBox">
                <Setter Property="Visibility" Value="Hidden"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ControlD}" Value="True">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
              </Style>
            </TextBox.Style>
        </TextBox>
        <Button Grid.Row="5" 
                Content="Press me" 
                Visibility="{Binding Path=ControlE, Converter={StaticResource booleanVisibilityConverter}, ConverterParameter=True, Mode=OneWay}">
    </Grid>
  </DockPanel>
</Window>

Here is the ViewModel:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
  }

  private bool _alpha = true;
  public bool Alpha
  {
     get
     {
        return _alpha;
     }
     set
     {
        _alpha = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _beta = true;
  public bool Beta
  {
     get
     {
        return _beta;
     }
     set
     {
        _beta = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _gamma = true;
  public bool Gamma
  {
     get
     {
        return _gamma;
     }
     set
     {
        _gamma = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  private bool _delta = true;
  public bool Delta
  {
     get
     {
        return _delta;
     }
     set
     {
        _delta = value;
        OnPropertyChanged("ControlA");
        OnPropertyChanged("ControlB");
        OnPropertyChanged("ControlC");
        OnPropertyChanged("ControlD");
        OnPropertyChanged("ControlE");
     }
  }

  public Visibility ControlA
  {
     get
     {
        Visibility result = Visibility.Hidden;
        if ( Alpha && (Beta || Gamma))
        {
           result = Visibility.Visible;
        }
        return result;
     }
  }

  public Visibility ControlB
  {
     get
     {
        Visibility result = Visibility.Hidden;
        if ( Delta )
        {
           result = Visibility.Visible;
        }
        return result;
     }
  }

  private bool _controlC = false;
  public bool ControlC
  {
     get
     {
        return Delta || Beta;
     }
  }

  private bool _controlD = false;
  public bool ControlD
  {
     get
     {
        return Gamma && Alpha && Delta;
     }
  }

  private bool _controlE = false;
  public bool ControlE
  {
     get
     {
        return Alpha || Gamma;
     }
  }
}

Here is the converter:

public class BooleanVisibilityConverter : IValueConverter
{
  public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
  {
    if( ( value == null ) || ( !( value is bool ) ) )
      return Binding.DoNothing;

    Visibility elementVisibility;
    bool shouldCollapse = ( ( bool )value );

    if( parameter != null )
    {
      try
      {
        bool inverse = System.Convert.ToBoolean( parameter );

        if( inverse )
          shouldCollapse = !shouldCollapse;
      }
      catch
      {
      }
    }

    elementVisibility = shouldCollapse ? Visibility.Collapsed : Visibility.Visible;
    return elementVisibility;
  }

  public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
  {
    throw new NotImplementedException();
  }
}
无需解释 2024-10-01 13:04:59

假设是否应显示控件存在业务逻辑原因,我肯定会将逻辑存储为 ViewModel 中的布尔值(尽管我会根据业务逻辑命名它,例如:CriteriaA 而不是 ControlAVsible)。这样可以轻松进行单元测试,以确保在 alpha、beta、gamma 和 delta 变化时正确设置标准。除此之外,我同意华尔街程序员的回答(尽管我没有代表评论或投票他的回答)。

Assuming that there's a business logic reason for whether or not the controls should be displayed I'd definitely have the logic stored as a bool in the ViewModel (though I'd name it according to the business logic e.g.: CriteriaA not ControlAVisible). This allows easy unit testing to ensure that the Criteria are set correctly as alpha, beta, gamma and delta change. Other than that I'd agree with Wallstreet Programmers answer (though I don't have the rep to comment or vote his response).

本王不退位尔等都是臣 2024-10-01 13:04:59

如果控件支持命令(例如,如果它们是按钮),请使用命令模式。使用 RelayCommand(查找),您可以使用 lambda 表达式指定启用控件的条件(这正是您所需要的)。但它需要一些隐藏代码。

If the controls support commands (for example if they are buttons), use command pattern. With RelayCommand (look it up), you can specify the condition under which the control is enable with an lambda expression (which is exactly what you need). It needs some code-behind though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文