Proper Path reference for multiBinding converter parameter with combobox control - wpf

With all the styling information around controls, and what's nested, control template, and triggers all around, I'm trying to figure out the following.
Take a combobox control. it has a control template for the toggle button component which shows the normal display in standard display (not drop-down mode) which shows the display value and toggle button to activate the drop-down.
<ControlTemplate TargetType="ToggleButton" x:Key="baseComboBoxToggleButton" >
<!-- overall border covering left side display value and the actual toggle button -->
<Border x:Name="Border" Grid.ColumnSpan="2" />
<!-- area in left side (column=0) that shows the DISPLAY value -->
<Border x:Name="ShowDisplayValueArea" Grid.Column="0" />
<!-- second column using a path to draw the glyph down arrow -->
<Path Grid.Column="1" />
<Triggers for the toggle button ... />
</ControlTemplate>
Then, you have the main combobox control that uses the template of the toggle button above
<ControlTemplate TargetType="ComboBox" x:Key="ComboBoxGridControlTemplate" >
<Grid>
<ToggleButton Name="ToggleButton"
Template="{StaticResource baseComboBoxToggleButton}"
... />
</Grid>
</ControlTemplate>
So, I'm trying to ULTIMATELY change the background color of the "ShowDisplayValueArea" based on the results of a MultiBinding converter. If I place the multibinding converter in the toggleButton control template area such as..
<MultiBinding Converter="{StaticResource myMultiParmConverter}">
<Binding Path="." RelativeSource="{RelativeSource Self}" />
</MultiBinding>
The first "value" in the values object array is correctly passing the instance of the toggle button control template. The entire object reference, not just the name.
public object Convert(object[] values,
Type targetType,
object parameter,
CultureInfo culture)
So, how I tell the Binding parameter to pass the actual Combobox that the toggle button came from (ie: the parent to the toggle button) so I get the actual entire combobox control passed as the parameter.

Found it... completely by accident...
While in the debugger of my converter class, I was looking at the object reference in the debugger window. Then, since it was an object, I clicked on the magnifying glass to the right side of the "Value" column. Doing so, brought up the "WPF Visualizer" (never used/seen that before). So, seeing it from that perspective, and saw that it's "TemplatedParent" was actually the object I wanted. There were obviously other properties I could consider playing with at some other time, but the TemplatedParent was the one I wanted.
So, I changed the binding from
<Binding Path="." RelativeSource="{RelativeSource Self}" />
to
<Binding Path=".TemplatedParent" RelativeSource="{RelativeSource Self}" />
and correctly got the actual Combobox control passed as the argument to my converter. Now, I can utilize any/all properties of the combobox directly in the converter without having to explicitly pass them all individually.

Related

I can't get the property of the view model in the multi value converter

I have a DataGrid. I want to decide when to collapse a column and when to show it.
This is my code:
<UserControl.Resources>
<ResourceDictionary>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}" />
</ResourceDictionary>
<UserControl.Resources>
<DataGridTextColumn.Visibility>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="{StaticResource ProxyElement}" Path="DataContext.MyPropertyInViewModel" />
<Binding Source="1"/>
</MultiBinding>
</DataGridTextColumn.Visibility>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//Do the conversion
}
I need the proxy element to access the view model from an element that doesn't belong to the visual tree.
In the MultiBinding, the second binding works. In the converter I receive the value 1, but the problem is with the first element. I don't get the property of the view model, that it is a string. I get a DependencyProperty.UnsetValue.
How can I pass a property of my view model to the multi-value converter?
The ProxyElement will not bind the data context in Resources as it is not part of the visual tree. To make this work, define the FrameworkElement anywhere in the visual tree, e.g. like below in a Grid. The DataContext is inherited, but you can also set it explicitly. Set the Visibility of the proxy to Collapsed, so it is hidden.
<Grid>
<!-- ...grid definitions. -->
<FrameworkElement Grid.Row="42" x:Name="ProxyElement" Visibility="Collapsed"/>
</Grid>
Reference it using x:Reference, since ElementName bindings only work in the visual tree, but columns are not part of it.
<DataGridTextColumn.Visibility>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.InitialDepositAmount"/>
<Binding Source="1"/>
</MultiBinding>
</DataGridTextColumn.Visibility>
A better way is to use a Freezable as binding proxy. Those can access the data context even outside of the visual tree. See this related post that shows an approach with a custom BindingProxy, that also works in Resources and without x:Reference.

Using converter in Validation.Errors binding

