MultiBinding with PriorityBinding workaround - c#

I'm currently working on an app in which borders are displaying objects in a "change-state". The visibility of these borders is controlled using "BorderThickness" and a priority binding (the visibility is set in a converter). Now there's a request to make the thickness of the change-state border configurable (obtained at runtime).
My initial attempt was changing the converter into a multi-binding converter:
<Border.BorderThickness>
<MultiBinding Converter="{StaticResource StringToBorderThickness}">
<PriorityBinding>
<Binding Path="Change1" />
<Binding Path="Change2" />
</PriorityBinding>
<Binding Path="DataContext.Settings.ChangeStateThickness" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" />
</MultiBinding>
</Border.BorderThickness>
This does NOT work because WPF does not support MultiBinding+PriorityBinding at the same time (as it turns out).
Can anyone recommend a workaround? I can't remove the Priority Binding and I can't find a different way to control the border visibility (BorderVisibility also makes the object IN the border invisible)
Thank you.

Related

MultiBinding across multiple DataTemplates with same ItemsSource

I have two data templates that make use of a multi-binding to control the visibility of a button, e.g. in data template A
<Button.Visibility>
<MultiBinding Converter="{StaticResource HideFirstOrderedItemConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="Items"/>
<Binding Path="Entity"/>
</MultiBinding>
</Button.Visibility>
and in data template B
<Button.Visibility>
<MultiBinding Converter="{StaticResource HideFirstOrderedItemConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="Items"/>
<Binding Path="Entity"/>
</MultiBinding>
</Button.Visibility>
The data templates are bound to the same list box items source and a data template selector is used to determine which datatemplate to use based on a property of the item in the list (Note: the datatemplate forms part of the ItemTemplate for the ListBox).
Question: Will wpf trigger the MultiBinding converter HideFirstOrderedItemConverter if either the binding to Items or Entity changes, or do both Items and Entity have to change?
Currently my binding on data template A doesn't fire when the Items property changes (an ObservableCollection that has items added and removed) and Entity does not change. Ultimately I would like the binding to fire on both data templates whenever the Items property changes.
If I comment out the binding to Entity in both templates as follows
<Button.Visibility>
<MultiBinding Converter="{StaticResource HideFirstOrderedItemConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="Items"/>
<!-- <Binding Path="Entity"/> -->
</MultiBinding>
</Button.Visibility>
My converter gets hit when I add an item to the collection, but only for the data template asscoiated with the item just added. I'd like it to get hit for all items.
UPDATE:
The comments below from #ASh and #GazTheDestroyer helped my understanding here. I need to notify my view of a collection changed event using a property changed event (collection changed events are NOT surfaced to the binding framework). So by updating my XAML to
<Button.Visibility>
<MultiBinding Converter="{StaticResource HideFirstOrderedItemConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="DataContext.Entities"/>
<Binding Path="Entity"/>
</MultiBinding>
</Button.Visibility>
where DataContext.Entities is the undelying ObservableCollection, and in the bound view model hook into the collection changed event of that ObservableCollection, i.e.,
Entities.CollectionChanged += CollectionChanged
where the CollectionChanged callback raises the notify property changed event on the Entities ObservableCollection. I believe this is the way to do it (might be nice to abstract this behaviour into its own type).
Will wpf trigger the MultiBinding converter HideFirstOrderedItemConverter if either the binding to Items or Entity changes, or do both Items and Entity have to change?
Either. The problem is that the Items property of the ListBox isn't set when you add or remove items from the source collection. Bind to Items.Count instead:
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}"
Path="Items.Count "/>
This property will change when you add or remove items from the source collection.
Also note that your Entity property must raise the PropertyChanged event for the converter to be re-invoked when you set it.

Passing incorrect values into MultiValueConverter by MultiBinding

I've been trying to implement a dynamic ToolTip (WPF) for RadioButton (the ToolTip switches, when IsEnabled of the RadioButton changes). I wanted to achieve this with a MultiValueConverter, which would be sort of a general Converter, that accepts 3 values - the IsEnabled value, enabled ToolTip and disabled ToolTip in this exact order.
But sadly I have encountered an issue, that I haven't been able to solve yet. Basically when the code reaches the Convert Method, the Array of values is filled with DependencyProperty.UnsetValue items.
What I managed to find while googling was, that the problem is propably caused by having a wrong DataContext as mentioned here WPF MultiBinding in Convertor fails ==> DependencyProperty.UnsetValue , but I feel like I have tried every combination of RelativeSources and DataContexts, that I could come up with and nothing helped.
Here is the sample code of the View:
<window.Resources>
<local:BooleanToStringConverter x:Key="BooleanToStringConverter"/>
</window.Resources>
<Grid>
<RadioButton x:Name="RadioButton" ToolTipService.ShowOnDisabled="True"
IsEnabled="{Binding IsRadioButtonEnabled}" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Radio">
<RadioButton.ToolTip>
<ToolTip>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource BooleanToStringConverter}">
<Binding ElementName="RadioButton" Path="IsEnabled"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type local:MainWindow}}" Path="ViewModel.EnabledToolTip"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type local:MainWindow}}" Path="ViewModel.DisabledToolTip"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ToolTip>
</RadioButton.ToolTip>
</RadioButton>
So the result, that I expect from this, is that the correct values will be passed into the Converter (in this case value of IsEnabled and string values of Enabled/Disabled ToolTips).
If anybody has any ideas, I would very much appreciate to hear them :).
Thanks in advance.
I managed to fix this by explicitly setting DataContext on the RadioButton and removing the RelativeSources within the MultiBinding. Though I don't understand why it did not work with the RelativeSources, it works. Here is the code, in case of anyone reading this in the future:
<RadioButton x:Name="RadioButton" ToolTipService.ShowOnDisabled="True"
DataContext="{Binding ViewModel, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"
IsEnabled="{Binding IsRadioButtonEnabled}" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Radio">
<RadioButton.ToolTip>
<ToolTip>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource BooleanToStringConverter}">
<Binding Path="IsRadioButtonEnabled"/>
<Binding Path="EnabledToolTip"/>
<Binding Path="DisabledToolTip"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ToolTip>
</RadioButton.ToolTip>
</RadioButton>

