Свойство неправильно связано в ListBox DataTemplate

У меня возникли проблемы с правильной привязкой списка к коллекции.

Я дам код фреймворка, а затем объясню, что я хочу, чтобы он делал.

Разметка XAML:

<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#:

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#:

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

Моя проблема в том, что какие-либо изменения в текстовых полях для строк в коллекции Bar не установлены. Когда выбранный Foo изменяется на другой Foo и обратно, отображаются исходные Bars.

Может ли кто-нибудь сказать мне, что я делаю неправильно? Кажется, это должно быть намного проще. Спасибо!

Обновление: я изменил код в соответствии с предложением Tri Q, но изменения, внесенные в текстовое поле, не отражаются в самом свойстве. Есть идеи?


person llaughlin    schedule 04.10.2010    source источник


Ответы (1)


Ваш класс модели Foo, который я беру, был упрощен для этого примера, но опущенный код может быть виновником вашей проблемы. Позволь мне объяснить.

Foo также необходимо реализовать INotifyPropertyChanged, чтобы сообщить Listbox, когда вы инициализировали коллекцию Bars, и это определенно зависит от того, когда вы ее инициализируете.

Скажем, вы инициализируете Bars в конструкторе Foo, что приведет к привязке Listbox ItemsSource к действительной коллекции Bars.

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

Но если вы сделаете что-то подобное, Listbox не будет знать, что коллекция Bars была инициализирована, и не будет обновлять ее источник...

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>();
            // ...
        }
    }
}

Также вот несколько вещей, которые вы, возможно, захотите пересмотреть в своем XAML.

Во-первых, привязка Textbox по умолчанию равна TwoWay, поэтому вам не нужно устанавливать Mode или Path.

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

Во-вторых, нет смысла ставить Mode="TwoWay" вместо ItemsSource. ItemsSource="{Привязка Main.SelectedFoo.Bars, Mode=TwoWay}"

Наконец, вам не нужно устанавливать DataType для вашего DataTemplate. DataType="{x:Система типов:String}"

person Tri Q Tran    schedule 05.10.2010
comment
Потрясающий ответ! Большое спасибо за дополнительную информацию. Да, как вы можете ясно видеть, я все еще изучаю WPF и MVVM, и я искренне благодарен за комментарии. Я попробую ваше предложение. - person llaughlin; 05.10.2010
comment
Следует отметить одну вещь: так как у меня есть привязка TextBox, требуется путь, потому что режим TwoWay (даже по умолчанию), и я отметил это в своих комментариях XAML. Кажется, это единственный способ связать TextBox с текущим элементом в коллекции. - person llaughlin; 05.10.2010