【问题标题】:How to bind to calculated property using multiple dependency properties如何使用多个依赖属性绑定到计算属性
【发布时间】:2019-12-29 04:01:41
【问题描述】:

我正在尝试构建一个可以在整个项目中重复使用的 WPF UserControl。基本上是这样的:

  • 水平线(称为顶线)
  • 文本块 - 水平线(底线)

当窗口调整大小并且太小时,我只显示底线以节省空间。所以我想要这些选项:

  • 隐藏所有行
  • 仅显示顶行
  • 只显示底线

我正在使用三个依赖属性来间接设置每一行的可见性:

  • 显示线条(启用线条)
  • 可以压缩(启用显示顶部或底部,取决于可用空间)
  • 显示压缩(真时显示底线,假时显示顶线)

直接绑定到依赖属性有效。间接没有。

现在的问题是我是否需要使用:

  1. FrameworkPropertyMetadataOptions.AffectsRender 等标志用于 FrameworkPropertyMetadata
  2. 为 DependencyProperty.Register 创建 FrameworkPropertyMetadata 时的 PropertyChangedCallback
  3. 一个值转换器
  4. INotifyPropertyChanged

我尝试过的

我尝试了上述所有四个选项,但我对它的理解还不够好,无法让其中一个发挥作用。

我创建了从三个依赖属性返回计算值的普通属性。这些使用通过 DependencyProperty.Register 设置的默认值,它们不更新甚至不使用父用户控件中设置的值。

当前状态

<StackPanel>
    <Border Height="1"
            Background="Black"
            Margin="0 10 0 0"
            Visibility="{Binding ShowTopLine, ElementName=Root, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0"
                   Text="{Binding Text, ElementName=Root, Mode=TwoWay, FallbackValue=Heading}"
                   Margin="0 10 10 0" />
        <Border Grid.Column="1" 
                Height="1" 
                Margin="0 10 0 0" 
                Background="Black" 
                VerticalAlignment="Center" 
                Visibility="{Binding ShowBottomLine, ElementName=Root, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
    </Grid>
</StackPanel>

代码隐藏的相关部分,显示三个依赖属性之一:

internal partial class Heading
{
  public bool ShowBottomLine => ShowLine && (!CanCompress || ShowCompressed);

  public static readonly DependencyProperty CanCompressProperty = GetRegisterProperty("CanCompress", typeof(bool), true);

  public bool CanCompress
  {
    get => (bool) GetValue(CanCompressProperty);
    set => SetValue(CanCompressProperty, value);
  }

  // Same for other two properties

  public static DependencyProperty GetRegisterProperty(string name, Type type, object defaultValue)
  {
    return DependencyProperty.Register(name, type, typeof(Heading), new FrameworkPropertyMetadata(defaultValue));
  }
}

如何在另一个用户控件中使用标题:

<local:Heading Text="{Binding HeaderText}"
                                   CanCompress="False" 
                                   ShowLine="False"/>

我该如何从这里开始?我知道放弃计算的属性很简单,但这意味着我需要在其他地方计算它们的状态。我想在标题中做到这一点。

主要问题似乎是计算的属性不会被强制刷新。这是一个公平的总结吗?

【问题讨论】:

    标签: c# wpf data-binding dependency-properties


    【解决方案1】:

    您可以通过定义ControlTemplate 和简单的触发器来实现此逻辑:

    <UserControl x:Class="ExampleControl">
      <UserControl.Template>
        <ControlTemplate TargetType="ExampleControl">
    
          <StackPanel>
            <Border x:Name="TopLine"
                    Background="Black" 
                    Height="1"
                    Margin="0 10 0 0" />
            <Grid>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
              </Grid.ColumnDefinitions>
              <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, FallbackValue=Heading}"
                         Margin="0 10 10 0" />
              <Border x:Name="BottomLine"
                      Grid.Column="1"
                      Height="1"
                      Margin="0 10 0 0"
                      Background="Black"
                      VerticalAlignment="Center" />
            </Grid>
          </StackPanel>
    
          <ControlTemplate.Triggers>
            <Trigger Property="ShowLines" Value="False">
              <Setter TargetName="TopLine" Property="Visibility" Value="Collapsed" />
              <Setter TargetName="BottomLine" Property="Visibility" Value="Collapsed" />
            </Trigger>
    
            <MultiTrigger>
              <MultiTrigger.Conditions>
                <Condition Property="CanCompress" Value="True" />
                <Condition Property="ShowCompressed" Value="True" />
              </MultiTrigger.Conditions>
              <Setter TargetName="TopLine"
                      Property="Visibility"
                      Value="Collapsed" />
            </MultiTrigger>
    
            <MultiTrigger>
              <MultiTrigger.Conditions>
                <Condition Property="CanCompress" Value="True" />
                <Condition Property="ShowCompressed" Value="False" />
              </MultiTrigger.Conditions>
              <Setter TargetName="BottomLine"
                      Property="Visibility"
                      Value="Collapsed" />
            </MultiTrigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </UserControl.Template>
    </UserControl>
    

    或者(并且推荐)使用自定义控件 (extend Control)。

    【讨论】:

    • 太棒了,非常感谢。我创建了一个使用这些触发器的自定义控件。我什至可以摆脱 MultiTrigger,因为实际上只有三个选项,所以两个布尔值就足够了。既然我已经习惯了绑定,我还没有想到直接设置可见性的选项。但是当我没有计划支持主题或设置控件模板时,真的有必要创建控件而不是用户控件吗?
    • 不,扩展Control 不是实现所需行为的必要条件。扩展 UserControl 也可以正常工作。但是扩展Control 并没有增加更多的工作,而是增加了灵活性。所以我认为没有理由更喜欢UserControl 而不是Control,除非你不熟悉模板。
    • 另外,如果你需要释放布局空间,你应该使用Visibility.Collapsed而不是Visibility.Hidden
    猜你喜欢
    • 2016-01-22
    • 2020-05-17
    • 1970-01-01
    • 2020-12-22
    • 1970-01-01
    • 2023-03-26
    • 2018-05-21
    • 1970-01-01
    • 2020-08-06
    相关资源
    最近更新 更多