In my WPF application (.Net 4.5) I want to provide extended visual feedback about validation results to the UI. The data layer's validation engine returns warnings and errors via INotifyDataErrorInfo interface.
I have the following XAML to display red or orange border depending on the error type and the list of error messages. Here errorToColor is a resource key of the value converter which returns red brush if there is at least one error in the Validation.Errors collection and orange brush if there are only warnings.
<TextBox Name="MappingName" Text="{Binding Path=Mapping.Name, NotifyOnValidationError=True}" >
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="{Binding Converter={StaticResource errorsToColor}}" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
<ListView DisplayMemberPath="ErrorContent" ItemsSource="{Binding}" />
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Now let's see what happens when I type some 'not valid' text in the TextBox.
Typed 'Text1' and changed focus.Debugger stepped into the converter and both validators resulting in two items in the ListView (1 error and 1 warning) and a red border. [OK]
Typed 'Text' to correct the error, changed focus.
The value converter wasn't even hit! Of course, the same red border. But the ListView has changed and shows only one warning.
Can somebody explain what's going on? Why the ListView receives collection change notification and the Border not? Is it because ListView is an ItemsControl and the Validation.Errors is wrapped into the CollectionView?
For those who are interested. The errorsToColor converter wasn't fired because the Validation.Errors collection didn't raise PropertyChanged event (needed trigger binding coverter) when errors were added or removed.
In order to raise PropertyChanged event we need to bind to a property which is changed on each error is added, for example Count. I still need the Errors collection itself in the converter, so I used a multi-binding here.
<Border BorderThickness="1">
<Border.BorderBrush>
<MultiBinding Converter="{StaticResource errorsToColor}">
<Binding Path="." />
<Binding Path=".Count" />
</MultiBinding>
</Border.BorderBrush>
<AdornedElementPlaceholder Name="adornedElement" />
</Border>
Now the errorsToColor converter (which is now implements IMultiValueConverter) is executed every time new error is added / removed.

How to bind to the sum of two data bound values in WPF?

I have designed an analog clock control. It uses the stroke from two ellipses to represent an outer border and an inner border to the clock face.
I have exposed properties in the UserControl that allow a user to alter the thickness of these two borders. The Ellipse.StrokeThickness properties are then bound to these UserControl properties. At the moment, I am binding the UserControl property for the outer border thickness to the margins of the inner elements so that they are not hidden when the border size is increased.
<Ellipse Name="OuterBorder" Panel.ZIndex="1" StrokeThickness="{Binding OuterBorderThickness,
ElementName=This}" Stroke="{StaticResource OuterBorderBrush}" />
<Ellipse Name="InnerBorder" Panel.ZIndex="5" StrokeThickness="{Binding InnerBorderThickness,
ElementName=This}" Margin="{Binding OuterBorderThickness, ElementName=This}"
Stroke="{StaticResource InnerBorderBrush}">
...
<Ellipse Name="Face" Panel.ZIndex="1" Margin="{Binding OuterBorderThickness, ElementName=This}"
Fill="{StaticResource FaceBackgroundBrush}" />
...
The problem is that if the inner border thickness is increased, this does not affect the margins and so the hour ticks and numbers can become partially obscured or hidden. So what I really need is to be able to bind the margin properties of the inner controls to the sum of the inner and outer border thickness values (they are of type double).
I have done this successfully using 'DataContext = this;', but am trying to rewrite the control without this as I hear it is not recommended. I also thought about using a converter and passing the second value as the ConverterParameter, but didn't know how to bind to the ConverterParameter. Any tips would be greatly appreciated.
EDIT>>
Thanks to Kent's suggestion, I've created a simple MultiConverter to add the input values and return the result. I've hooked the SAME multibinding with converter XAML to both a TextBlock.Text property and the TextBlock.Margin property to test it.
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SumConverter}" ConverterParameter="Add">
<Binding Path="OuterBorderThickness" ElementName="This" />
<Binding Path="InnerBorderThickness" ElementName="This" />
</MultiBinding>
</TextBlock.Text>
<TextBlock.Margin>
<MultiBinding Converter="{StaticResource SumConverter}" ConverterParameter="Add">
<Binding Path="OuterBorderThickness" ElementName="This" />
<Binding Path="InnerBorderThickness" ElementName="This" />
</MultiBinding>
</TextBlock.Margin>
</TextBlock>
I can see the correct value displayed in the TexBlock, but the Margin is not set. Any ideas?
EDIT >> >>
Interestingly, the Margin property can be bound to a data property of type double, but this does not seem to apply within a MultiBinding. As advised by Kent, I changed the Converter to return the value as a Thickness object and now it works. Thanks Kent.
Sounds like you're looking for a MultiBinding with a converter capable of evaluating an expression. My ExpressionConverter permits exactly this. Of course, if you don't want a dependency on a third party library just for this, you could write your own multi-value-converter that adds two values together.
I have done this successfully using 'DataContext = this;', but am trying to rewrite the control without this as I hear it is not recommended.
You can bind to properties of a user control without changing its DataContext; just add an x:Name attribute to its root element and then bind using {Binding ElementName=Root, Path=MyProperty}. It's much simpler to do this than it is to use converters to inject code into the binding.

