3

As a preface, this question comes from extending this answer on how to make the selected item look different from the dropdown items in a ComboBox.

I'm trying to make my custom selected item use information from the ComboBox's Tag attribute. Since I need to use a converter, I' using a mutli-converter in order to be able to send "myself" to the conversion class. Thus, I have these two templates:

<DataTemplate x:Key="SelectedItemTemplate">
    <StackPanel Orientation="Horizontal">
        <TextBlock>
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource SelectedConverter}">
                    <Binding RelativeSource="{RelativeSource Self}" />
                    <Binding StringFormat="This is here so it's called every time" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        <TextBlock Text="{Binding}" />
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="DropDownItemsTemplate">
    <TextBlock Grid.Column="0" Text="{Binding}"  Margin="0,0,10,0" VerticalAlignment="Center" />
</DataTemplate>

And my converter:

class SelectedConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var retVal = "";

        // The value passed in should be the TextBox in the ComboBoxItem
        var element = values[0] as DependencyObject;

        // Try to find it's parent RoleComboBox
        while (element != null && !(element is ComboBox))
        {
            element = VisualTreeHelper.GetParent(element);
        }

        // If we didn't find anything, return an empty string
        if (element == null) return retVal;

        // Otherwise, get the role from the ComboBox
        var tag = (element as ComboBox).Tag;

        if (tag?.ToString().Contains("Custom") ?? false)
        {
            retVal = "Custom Stuff ";
        }

        return retVal;
    }

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

XAML for the ComboBox

<ComboBox Name="myComboBox" Grid.Column="0" Tag="My Custom Tag" ItemTemplateSelector="{StaticResource CbTemplateSelector}">
    <ComboBox.Items>
        <ComboBoxItem Content="A" />
        <ComboBoxItem Content="B" />
        <ComboBoxItem Content="C" />
        <ComboBoxItem Content="D" />
        <ComboBoxItem Content="E" />
    </ComboBox.Items>
</ComboBox>

And all of this nicely produces this:

Screen Snip

Now, what I need to do is have the "Custom Stuff C" change to something else when I change the Tag property. So far, it will only change when I change the selected item, but I need it to change without the user having to change it to D, and then back to C.

From reserach, I've tried forcing the updating with the below code, but it does not work.

myComboBox.GetBindingExpression(ComboBox.ItemTemplateProperty)?.UpdateTarget();
myComboBox.GetBindingExpression(ComboBox.ItemTemplateSelectorProperty)?.UpdateTarget();
myComboBox.GetBindingExpression(ComboBox.SelectionBoxItemTemplateProperty)?.UpdateTarget();
myComboBox.GetBindingExpression(ComboBox.TemplateProperty)?.UpdateTarget();
myComboBox.UpdateLayout();
myComboBox.OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, new List<bool> { }, new List<bool> { }));

Note that I do actually have a custom control that inherits from ComboBox, so I do have access to protected methods (which is how the last line of code above works). I just extracted that part out in order to give a small subset of reproducable code.

At this point, I'm not sure what else to try. So how can I force my binding to update, based on how I'm doing it?

Community
  • 1
  • 1
David
  • 4,744
  • 5
  • 33
  • 64
  • 1
    Try to bind directly to ComboBox.Tag, like this: {Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=Tag} – Evk Nov 08 '16 at 16:18
  • @Evk, I can't do that because I don't want what's actually in the Tag. I want to use what's in the Tag to make it be something else. Hence the converter. – David Nov 08 '16 at 16:33
  • I understand, but I mean instead of this "" in your multibinding, bind directly to Tag as described above (but still use multibinding). Though not sure why you use multibinding, in converter you use just first parameter, so you can as well use regular binding with converter. – Evk Nov 08 '16 at 16:35
  • Main point is: whether you use multibinding or regular converter - if you bind (in one of your several bindings) directly to ComboBox.Tag - binding will be refreshed when this Tag change. – Evk Nov 08 '16 at 16:38
  • @Evk, Trying it now. Preliminary seems to be working. Care to move yours to an answer? – David Nov 08 '16 at 16:43

1 Answers1

2

Instead of binding to self and then searching for parent combobox in code, just use built-in methods to do that:

<TextBlock.Text>
    <MultiBinding Converter="{StaticResource SelectedConverter}">
        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}" Path="Tag" />
        <Binding StringFormat="This is here so it's called every time" />
    </MultiBinding>
</TextBlock.Text>

As a bonus, if you bind directly to ComboBox.Tag - whole multibinding will be refreshed when that Tag changes.

Evk
  • 98,527
  • 8
  • 141
  • 191