Sandy Gifford
Sandy Gifford

Reputation: 8146

Binding an ObservableCollection to a ComboBox

I'm trying to bind an ObservableCollection (C#) to a ComboBox (XAML). A million articles, question/answers, and posts on the internet suggest that this is a totally simple task. So far, I have to disagree with them:

XAML

<Window
    x:Class     = "ComboTest.MainWindow"
    xmlns       = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x     = "http://schemas.microsoft.com/winfx/2006/xaml"
    Title       = "MainWindow"
    Height      = "350"
    Width       = "525"
    DataContext = "{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel>
        <ComboBox 
            Name          = "URICombo"
            ItemsSource   = "{Binding URIs}" 
            SelectedIndex = "0">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"></TextBlock>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</Window>

C#

namespace ComboTest
{
    public partial class MainWindow : Window
    {
        public class URIPairing
        {
            public string URI  { get; set; }
            public string Name { get; set; }

            public URIPairing(string _Name, string _URI)
            {
                this.Name = _Name;
                this.URI  = _URI;
            }
        }

        public ObservableCollection<URIPairing> URIs { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            this.URIs = new System.Collections.ObjectModel.ObservableCollection<URIPairing>();
            this.URIs.Add(new URIPairing( "DEV"     , "Some URL" ));
            this.URIs.Add(new URIPairing( "SANDBOX" , "Some URL" ));
            this.URIs.Add(new URIPairing( "QA"      , "Some URI" ));
        }
    }
}

When run, the application shows a simple, empty ComboBox.

Debugging shows that the ObservableCollection is populating correctly, and looking at Visual Studio's "Apply Data Binding..." panel, I can see that the DataContext is null and that no paths are available to bind to.

Apply Data Binding dialog box

I sincerely hope that I'm not making some silly typo (I've copy/pasted every like-namespace I can find with no luck); I'm at an absolute loss, otherwise, though.

Upvotes: 0

Views: 2027

Answers (3)

Rohit Vats
Rohit Vats

Reputation: 81323

You forgot to set DataContext to itself:

public MainWindow()
{
   InitializeComponent();
   DataContext = this;
}

OR

May be in XAML:

<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"/>

Of course your combobox declaration should be this:

<ComboBox ItemsSource="{Binding URIs}"/>

UPDATE (For anyone who stumbled upon this post in future)

Generally bindings are done with ViewModels, so I don't feel any UI component should implement INPC. But, that's completely opinion based.

Anyhow if you set DataContext from XAML, it wasn't working because XAML gets parsed with line InitializeComponent() but at that time list wasn't initialized so binding fails silently (you can check that in Output window of VS2010, you will see binding failure message over there).

That being said, if you want it to work from XAML as well (without implementing INPC), all you have to do is initialize list before InitializeComponent() and it will work from both sides.

    public MainWindow()
    {
        URIs = new ObservableCollection<URIPairing>();
        InitializeComponent();

        this.URIs.Add(new URIPairing("DEV", "Some URL"));
        this.URIs.Add(new URIPairing("SANDBOX", "Some URL"));
        this.URIs.Add(new URIPairing("QA", "Some URI"));
    }

Upvotes: 3

123 456 789 0
123 456 789 0

Reputation: 10865

The reason that didn't work is because you don't have INotifyPropertyChanged implemented in your MainWindow. When you did a binding to the DataContext to RelativeSource Self on the Window, initially when you run the application it tried to grab the URIs property which is not yet set and then you reset the DataContext in the constructor to this which would mark it as new one and it is already late, thus you need to call INPC.

An easy and cleaner fix is to remove either the setting of the DataContext in the constructor or the binding in the XAML but not both.

public partial class MainWindow : Window, INotifyPropertyChanged
{
    // Implementation of the INPC
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public class URIPairing
    {
        public string URI  { get; set; }
        public string Name { get; set; }

        public URIPairing(string _Name, string _URI)
        {
            this.Name = _Name;
            this.URI  = _URI;
        }
    }
    private ObservableCollection<URIPairing> _uris;
    public ObservableCollection<URIPairing> URIs { get { return _uris; } set { _uris = value; OnPropertyChanged(); } }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;

        this.URIs = new System.Collections.ObjectModel.ObservableCollection<URIPairing>();
        this.URIs.Add(new URIPairing( "DEV"     , "Some URL" ));
        this.URIs.Add(new URIPairing( "SANDBOX" , "Some URL" ));
        this.URIs.Add(new URIPairing( "QA"      , "Some URI" ));
    }
}

This will propagate the new value to the User Interface.

Upvotes: 1

Sajeetharan
Sajeetharan

Reputation: 222720

You have missed the main part assigning to the combobox.

        this.URIs = new stem.Collections.ObjectModel.ObservableCollection<URIPairing>();
         this.URIs.Add(new URIPairing("DEV", "Some URL"));
        this.URIs.Add(new URIPairing("SANDBOX", "Some URL"));
        this.URIs.Add(new URIPairing("QA", "Some URI"));
        URICombo.ItemsSource = URIs;
        URICombo.DisplayMemberPath = "Name";

Upvotes: 1

Related Questions