WPF: Menu Items only bind command parameters once

Ive noticed this a couple of times when using menus with commands, they are not very dynamic, check this out. I am creating a menu from a collection of colours, I use it to colour a column in a datagrid. Anyway when i first bring up the menu (its a context menu) the command parameter binding happens and it binds to the column that the context menu was opened on. However the next time i bring it up it seems wpf caches the menu and it doesnt rebind the command parameter. so i can set the colour only on the initial column that the context menu appeared on.
I have got around this situation in the past by making the menu totally dynamic and destroying the collection when the menu closed and forcing a rebuild the next time it opened, i dont like this hack. anyone got a better way?
<MenuItem
Header="Colour"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColumnColourCollection}"
ItemTemplate="{StaticResource colourHeader}" >
<MenuItem.Icon>
<Image
Source="{StaticResource ColumnShowIcon16}" />
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style
TargetType="MenuItem"
BasedOn="{StaticResource systemMenuItemStyle}">
<!--Warning dont change the order of the following two setters
otherwise the command parameter gets set after the command fires,
not mush use eh?-->
<Setter
Property="CommandParameter">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<local:ColumnAndColourMultiConverter/>
</MultiBinding.Converter>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}" Path="Column"/>
<Binding Path="."/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColourColumnCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The problem is that ContextMenu's are apparently the root of their own visual tree I read somewhere that it takes the datacontext its parent, but only once on loading, so if the parents datacontext changes the menuitems does not. (unfortunately I can't find a link for that right not)
I have encountered this problem before, and what I did was use Josh Smith's Virtual Branch Pattern. It's fairly technical but the article helped me understand really well what was going on with this visual tree nonsense.
Essentially you create this bridge that binds to the view's datacontext. The bridge is created as a static resource, allowing you to bind to it from the context menu even if it is outside the visual tree.
Add this to your xaml:
<Window.Resources>
<!-- This is the "root node" in the virtual branch
attached to the logical tree. It has its
DataContext set by the Binding applied to the
Window's DataContext property. -->
<FrameworkElement x:Key="DataContextBridge" />
</Window.Resources>
<Window.DataContext>
<!-- This Binding sets the DataContext on the "root node"
of the virtual logical tree branch. This Binding
must be applied to the DataContext of the element
which is actually assigned the data context value. -->
<Binding
Mode="OneWayToSource"
Path="DataContext"
Source="{StaticResource DataContextBridge}"
/>
</Window.DataContext>
This is the bridge I spoke of. It takes the datacontext and __pushes it_ to to the bridges datacontext, which is (as I said before) a static resource.
Then you simply this to the contextmenu's datacontext:
DataContext="{Binding
Source={StaticResource DataContextBridge},
Path=DataContext}"
Now throw away all the relative pathing etc and use regular binding inside the menu items, and you should be fine. The datacontext will update as usual.
Just one note:
You will obviously have to have some property in the datacontext to discern which command to use, but i'm sure you can figure it out. This solution just deals with the way contextmenu's dont update

Dependency propery for ValueConverter or rebind control?

I have custom control with some text in content template:
<ControlTemplate TargetType="{x:Type local:TouchScreenKey}">
<TextBlock><ContentPresenter Content="{TemplateBinding Title, Converter={StaticResource CaseConverter}}" /></TextBlock>
</ControlTemplate>
and custom IValueConverter CaseConverter - with property UpperCase. So, when UpperCase property of converter set to true it converts text to upper case on binding. Everything goes fine if I change UpperCase in markup. But if i change property in runtime - nothing happens - because changing converter property not force my control to rebind.
How can I rebind control which uses converter on converter's property change?
As far as I know there is no way to tell converter to update all targets. Converter knows nothing about targets. It's just a stateless function, F(x), takes one value and returns another.
To update property you should ask WPF to do so. For example, if property is bound to some source property, you can implement INotifyPropertyChanged, and trigger PropertyChanged event. Or you can ask BindingOperations to get binding expression, and invoke UpdateTarget() manually.
Maybe converter isn't the best choice here? You may also want to consider using Attached Properties to change capitalization.
It may help someone - I found solution - using multibinding
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter>
<ContentPresenter.Content>
<MultiBinding Converter="{StaticResource MultiCaseConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Title" />
<Binding ElementName="TouchKeyboard" Path="UpperCase" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter>
and wrote MultiCaseConverter - which convert first parameter depending from second (UpperCase)

Resources