Search code examples
c#.netwpfxamldata-binding

How to select an instance of object from ComboBox that filled by objects


The question may be dumb but I am a newbie in WPF, so I am sorry. I found a lot of similar questions here, but none of them helped.

I have a form that gets some object with a Location property as input. I want this Location property to be selected in the ComboBox when the form is shown. I also want to make this property changeable during the lifecycle of the form.

I madea binding in this way:

<ComboBox Name="CB_Location" ItemsSource="{Binding Locations}" SelectedItem="{Binding SelectedLocation}" DisplayMemberPath="LocationName" SelectedValuePath="LocationID" SelectionChanged="CB_Location_SelectionChanged"/>
public class Location
{
    public int LocationID { get; set; }
    public string LocationName { get; set; }
}
public Form(object _obj)
{
    InitializeComponent();

    lctrl = new LocationController();
    Locations = lctrl.GetAllLocations();

    SelectedLocation = lctrl.GetLocationById(_obj.LocationID);

    DataContext = this;    
}

public List<Location> Locations { get; set; }
public Location SelectedLocation; { get; set; }

The ComboBox is populated with objects correctly, but I cannot set the SelectedItem property correctly.


Solution

  • The issue why the selected item is not set is because SelectedLocation is a field, which you cannot bind. For more information about binding sources, you can refer to the documentation.

    You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.

    In order to make your current code work, just make SelectedLocation a public property.

    public Location SelectedLocation { get; set; }
    

    Apart from that, if you only want to bind the selected item, setting a SelectedValuePath is useless.

    <ComboBox Name="CB_Location"
              ItemsSource="{Binding Locations}"
              SelectedItem="{Binding SelectedLocation}"
              DisplayMemberPath="LocationName"
              SelectionChanged="CB_Location_SelectionChanged"/>
    

    If you wanted to bind the SelectedValue where the SelectedValuePath is applicable instead, you would have to expose a property that matches the type of the selected value path, here int.

    public int SelectedLocationID { get; set; }
    

    Then you can bind this the SelectedValue with value path LocationID (without SelectedItem).

    <ComboBox Name="CB_Location"
              ItemsSource="{Binding Locations}"
              DisplayMemberPath="LocationName"
              SelectedValuePath="LocationID"
              SelectedValue="{Binding SelectedLocationID}"
              SelectionChanged="CB_Location_SelectionChanged"/>
    

    Another note on updating properties. It seems that you do not implement the INotifyPropertyChanged interface. If you set Location for instance, the user interface (here the ComboBox) will not reflect the change, as it does not get notified. Therefore, if you intend to change the Location or other properties bound in your form, you have to implement INotifyPropertyChanged, e.g.:

    public class YourViewModel : INotifyPropertyChanged
    {
       private Location _selectedLocation;
       public Location SelectedLocation
       {
          get => _selectedLocation;
          set
          {
             if (_selectedLocation == value)
                return;
       
             _selectedLocation = value;
             OnPropertyChanged();
          }
       }
    
       protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
       {
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       }
    
       // ...other code.
    }