views:

62

answers:

1

I've been having some trouble getting a listbox to correctly bind to a collection.

I'll give the framework code, then explain what I want it to do.

XAML Markup:

<ListBox DataContext="{Binding Foos, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                       ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" 
                       SelectedItem="{Binding Main.SelectedFoo, Mode=TwoWay, 
                       Source={StaticResource Locator}, 
                       UpdateSourceTrigger=PropertyChanged}" 
                       SelectedValue="{Binding Main.SelectedFoo, Source={StaticResource Locator}}"/>


<ListBox ItemsSource="{Binding Main.SelectedFoo.Bars}"  SelectedItem="{Binding Main.SelectedBar}"  >
<ListBox.ItemTemplate>
    <DataTemplate>
        <Grid HorizontalAlignment="Right">
            <!-- The binding requires "{Binding .}" because a path must be explicitly set for Two-Way binding,
                 even though {Binding .} is supposed to be identical to {Binding} -->
                <TextBox Text="{Binding Path=. , UpdateSourceTrigger=PropertyChanged}"  />
         </Grid>
    </DataTemplate>
</ListBox.ItemTemplate>

C# ViewModel:

private ObservableCollection<Foo> _barList = new ObservableCollection<Foo>();
private const string BardListPN = "FooList";

public ObservableCollection<Foo> FooList
{
    get { return _fooList; }

    set
    {
        if (_fooList == value)
        {
            return;
        }

        var oldValue = _fooList;
        _fooList = value;

        RaisePropertyChanged(FooListPN);
    }
}

private Foo _selectedFoo;
private const string SelectedFooPN = "SelectedFoo";

public Foo SelectedFoo
{
    get { return _selectedFoo; }

    set
    {
        if (_selectedFoo == value)
        {
            return;
        }

        var oldValue = _selectedFoo;
        _selectedFoo = value;

        // Update bindings, no broadcast
        RaisePropertyChanged(SelectedFooPN);
    }
}

public const string SelectedBarPN = "SelectedBar";
private string _selectedBar = "";

public string SelectedBar
{
    get
    {
        return _selectedBar;
    }

    set
    {
        if (_selectedBar == value)
        {
            return;
        }

        var oldValue = _selectedBar;
        _selectedBar = value;


        // Update bindings, no broadcast
        RaisePropertyChanged(SelectedBarPN);
    }
}

C# Model:

public class Foo
{
    public ICollection<string> Bars
    {
        get { return _bars; }
        set
        {
            _bars= value;
            NotifyPropertyChanged("Bars"); 
            // snipped obvious INotifyPropertyChanged boilerplate code
        }
    }
}

My problem is that any changes to the textboxes for the strings in the Bar collection aren't set. When the selected Foo changes to a different Foo and back, the original Bars are displayed.

Could someone tell me what I'm doing wrong? This seems like it should be much more simple. Thanks!

Update: I've changed the code as per Tri Q's suggestion, but the changes made to the textbox aren't reflected in the property itself. Any ideas?

+2  A: 

Your Foo model class I take has been simplified for this example, but the omitted code could be the culprit of your problem. Let me explain.

Foo also needs to implement INotifyPropertyChanged to let the Listbox know when you have initialized the Bars collection and this most definitely depends on when you are initializing it.

Say you initialize Bars in Foo's constructor will cause the Listbox ItemsSource to bind to a valid Bars collection.

public Foo()
{
    Bars = new ObservableCollection<string>();
    ...
}

Buut if you did something like this, the Listbox will not know that the Bars collection has been initialized and will not update it's source...

public Foo SelectedFoo
{
    get { return _selectedFoo; }

    set
    {
        if (_selectedFoo == value)
        {
            return;
        }

        var oldValue = _selectedFoo;
        _selectedFoo = value;

        // Update bindings, no broadcast
        RaisePropertyChanged(SelectedFooPN);

        if(_selectedFoo.Bars == null)
        {
            _selectedFoo.Bars = new ObservableCollection<string>();
            // ...
        }
    }
}

Also here are a few things you might want to revise in your XAML.

Firstly, binding of the Textbox is TwoWay by default, so you do not need to set the Mode or the Path.

<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged}"  />

Secondly, it makes no sense to set Mode="TwoWay" for ItemsSource. ItemsSource="{Binding Main.SelectedFoo.Bars, Mode=TwoWay}"

Finally, you don't need to set the DataType for your DataTemplate. DataType="{x:Type System:String}"

Tri Q
Awesome answer! Thanks so much for the extra info. Yes, as you can plainly see, I'm still learning WPF and MVVM, and I sincerely appreciate the comments. I'll try your suggestion out.
liquidrogue
One thing to note: the way I have the `TextBox` bound, a Path is required because the mode is `TwoWay` (even by default), and I noted this in my XAML comments. This seems to be the only way to have the `TextBox` bind to the current item in the collection.
liquidrogue