Equiv. to Coalesce() in XAML Binding?
Asked Answered
R

3

7

In SQL I can do this:

Select Coalesce(Property1, Property2, Property3, 'All Null') as Value
From MyTable 

If Property1, 2 and 3 are all null, then I get 'All Null'

How do I do this in XAML? I tried the following, but no luck:

<Window.Resources>
    <local:Item x:Key="MyData" 
                Property1="{x:Null}"
                Property2="{x:Null}"
                Property3="Hello World" />
</Window.Resources>

<TextBlock DataContext="{StaticResource MyData}">
    <TextBlock.Text>
        <PriorityBinding TargetNullValue="All Null">
            <Binding Path="Property1" />
            <Binding Path="Property2" />
            <Binding Path="Property3" />
        </PriorityBinding>
    </TextBlock.Text>
</TextBlock>

The result should be 'Hello World' but instead it is 'All Null'

I hope my question is clear.

Rehearsal answered 4/8, 2011 at 19:51 Comment(0)
I
11

You'd have to build a custom IMultiValueConverter to do that and use a MultiBinding. PriorityBinding uses the first binding in the collection that produces a value successfully. In your case, the Property1 binding resolves immediately, so it's used. Since Property1 is null, the TargetNullValue is used.

A converter like this:

public class CoalesceConverter : System.Windows.Data.IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, 
            object parameter, System.Globalization.CultureInfo culture)
    {
        if (values == null)
            return null;
        foreach (var item in values)
            if (item != null)
                return item;
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, 
            object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

And MultiBinding like this:

<Window.Resources>
    <local:Item x:Key="MyData" 
                Property1="{x:Null}"
                Property2="{x:Null}"
                Property3="Hello World" />
    <local:CoalesceConverter x:Key="MyConverter" />
</Window.Resources>

<TextBlock DataContext="{StaticResource MyData}">
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource MyConverter}">
            <Binding Path="Property1" />
            <Binding Path="Property2" />
            <Binding Path="Property3" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
Indisputable answered 4/8, 2011 at 19:55 Comment(5)
I was hoping to do it all in XAMLRehearsal
@Jerry - Yeah, once you have the converter setup you can :-)Indisputable
A 4th Property on the class could do it, too; I suppose.Rehearsal
@Jerry - Yeah, that would work also return this.Property1 ?? this.Property2 ?? this.Property3;Indisputable
actually it would not because of INotifyPropertyChanged events not firing. I realize you could fake the event. But the converter handles it all.Rehearsal
H
3

Since you are binding to a String, null is a valid value for the PriorityBinding. I'm not sure what your Item class's property types are, but if you use Object, and set them to DependencyProperty.UnsetValue, you will get the behavior you are looking for.

The PriorityBinding documentation's remarks section describes how it works in more detail.

Huehuebner answered 4/8, 2011 at 20:2 Comment(3)
The only unfortunate part of this solution is the reliance on the Presentation assemblies inside my Entity Library. You are right, they are strings in this sample. I think making them Object would too loosely type my objects (esp. for just the sake of the XAML) ;) Thank you for your answer.Rehearsal
Yeah, in that case I'd definitely go with @CodeNaked's solution.Huehuebner
I don't want to use DependencyProperty.UnsetValue in the model classes in order to not to depend on GUI specifics there.Suppurative
O
0

The PriorityBinding is only looking for DependencyProperty.UnsetValue to advance to the next Binding. Since Property1 exists it is set and the PriorityBinding is taking the value of it.

For a pure XAML solution, this Style will do the job:

   <TextBlock>
        <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Text"
                        Value="{Binding Property1}" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Property1}"
                                 Value="{x:Null}">
                        <Setter Property="Text"
                                Value="{Binding Property2}" />
                    </DataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Property1}"
                                       Value="{x:Null}" />
                            <Condition Binding="{Binding Property2}"
                                       Value="{x:Null}" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Text"
                                Value="{Binding Property3}" />
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Property1}"
                                       Value="{x:Null}" />
                            <Condition Binding="{Binding Property2}"
                                       Value="{x:Null}" />
                            <Condition Binding="{Binding Property3}"
                                       Value="{x:Null}" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Text"
                                Value="All Null" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>

Although, it's a bit convoluted way of doing it, and IMHO, doesn't belong in the UI but in the ViewModel.

Only answered 4/8, 2011 at 20:32 Comment(1)
Oh well, need to type faster... :)Only

© 2022 - 2024 — McMap. All rights reserved.