I'm trying to write a universal control in which I can pass different data models (all implement INotifyPropertyChanged) with a converter. The data is passed in the control without problems and is displayed correctly (some of it also uses IMultivalueConverts, which work flawlessly). Although the data is modified, the IMultiValueConverter of the MainControl is not called.
The universal control should just show rectangles calculated from coordinates in an ObservableCollection.
I put DebugConverters on all bindings, and everything seems to be updated except the ConvertBack to the top. The SourceUpdate from the ListBox is also called.
I tried this converter with different NotifyOn...Updated, Mode and UpdateSourceTrigger, I always see the values changing in the control, but never the ConvertBack of the Main Control.
The partial data template used (Updates are done correctly), all the DegreeTo... converters are called both ways
<DataTemplate x:Key="RectangleWithLabel">
<Canvas IsHitTestVisible="True">
<Rectangle x:Name="RectangleROI" MouseLeftButtonDown="myCanvas_PreviewMouseLeftButtonDown" >
<!--
<Rectangle.Visibility>
<Binding Path="ROI" Converter="{StaticResource NullToVisibilityConverter}"/>
</Rectangle.Visibility>
-->
<Canvas.Left>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelConverterH}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged" >
<Binding Path="ROI.Begin.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelConverterV}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</Canvas.Top>
<Rectangle.Width>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelWidthConverter}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="ROI.End.PosH" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True"/>
</MultiBinding>
</Rectangle.Width>
<Rectangle.Height>
<MultiBinding Converter="{StaticResource DegreeToScreenPixelHeightConverter}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="ROI.Begin.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True"/>
<Binding Path="ROI.End.PosV" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" />
<Binding Path="DataContext.UsedCoordinateSystem" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" NotifyOnSourceUpdated="True" Mode="TwoWay" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</Rectangle.Height>
<Rectangle.Fill>#33FF0000</Rectangle.Fill>
<Rectangle.Stroke>#FF00FF00</Rectangle.Stroke>
<Rectangle.IsHitTestVisible>true</Rectangle.IsHitTestVisible>
</Rectangle>
The List View containing all the data:
<ListView ItemsSource="{Binding Rectangles, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" x:Name="listBox" Width="{Binding ActualWidth, ElementName=ImageControl, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" Height="{Binding ActualHeight, ElementName=ImageControl, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" ItemTemplate="{StaticResource RectangleWithLabel}" MouseMove="ListBox_MouseMove" DataContextChanged="ListBox_DataContextChanged" SourceUpdated="ListBox_SourceUpdated" IsSynchronizedWithCurrentItem="True" TargetUpdated="ListBox_TargetUpdated" />
The call to the ImageViewer from the parent, here the converter is called on the way to the ImageViewer, but the ConvertBack is never called:
<common:ImageViewer x:Name="ctrlImage" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<common:ImageViewer.DataContext>
<MultiBinding Converter="{StaticResource ConverterWhichIsOnlyCalledOneWay}" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding Path="." UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" NotifyOnSourceUpdated="True" NotifyOnTargetUpdated="True"/>
</MultiBinding>
</common:ImageViewer.DataContext>
</common:ImageViewer>
How can I get the ConvertBack to be called?
EDIT:
I made an overview of the controls and the converters. I think I was wrong, that the ConvertBack should be called, everything is updating, even without the big converter. But I'm still stuck when adding some elements to the ObservableCollection. When I add some values, the values appear in the DataContext of the MainWindow. But the Converter is not triggered. When I add the Observable.Count as Binding to the Big Converter, the update is triggered, but all bindings are lost.
Changes from the original code were: Changing all internal variables to DependencyProperties, this made updates reliable for the single entries in the ObservableCollection.
Overview of the GUI and the used converters
Time to answer the question myself:
I was wrong when assuming that a changes travels down throught the converter when there is a direct connection between two elements.
So if I have an element (just consider it as a leave of a tree) in a deeply nested and it is once displayed once through a converter (which changes some other objects) and the same time without a converter, the element is updated without calling the converter. WPF is so clever to see that the value is the same and does not pipe the change through the converter up and down.
Related
I have a button and want to pass multiple command parameter one being Binding and one is a constant string (in this case a constant string TDS)
I am trying to follow this link: Passing two command parameters using a WPF binding but this is for multibinding. In my case I am using 1 binding and one constant string. I tried the below but it is giving syntax error in VS.
<Button.CommandParameter>
<MultiBinding>
<Binding Path="."/>
<s:String>TDS</s:String>
</MultiBinding>
</Button.CommandParameter>
How do I fix this?
Try this:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource yourConverter}">
<Binding Path="."/>
<Binding>
<Binding.Source>
<s:String>TDS</s:String>
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
If your string is defined in resources you can reference it this way:
...
<x.Resources>
<s:String x:Key="stringKey">TDS</s:String>
</x.Resources>
...
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource yourConverter}">
<Binding Path="."/>
<Binding Source="{StaticResource stringKey}" />
</MultiBinding>
</Button.CommandParameter>
As mentioned in the comment a converter must be specified for MultiBinding.
I have a TextBlock with two properties (Text and Foreground) bound to the same ViewModel property.
Both also have converters. One of the converters checks the Text property and returns a 'dash' if the value is NaN. The other checks that the value is above, below or equals zero and accordingly sets the foreground to different colors.
XAML example:
<TextBlock>
<TextBlock.Text>
<Binding Path="AvgDistance" StringFormat="{}{0:N1}"
Converter="{x:Static converter:ValueToDash.Instance}"/>
</TextBlock.Text>
<TextBlock.Foreground>
<MultiBinding Converter="{x:Static converter:ValueToColor.Instance}">
<Binding Path="AvgDistance"/>
<Binding ElementName="currentPeriod" Path="IsChecked" />
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
Now I need that the ValueToDash converter fired before the ValueToColor converter, but it is always vice versa.
The Foreground property seems to be always set first, and only then the Text property is set.
Why is it so? And is it possible to reverse the order of setting?
You shouldn't rely on the order in which the properties are being set.
What you could do instead is to add another binding to your MultiBinding that binds to the Text property of the TextBlock:
<TextBlock>
<TextBlock.Text>
<Binding Path="AvgDistance" StringFormat="{}{0:N1}"
Converter="{x:Static converter:ValueToDash.Instance}"/>
</TextBlock.Text>
<TextBlock.Foreground>
<MultiBinding Converter="{x:Static converter:ValueToColor.Instance}">
<Binding Path="AvgDistance"/>
<Binding ElementName="currentPeriod" Path="IsChecked" />
<Binding Path="Text" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
Then the ValueToColor converter will be invoked (again) whenever the Text property is set to some new value.
I have bound the Checked event of a checkbox to a method. I am also passing two Command Parameters to that method through a converter (implementing IMultiValueConverter). In the code below, the two CommandParameter bindings are of type bool and type string respectively.
<CheckBox Name="cbTopLeftHeader" Content="Top Left Header">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding IncludeCheckedCommand}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiBindingConverter}" ConverterParameter="IncludeChecked">
<Binding ElementName="cbTopLeftHeader" Path="IsChecked"></Binding>
<Binding Source="{StaticResource TopLeft}"></Binding>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
Why is it that if I replace the line:
<Binding ElementName="cbTopLeftHeader" Path="IsChecked"></Binding>
With:
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked"></Binding>
In my converter the IsChecked parameter changes from type bool to type DependencyProperty (DependencyProperty.UnsetValue)?
How can I achieve passing the Checked property without having the element bind to itself by name?
Get rid the interaction trigger and use the Command and CommandParameter properties of the CheckBox:
<CheckBox Name="cbTopLeftHeader" Content="Top Left Header" Command="{Binding IncludeCheckedCommand}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource MultiBindingConverter}" ConverterParameter="IncludeChecked">
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked"/>
<Binding Source="{StaticResource TopLeft}"></Binding>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
Or stick with your current approach of binding to the CheckBox using ElementName. {RelativeSource Self} of an InvokeCommandAction is not a CheckBox.
I have a Textbox with a Value and a Unit bound like this:
<TextBox Text="{Binding Path=Value,StringFormat='{}{0} mm'}" />
The Unit mm should be also bound to the ViewModel Property Unit. This can be done via a Multibinding:
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Value"
Mode="TwoWay" />
<Binding Path="Unit"
Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
But with this I lose my Two Way Binding and I don't want to edit the Unit aswell. If ths user deletes "8 mm" and enteres an "8" the binding should automatically reevaluate the binding and add the unit as it is done via normal string format binding.
So finally I need something like this:
<TextBox>
<TextBox.Text>
<Binding Path="Value"
StringFormat="{Binding Path=ValueUnitStringFormat}" />
</TextBox.Text>
</TextBox>
But unfortunally StringFormat Property on BindingBase is not a DependencyProperty.
Anyone got a solution for this?
I have a IMultiValueConverter called Placer, being use like this:
<Rectangle Name="HostBox" Fill="#FFF4F4F5" Height="36" Stroke="Black" Canvas.Top="32"
Width="86" RadiusY="9.5" RadiusX="9.5">
<Canvas.Left>
<MultiBinding Converter="{StaticResource Placer}" ConverterParameter="0.5">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType={x:Type Canvas}}"/>
<Binding Path="Width" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Canvas.Left>
</Rectangle>
But I have many Rectangles, on which I want to apply the same logic, but with different ConverterParameter value. Do I have to include this not-so-small snippet under each Rectangle's Canvas.Left attached property? (rhetorical question... obviously there's a smarter way... but how?)
Try using a style. For instance, the following one is applied to all the rectangle instances but you could also give it a key and apply it individually to your rectangles:
<Style TargetType="Rectangle">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource Placer}" ConverterParameter="0.5">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType={x:Type Canvas}}"/>
<Binding Path="Width" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
In order to parameterize MultiBinding.ConverterParameter you may simply use a binding.
EDIT: I stand corrected about binding to MultiBinding.ConverterParameter: it is not possible since it is not a DependencyProperty but you can work around it.