Search code examples
c#wpfxamlcomboboxuser-controls

WPF ComboBox with CompositeCollection in Usercontrol does not work: SelectedIndex set to -1


I am using MVVM.

I have a CompositeCollection consisting of

  • ComboboxItem with 'Select a vendor' as content
  • CollectionContainer which is bounded

When I use the ComboBox XAML code directly in my view the SelectedIndex is set to 0 (as expected).

enter image description here

However, when I put the ComboBox XAML code in a Usercontrol and use the control in my view, the SelectedIndex is set to -1. Any idea how to fix this issue, so that I can use the usercontrol?

enter image description here

All my bindings work.

Note:

  • when Combobox XAML code is directly in view: the ComboboxConverter sets the Vendor property to null when 'Select a vendor' is selected by the user.

However, when ComboBox XAML code is in a Usercontrol the code does not get in

if (comboboxItem.Content.ToString() == "Select a vendor")
                {
                    //gets here when code is in view <-> code in control
                    return null;
                }

ComboboxConverter

public class ComboboxConverter : 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)
        {
            if (comboboxItem.Content.ToString() == "Select a vendor")
            {
                //gets here when code is in view <-> code in control
                return null;
            }
            return null;
        }

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

VendorControl

<UserControl x:Class="Tool.Controls.VendorControl"
         xmlns:local="clr-namespace:Tool.Controls"
         xmlns:System="clr-namespace:System;assembly=mscorlib"
         xmlns:objects='clr-namespace:Tool.Objects'
         xmlns:converters='clr-namespace:Tool.Converters'
         mc:Ignorable="d" >
<UserControl.Resources>
<converters:ComboboxConverter x:Key='ComboboxConverter' />
</UserControl.Resources>
<Grid>
<ComboBox Name='cmbVendor'
              SelectedItem='{Binding Vendor, Converter={StaticResource ComboboxConverter}, Mode=TwoWay}'
              Grid.Column='1'
              IsSynchronizedWithCurrentItem='True'>
      <ComboBox.Resources>
    <CollectionViewSource x:Key='VendorsCollection'
                          Source='{Binding Vendors}' />            
        <DataTemplate DataType='{x:Type objects:Vendor}'>
          <StackPanel Orientation='Horizontal'>
            <TextBlock Text='{Binding Name}' />
          </StackPanel>
        </DataTemplate>
      </ComboBox.Resources>
      <ComboBox.ItemsSource>
        <CompositeCollection>
          <ComboBoxItem Content='Select a vendor' />
          <CollectionContainer Collection='{Binding Source={StaticResource VendorsCollection}}' />
        </CompositeCollection>
      </ComboBox.ItemsSource>
    </ComboBox>

ViewModel

private void OnWindowLoaded()
    {
         LoadVendors();
    }

    ObservableCollection<Vendor> _vendors = new ObservableCollection<Vendor>();
    public ObservableCollection<Vendor> Vendors
    {
        get
        {
            return _vendors;
        }

    }


    private Vendor _vendor;
    public Vendor Vendor
    {
        get
        {
            return _vendor;
        }

        set
        {
            if (value != _vendor)
            {
                _vendor = value;
                RaisePropertyChanged(nameof(Vendor));
            }
        }
    }

private void LoadVendors()
    {
            var dVendors = VendorHelper.GetVendors();

            if (Vendors.Count > 0)
            {
                DispatcherHelper.CheckBeginInvokeOnUI(() => Vendors.Clear());
            }

            dVendors.ForEach(dV =>
            {
                var vendor = new Vendor(dV);
                DispatcherHelper.CheckBeginInvokeOnUI(() => Vendors.Add(vendor));
            });
    }

Solution

  • At the beginning when your Vendor is null, function Convert will return null as well but your combobox does not know what null means, that's why it sets its selected index value to -1. You can solve this by creating a behavior which will always select index 0 when it is set to -1.

    public 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;
            }
        }
    }
    

    And then in your XAML part:

       xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    

    ...

    inside your Combobox:

    <i:Interaction.Behaviors>
        <SelectFirstItemBehavior/>
    </i:Interaction.Behaviors>