具有只读属性的TwoWay MultiBinding

Ada*_*ura 6 data-binding wpf

如何跳过更新一些子绑定MultiBinding?我已经在代码隐藏中定义了(我在XAML中遇到了一些麻烦,我觉得这不重要 - 毕竟代码隐藏不是那么表达,而是XAML)a MultiBinding需要两个只读属性和一个普通属性产生单一价值.如果ConvertBack不修改只读属性(它们维持其值)并且仅更改普通属性.

虽然定义了MultiBinding整个MultiBinding被设置为TwoWay特定的子绑定设置适当(前两个到OneWay第三个TwoWay).


问题出现在我自己的控件中.然而,为了便于演示,我将其简化为较小的控件.此示例中显示Slider的控件是类似控件,允许选择[0.0; 1.0]范围.所选值由拇指表示并显示为a DependencyProperty.

基本上,控制是由1行x 3列构建的,Grid其中拇指位于中间列中.要正确定位拇指,左列必须指定与所选位置对应的宽度.然而,这个宽度还取决于整个控件的实际宽度和拇指本身的实际宽度(这是因为位置是[0.0; 1.0]范围内的相对值).

移动拇指时,应适当更新位置,但拇指宽度和控制宽度显然不会改变.

代码按预期工作,但是在拇指移动期间在IDE中运行时,当MultiBinding尝试将值设置为这两个只读属性时,"输出"窗口会混乱出现异常信息.我怀疑它没有害处,但有点烦人和误导.并且它意味着代码执行其他事情然后我希望它做,因为我不想设置这些属性(这在他们不是只读的情况下很重要,这实际上会修改它们).

MultiBinding 备注部分中的文档提到允许单个子绑定覆盖MultiBinding模式值,但它似乎不起作用.

也许这可以通过以某种方式表达对控件和拇指宽度(只读属性)的依赖性以某种方式解决.例如,单独注册其通知并在更改时强制执行更新.然而,这对我来说似乎并不自然.MultiBinding另一方面,因为所有左列宽度确实取决于这三个属性.


这是示例XAML代码.

<UserControl x:Class="WpfTest.ExampleUserControl"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
   <ColumnDefinition x:Name="leftColumn" />
   <ColumnDefinition x:Name="thumbColumn" Width="Auto" />
   <ColumnDefinition />
  </Grid.ColumnDefinitions>
  <!-- Rectangle used in the left column for better visualization. -->
  <Rectangle Grid.Column="0">
   <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
     <GradientStop Color="Black" Offset="0" />
     <GradientStop Color="White" Offset="1" />
    </LinearGradientBrush>
   </Rectangle.Fill>
  </Rectangle>
  <!-- Thumb representing the Position property. -->
  <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" />
  <!-- Rectangle used in the right column for better visualization. -->
  <Rectangle Grid.Column="2">
   <Rectangle.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
     <GradientStop Color="White" Offset="0" />
     <GradientStop Color="Black" Offset="1" />
    </LinearGradientBrush>
   </Rectangle.Fill>
  </Rectangle>
 </Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

这里是相应的代码隐藏

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfTest
{
 public partial class ExampleUserControl : UserControl
 {
  #region PositionConverter

  private class PositionConverter : IMultiValueConverter
  {
   public PositionConverter(ExampleUserControl owner)
   {
    this.owner = owner;
   }

   #region IMultiValueConverter Members

   public object Convert(
    object[] values,
    Type targetType,
    object parameter,
    CultureInfo culture)
   {
    double thisActualWidth = (double)values[0];
    double thumbActualWidth = (double)values[1];
    double position = (double)values[2];

    double availableWidth = thisActualWidth - thumbActualWidth;

    double leftColumnWidth = availableWidth * position;

    return new GridLength(leftColumnWidth);
   }

   public object[] ConvertBack(
    object value,
    Type[] targetTypes,
    object parameter,
    CultureInfo culture)
   {
    double thisActualWidth = owner.ActualWidth;
    double thumbActualWidth = owner.thumbColumn.ActualWidth;
    GridLength leftColumnWidth = (GridLength)value;

    double availableWidth = thisActualWidth - thumbActualWidth;

    double position;
    if (availableWidth == 0.0)
     position = 0.0;
    else
     position = leftColumnWidth.Value / availableWidth;

    return new object[] {
     thisActualWidth, thumbActualWidth, position
    };
   }

   #endregion

   private readonly ExampleUserControl owner;
  }

  #endregion

  public ExampleUserControl()
  {
   InitializeComponent();

   MultiBinding leftColumnWidthBinding = new MultiBinding()
   {
    Bindings =
    {
     new Binding()
     {
      Source = this,
      Path = new PropertyPath("ActualWidth"),
      Mode = BindingMode.OneWay
     },
     new Binding()
     {
      Source = thumbColumn,
      Path = new PropertyPath("ActualWidth"),
      Mode = BindingMode.OneWay
     },
     new Binding()
     {
      Source = this,
      Path = new PropertyPath("Position"),
      Mode = BindingMode.TwoWay
     }
    },
    Mode = BindingMode.TwoWay,
    Converter = new PositionConverter(this)
   };
   leftColumn.SetBinding(
    ColumnDefinition.WidthProperty, leftColumnWidthBinding);
  }

  public static readonly DependencyProperty PositionProperty =
   DependencyProperty.Register(
    "Position",
    typeof(double),
    typeof(ExampleUserControl),
    new FrameworkPropertyMetadata(0.5)
   );

  public double Position
  {
   get
   {
    return (double)GetValue(PositionProperty);
   }
   set
   {
    SetValue(PositionProperty, value);
   }
  }

 }
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*ura 11

最后我自己找到了解决方案.实际上它在文档中 - 我不知道我是如何错过的,但我为此付出了沉重的代价(浪费时间).

根据文件ConvertBack应该返回Binding.DoNothing没有设置值的位置(特别OneWay是需要绑定).另一个特殊价值是DependencyProperty.UnsetValue.

这不是一个完整的解决方案,因为现在IMultiValueConverter实现必须知道返回特殊值的位置.但是我认为这个解决方案涵盖了大多数合理的案例.