WPF MultiBinding fails in Syncfusion TabItemExt header

I have a subclass of TabItem as follows, for which I'm trying to set the Header property. I've tried this with a MultiBinding:
<DataEditPane x:TypeArguments="MyType" x:Class="MyDataEditPane">
<DataEditPane.Header>
<MultiBinding StringFormat="Hello world {0} {1}">
<Binding Path="BoundVariable1" />
<Binding Path="BoundVariable2" />
</MultiBinding>
</DataEditPane.Header>
</DataEditPane>
But it fails as such:
System.Windows.Data Error: 28 : MultiBinding failed because it has no valid Converter. MultiBindingExpression:target element is 'MyDataEditPane' (Name=''); target property is 'Header' (type 'Object')
System.Windows.Data Error: 28 : MultiBinding failed because it has no valid Converter. MultiBindingExpression:target element is 'MyDataEditPane' (Name=''); target property is 'Header' (type 'Object')
I'd always thought the StringFormat served the role of the converter, but perhaps not?
Wrapping the fields together in some kind of container, like a Label, also doesn't seem to work:
<DataEditPane x:TypeArguments="MyType" x:Class="MyDataEditPane">
<DataEditPane.Header>
<Label>
<Label.Text>
<MultiBinding StringFormat="Hello world {0} {1}">
<Binding Path="BoundVariable1" />
<Binding Path="BoundVariable2" />
</MultiBinding>
</Label.Text>
</Label>
</DataEditPane.Header>
</DataEditPane>
In this case, the .ToString() representation of the label ("System.Windows.Controls.Label") is shown as the header.
Note that a single binding works just fine:
<DataEditPane x:TypeArguments="MyType" x:Class="MyDataEditPane">
<DataEditPane.Header>
<Binding Path="BoundVariable1" />
</DataEditPane.Header>
</DataEditPane>
If it matters, I'm using the Syncfusion TabItemExt as one of my superclasses in the inheritance hierarchy, but as that class doesn't override the Header property I don't think that makes a difference.
What am I doing wrong? I know I can make another property in the ViewModel to act as the Header (and then single-bind that) but I want to learn how to do this properly in XAML.
Try a TextBlock instead of a Label. The following code worked fine for me.
I tried this:
<Window x:Class="ListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ListBox" Title="Window1" Height="300" Width="300">
<Window.DataContext>
<local:TextVM/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Text1}" />
<TextBox Text="{Binding Text2}" />
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="Hello World {0} - {1}">
<Binding Path="Text1" />
<Binding Path="Text2" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
I wonder if StringFormat is only valid in cases where a string is expected rather than an object.
There's an example on MSDN here: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.stringformat.aspx
Multi-Bindings requires a converter, i think that a converter that you may use is the StringFormatConverter, it is a IMultiValueConverter so works for multibindings.
Maybe you should adapt it to your case.
Hope this could be useful for you...

Clip a 4:3 image in WPF with a circle

I am trying to clip a 4:3 image with a circle within a grid control.
I need the circle clip to reveal the image from the middle in a perfect circle. See below.
alt text http://www.cse.unsw.edu.au/~vjro855/Untitled.png
The circle should dynamically re-size with the image.
I have tried Canvas.Clip and Ellipse+VisualBrush without achieving the correct behavior.
Thanks!
Solved my problem.
The solution was to use converters as part of the Grid.Clip property. I used code from the following site.
http://blogorama.nerdworks.in/entry-CenteringelementsonacanvasinWP.aspx
The challenge I encountered was having to use the EllipseGeometry instead of just Ellipse.
Ellipse uses height and width whilst EllipseGeometry uses radiusx,y and center.
With Ellipse I could have just set height and width to the height of the image to get the clip i required.
Would be a lot simpler if expressions worked with binding ie. {Binding Path=expr}
<Grid>
<Grid.Clip>
<EllipseGeometry>
<EllipseGeometry.RadiusX>
<MultiBinding
Converter="{StaticResource HalfValue1}">
<Binding
ElementName="vemap"
Path="ActualHeight" />
</MultiBinding>
</EllipseGeometry.RadiusX>
<EllipseGeometry.RadiusY>
<MultiBinding
Converter="{StaticResource HalfValue1}">
<Binding
ElementName="vemap"
Path="ActualHeight" />
</MultiBinding>
</EllipseGeometry.RadiusY>
<EllipseGeometry.Center>
<MultiBinding
Converter="{StaticResource HalfValue}">
<Binding
ElementName="vemap"
Path="ActualHeight" />
<Binding
ElementName="vemap"
Path="ActualWidth" />
</MultiBinding>
</EllipseGeometry.Center>
</EllipseGeometry>
</Grid.Clip>
<Image>
</Grid>

WPF - Workaround for ValidationRule not being a DependencyObject

What is the best solution for not being able to use data binding on a ValidationRule property since ValiationRule is not a DependencyObject?
Below is an example of what I would like to do. I want to be able to validate the text in the TextBox against some other DependencyProperty.
<TextBox Name="myTextBox">
<TextBox.Text>
<Binding Path="MySource" UpdateSourceTrigger="PropertyChanged">
<base:EqualsRule Target="{Binding MyTarget}" />
</Binding>
</TextBox.Text>
</TextBox>
You could use Josh Smith's virtual branch approach.

Categories