MultiBinding across multiple DataTemplates with same ItemsSource - c#

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.

Related

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>

MultiBinding with PriorityBinding workaround

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.

Implementing IValueConverter to get Name from ID

I have a table column called LOCATION_ID. I want to show the LOCATION_NAME which is located in the LOCATION table instead of the ID.
I am trying to implement IValueConverter but can't figure out how to do it. I am using WPF with entity framework.
How would I pass the ID value to this converter?
I have a method name GetLocationNameByID(). Where in the converter would I Call this method? And how would I bind the return value to the datagrid XAML?
Implementing the IValueConverer interface is pretty straightforward. In the XAML, you'd have something like this:
<Window x:Class="CarSystem.CustomControls.AlarmDisplayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNameSpace"
DataContext="{Binding Path=MyDataContextObject, RelativeSource={RelativeSource Self}">
<Window.Resources>
<local:MyIValueConverter x:Key="Converter" />
</Window.Resoures>
<TextBox Text="{Binding Converter={StaticResource Converter} Path=MyProperty}" />
</Window>
When WPF detects a change in the value of the MyProperty in the MyDataContextObject, it calls the MyIValueConverter object's Convert method, passing the value of the property as the Value parameter. Your implementation of the Convert method does what it has to do & returns the string to be displayed.
you can use multi-binding, have your value converter implement the IMultiValueConverter interface. The converter takes an object array, each of which can be a binding to your XAML.
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource MyConverter}">
<MultiBinding.Bindings>
<Binding Path="SomeProperty" />
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding.Bindings>
</MultiBinding>
</TextBox.Text>
</TextBox>

Multibinding using Window's DataContext

I'm trying to create drag adorner based on whether the Customer DependancyProperty of a Window is null. I have this in the Window's resources. The first part of the binding is set (the item being dragged), but the second (the DependancyProperty on the WIndow) shows as UnsetValue.
The property is definitely initialized as it's used as the window's datacontext.
<Window x:Name="root"
...
>
<Window.Resources>
<DataTemplate x:Key="DragAdorner">
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource vehicleDragConverter}">
<Binding/>
<Binding Path="Customer" ElementName="root"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
customer is not a direct property of element window.
use <Binding Path="DataContext.Customer"..../>
or you can also use RelativeSource=FindControl Window in binding.

Compound DisplayMemberPath for a combobox

I need to create a DisplayMemberPath that is a compound of a few properties (ie object.category.Name+" -> "+object.description) I'm pretty sure I can do this by creating a dynamic data type that encapsulates the object and also adds a new property called displayField that is what I need but I'm wondering if there is a more proper way to do this that does not involve creating a new object. Any ideas?
DisplayMemberPath is just a "shortcut" for when you don't need a complex template for items. If you need more control, use ItemTemplate instead:
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} -> {1}">
<Binding Path="Category.Name" />
<Binding Path="Description" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Categories