Search code examples
c#wpfxamlmultidatatrigger

Make Image.Source depend on comparison of TextBox and Label values


I have the following XML:

<Items>
  <Item>
    <Name>Item One</Name>
    <MyValue>42</MyValue>
  </Item>
</Items>

and XAML:

<DockPanel>
    <DockPanel.Resources>
        <XmlDataProvider x:Key="ItemsXml" XPath="Items/Item"
            Source="Resources/Items.xml"/>
    </DockPanel.Resources>

    <ListBox
        ItemsSource="{Binding Source={StaticResource ItemsXml}}"
        DisplayMemberPath="Name" Name="itemList"/>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.Resources>
            <Style x:Key="ValueFormat" TargetType="Label">
                <Setter Property="ContentStringFormat" Value="{}/{0,3}"/>
            </Style>
        </Grid.Resources>

        <Image Source="Icons/ConditionFalse.png" Grid.Column="0"/>

        <TextBox Name="myTextBox" VerticalAlignment="Center" Grid.Column="1"/>

        <Label
            Name="myLabel"
            DataContext="{Binding SelectedItem, ElementName=itemList}"
            Style="{StaticResource ValueFormat}"
            VerticalAlignment="Center"
            Content="{Binding XPath=MyValue}"
            Grid.Column="2"/>
    </Grid>
</DockPanel>

I want to make Image.Source depend on the condition that TextBox.Text be equal to the reference value of Label*. The reference value is a binding to the XML file so using that as a basis for comparison is also fine. The TextBox will have a binding to a property that doesn't exist yet so that's available as an option.

*The Label currently uses ContentStringFormat to alter its value. If this is problematic it can be gotten rid of.

I can use a DataTrigger to bind directly to a property representing this condition but that feels like a hack and I would prefer to avoid that. I tried setting up a MultiDataTrigger as shown below but firstly I couldn't get the condition working for the Label (it did work for the TextBox), secondly a constant value is no good in my case. It also doesn't have an "else" clause or a default value for when the condition evaluates to false but if the check can be made in the first place I'm hoping that will be a non-issue.

<Image Grid.Column="0">
    <Image.Style>
        <Style TargetType="{x:Type Image}">
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Value="/ 42"
                            Binding="{Binding Text, ElementName=myTextBox}"/>
                        <Condition Value="/ 42"
                            Binding="{Binding Content, ElementName=myLabel}"/>
                    </MultiDataTrigger.Conditions>
                    <Setter Property="Source" Value="Icons/ConditionTrue.png"/>
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>
</Image>

Solution

  • Harry's comment got me in the right direction.

    XAML:

    <Grid.Resources>
        <local:MyImageConverter x:Key="MyImageConverter"/>
    </Grid.Resources>
    
    <Image Grid.Column="0" Grid.Row="0">
        <Image.Style>
            <Style TargetType="{x:Type Image}">
                <Setter Property="Source" Value="Icons/ConditionFalse.png"/>
                <Style.Triggers>
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <MultiBinding
                                Converter="{StaticResource MyImageConverter}">
                                <Binding ElementName="myTextBox" Path="Text"/>
                                <Binding ElementName="myLabel" Path="Content"/>
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter Property="Source" Value="Icons/ConditionTrue.png"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>
    

    C#

    public class MyImageConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            System.Xml.XmlElement labelValue = values[1] as System.Xml.XmlElement;
            if (labelValue != null)
            {
                return ((string)values[0]) == labelValue.InnerText;
            }
            return false;
        }
    
        public object[] ConvertBack(object value, Type[] targetType,
           object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    

    This very quickly gets very messy. I'm using this for now but as time permits I will rework it into a ViewModel as per Whyaduck's answer.