具有只读属性的双向多重绑定
如何跳过更新MultiBinding
的某些子绑定?我已经在代码隐藏中定义了(我在XAML中遇到了一些麻烦,我不认为它很重要 - 毕竟代码隐藏并不逊色于XAML)MultiBinding
它带有两个只读属性和一个常规属性产生一个单一的价值。在ConvertBack
的情况下,只读属性不会被修改(它们保持它们的值)并且只有正常的属性被改变。具有只读属性的双向多重绑定
在定义MultiBinding
整个MultiBinding
设定为TwoWay
然而特定子绑定,其中设置合适的(前两个到OneWay
和第三双TwoWay
)。
该问题发生在我自己的控制中。但为了演示的目的,我将其简化为一个较小的控件。本例中的控件是一个类似于Slider
的控件,允许选择[0.0; 1.0]范围。所选值由大拇指表示并公开为DependencyProperty
。
基本上控制是由1行×3列Grid
其中拇指在中间列建立。要正确定位拇指左列,必须指定对应于所选位置的宽度。然而,这个宽度也取决于整个控件的实际宽度和拇指本身的实际宽度(这是因为位置在[0.0; 1.0]范围内以相对值给出)。
当拇指被移动到的位置应然而拇指宽度和控制宽度明显不改变被适当地更新。
代码按预期方式工作,但是在拇指移动期间在IDE中运行时输出窗口混杂着异常信息,当MultiBinding
试图将值设置为这两个只读属性时报告。我怀疑它不是有害的,但它有点烦人和误导。而且这也意味着代码做了其他事情,然后我希望它做,因为我不想设置这些属性(这很重要,如果它们不是只读的,这实际上会修改它们)。
MultiBinding
documentation备注部分提到,单个子绑定被允许覆盖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>
这里是对应的代码隐藏
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);
}
}
}
}
最后我自己找到了解决方案。其实这是在documentation - 我不知道我是如何错过的,但我付出了沉重的代价(浪费时间)。
根据文件ConvertBack
应该返回Binding.DoNothing
上没有设置数值的位置(特别是有OneWay
需要绑定)。另一个特殊值是DependencyProperty.UnsetValue
。
这不是一个完整的解决方案,因为现在的IMultiValueConverter
实现必须知道在哪里返回一个特殊值。不过,我认为这个解决方案涵盖了最合理的情况。
它看起来像MultiBinding不工作的权利。在练习之前,我已经看到了一些意想不到的行为(类似于你的)。您也可以在转换器中插入断点或一些跟踪,并且可以找到关于哪些转换器以及何时被调用的一些有趣的事情。 所以,如果可能的话,你应该避免使用MultiBinding。例如。您可以在视图模型中添加特殊属性,该特性将在您的设置器中设置您的可变属性的值,并使用其getter中的所有三个属性返回所需的值。它就像一个属性内的MultiValueConverter =)。
希望它有帮助。
如果这真的是MultiBinding中的某种错误,那么也许我们应该以某种方式报告它。在哪里做? –
您是否尝试在.NET 4.0中执行相同操作?也许它现在已经修复。 (我现在不能尝试,因为我的VS 2010存在一些技术问题)。 – levanovd
谢谢;这是我需要知道的!是否有一种简单的方法来获取ConvertBack中这些属性的输入值? –
谢谢!值得一提的是,您希望将值转换回来的单个“内部”绑定应该设置为“Mode = TwoWay”。没有在文档中找到。 :/ –