0

I have a composite collection which consists of:

  • combobox item with content 'Select a vendor'
  • collection container with collection of Vendor objects, bound to a combobox cmbVendor

When a vendor is selected from the combobox, the ToString() method is called. However, I want to display the value of the property Name of the selected Vendor object. enter image description here enter image description here

Setting the combobox property DisplayMemberPath='Name' works but then the 'Select a vendor' is not shown anymore on load, which is not desired.

Notes:

  • sometimes a Vendor object is chosen from code-behind
  • I can not override ToString() method of a Vendor object for other reasons

Any suggestions?

XAML

<UserControl.Resources>
    <converters:VendorConverter x:Key='VendorConverter' />
    <CollectionViewSource x:Key='VendorsCollection'
                          Source='{Binding Vendors}'>
    </CollectionViewSource>
  </UserControl.Resources>
  <Grid>
    <ComboBox Name='cmbVendor'
              SelectedItem='{Binding Vendor, Converter={StaticResource VendorConverter}, Mode=TwoWay}'
              IsSynchronizedWithCurrentItem='True'
              IsEditable='True'
              Width='{DynamicResource VendorCmbWidth}'>
      <!--Make sure "Select a vendor" is selected-->
      <i:Interaction.Behaviors>
        <behaviour:SelectFirstItemBehavior />
      </i:Interaction.Behaviors>
      <ComboBox.Resources>
        <DataTemplate DataType='{x:Type objects:Vendor}'>
          <StackPanel Orientation='Horizontal'>
            <TextBlock Text='{Binding Name}' />
          </StackPanel>
        </DataTemplate>
        <DataTemplate DataType='{x:Type system:String}'>
          <StackPanel Orientation='Horizontal'>
            <TextBlock Text='{Binding }' />
          </StackPanel>
        </DataTemplate>
      </ComboBox.Resources>
      <ComboBox.ItemsSource>
        <CompositeCollection>
          <ComboBoxItem Content='Select a vendor' />
          <CollectionContainer Collection='{Binding Source={StaticResource VendorsCollection}}' />
        </CompositeCollection>
      </ComboBox.ItemsSource>

    </ComboBox>
  </Grid>
</UserControl>

VendorConverter

internal class VendorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var vendor = value as Vendor;
            if (vendor != null)
            {
                return vendor;
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var comboboxItem = value as ComboBoxItem;
            if (comboboxItem != null)
            {
                return null;
            }

            var vendor = value as Vendor;
            if (vendor != null)
            {
                return vendor;
            }

            return null;
        }
    }

Behaviors

internal class SelectFirstItemBehavior : Behavior<ComboBox>
    {
        protected override void OnAttached()
        {
            AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
            base.OnDetaching();
        }

        private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var combobox = sender as ComboBox;
            if (combobox != null && combobox.SelectedIndex == -1)
            {
                combobox.SelectedIndex = 0;
            }
        }
    }
BertAR
  • 425
  • 3
  • 18
  • Can you set your default value as the object and make it the initial selected index? – Josh Adams Jan 11 '18 at 13:21
  • @JoshAdams sometimes a Vendor object from the collection is chosen from code behind. So I can not set a default. – BertAR Jan 11 '18 at 13:27
  • ahh @bertAR sorry my solution was not viable :( – Josh Adams Jan 11 '18 at 13:28
  • override the ToString() to return this.Name? or did i not understand something? is your goal the same as in the images of this question: https://stackoverflow.com/questions/47812405/wpf-combobox-loses-text-after-selection ? – Milan Jan 11 '18 at 13:58
  • @Milan Overriding the ToString() method is a possible solution. However, is this not possible in XAML only? – BertAR Jan 11 '18 at 14:28
  • in xaml, you dont have access to the selected item data template of the combobox, so you cant do this only in xaml, but you can edit the template using some code-behind if you really have to: https://stackoverflow.com/questions/4672867/can-i-use-a-different-template-for-the-selected-item-in-a-wpf-combobox-than-for – Milan Jan 11 '18 at 15:00

1 Answers1

1

Approach 1

You should be able to combine TextSearch.TextPath="Name" for your normal items and TextSearch.Text="Select a vendor" directly assigned to your special item:

<ComboBox
    IsEditable="True"
    TextSearch.TextPath="Name">
...

and

<CompositeCollection>
    <ComboBoxItem Content='Select a vendor' TextSearch.Text="Select a vendor" />
    <CollectionContainer Collection='{Binding Source={StaticResource VendorsCollection}}' />
</CompositeCollection>

Approach 2

Just display a visual hint text when nothing is selected:

<ComboBox
    ItemsSource="{Binding Source={StaticResource VendorsCollection}}"
    IsEditable="True">
    <ComboBox.Style>
        <Style TargetType="ComboBox">
            <Style.Resources>
                <VisualBrush x:Key="hintText" x:Shared="False"  AlignmentX="Left" Stretch="None">
                    <VisualBrush.Visual>
                        <Grid Background="White">
                            <TextBlock Margin="4 3" Text="Select a vendor"/>
                        </Grid>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <Trigger Property="Text" Value="">
                    <Setter Property="Background" Value="{StaticResource hintText}"/>
                </Trigger>
                <Trigger Property="Text" Value="{x:Null}">
                    <Setter Property="Background" Value="{StaticResource hintText}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ComboBox.Style>
</ComboBox>

This way, you can keep your items collection clean of extra entries and use TextSearch.TextPath and similar properties for your actual items.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • Try out `` for a better understanding, which property is showing where ;) – grek40 Jan 11 '18 at 15:22
  • Thank you for your answer. The first approach works! Unfortunately the alternative, 2nd approach did not work for me. The visual hint text is not shown. – BertAR Jan 12 '18 at 08:03
  • @BertAR maybe your text is `null` instead of empty (would need a different trigger) or you have some initial selection... but as long as the first approach does the trick, this should be enough. – grek40 Jan 12 '18 at 08:06
  • Indeed, my converter returns `null` for a `ComboBoxItem` (see question) i.e. for the case 'Select a vendor'. What modifications are needed for the trigger? It would be nice if I could use the 2nd approach as well. – BertAR Jan 12 '18 at 08:11
  • @BertAR edited with two triggers to support null and empty texts (I'm not really sure if the `x:Shared="False"` is really needed, I just want to avoid possible problems) – grek40 Jan 12 '18 at 08:22