How to skip updating some of the sub-bindings of a MultiBinding
? I have defined in code-behind (I had some troubles making it in XAML and I don't think it matters - after all code-behind is not less expressive then XAML) a MultiBinding
which takes two read-only properties and one normal property to produce a single value. In case of ConvertBack
the read-only properties are not modified (they sustain their value) and only the normal property is changed.
While defining the MultiBinding
the entire MultiBinding
was set to TwoWay
however particular sub-bindings where set appropriate (first two to OneWay
and the third two TwoWay
).
The problem occurs in a my own control. However for the sake of presentation I simplified it to a smaller control. The control presented in this example is a Slider
-like control allowing to select a value in [0.0; 1.0] range. The selected value is represented by the thumb and exposed as a DependencyProperty
.
Basically the control is build by a 1 row x 3 column Grid
where the thumb is in the middle column. To correctly position the thumb left column must be assigned width corresponding to selected position. However this width depends also on the actual width of the entire control and actual width of the thumb itself (this is because the position is given as a relative value in [0.0; 1.0] range).
When the thumb is moved the position should be updated appropriately however the thumb width and control width obviously do not change.
The code works as expected however when run in IDE during thumb moving Output window is cluttered with exceptions information as reported when MultiBinding
tries to set value to those two read-only properties. I suspect it is not harmful however it is somewhat annoying and misleading. And also it means that the code does something else then I wanted it to do as I didn't want to set those properties (this matters in case they were not read-only and this would actually modify them).
MultiBinding
documentation in Remarks section mentions that individual sub-bindings are allowed to override the MultiBinding
mode value but it doesn't seem to work.
Maybe this could be solved somehow by expressing the dependency on the control and thumb widths (the read-only properties) somehow differently. For example registering to their notifications separately and enforcing update upon their change. However it does not seem natural to me. MultiBinding
does on the other hand as after all left column width does depend on those three properties.
Here is the example XAML code.
<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>
And here is the corresponding code-behind
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);
}
}
}
}