Как реализовать INotifyPropertyChanged на индексированный дочерний элемент в SL4? - PullRequest
1 голос
/ 20 сентября 2011

Мой класс ViewModel имеет дочернее свойство типа 'Messages', которое имеет индексированное свойство, например:

public class ViewModel
{
    // ...
    public Messages Messages
    {
        get
        {
            if (_messages == null)
            {
                LoadMessagesAsync();
                _messages = new Messages();
            }
            return _messages;
        }
        set
        {
            _messages = values;
            PropertyChanged(new PropertyChangedArgs("Messages");
        }
    }
    // ...
    private void LoadMessagesAsync()
    {
        // Do the service call
        Messages = theResult;
    }
}

public class Messages
{
    // ...
    public String this[String name]
    {
        get { return _innerDictionary[name]; }
    }
    // ...
}

Не думаю, что мне нужно заполнять оставшиеся пробелы, так как все просто.

Проблема, с которой я столкнулся, заключается в том, что привязка не обновляется, когда я устанавливаю свойство Messages для нового объекта. Вот как я ссылаюсь на свойство в XAML (с ViewModel в качестве DataContext):

<TextBlock Text="{Binding Messages[HelloWorld]}" />

Я предполагал, что привязка обновится, когда будет вызвано событие PropertyChanged для свойства «Сообщения».

В другом месте я читал, что мой класс Messages должен вызывать событие PropertyChanged с пустой строкой (""), "Item []" или "Item [" + name + "]" для имени свойства. Однако, так как я полностью заменяю объект Messages, это не сработает, так как я никогда не изменяю содержимое.

Как мне сделать эту работу?

UPDATE

Итак, я немного покопался в поведении и в исходном коде BCL, чтобы увидеть, что ожидается, как способ выяснить, как заставить мой код работать. То, что я узнал, состоит из двух частей:

Во-первых, привязка данных Silverlight фактически рассматривает возвращаемый объект из свойства Messages в качестве источника привязки. Таким образом, поднятие PropertyChanged из ViewModel (отправитель ViewModel) не обрабатывается привязкой. На самом деле мне нужно вызвать событие из класса Messages.

Это ничем не отличается от использования следующего: Text = {Binding Messages.HelloWorld} "

Причина, по которой работает код Майлза, заключается в том, что «Данные» возвращают «это», поэтому привязка обманывается, рассматривая родительский класс как источник привязки.

Тем не менее, даже если я сделаю так, чтобы мой дочерний объект вызвал событие, он все равно не будет работать. Это связано с тем, что привязка использует System.Windows.IndexerListener в качестве цели привязки. В методе SourcePropertyChanged слушатель проверяет, является ли имя свойства "Item []", но не предпринимает никаких действий. Следующий оператор делегирует PropertyListener, который проверяет имя свойства и обрабатывает событие, только если оно равно "Item [HelloWorld]".

Итак, если я не вызову явное событие для каждого возможного значения в моей коллекции, пользовательский интерфейс никогда не будет обновляться. Это разочаровывает, потому что другие статьи и сообщения указывают, что «Item []» должен работать, но если посмотреть на источник, это доказывает обратное.

Тем не менее, я все еще надеюсь, что есть способ достичь моих целей.

Ответы [ 2 ]

1 голос
/ 21 сентября 2011

ОК, основная проблема здесь заключается в том, что для привязки не указан путь, поэтому структура привязки не знает, какое имя свойства нужно искать при обработке событий PropertyChanged. Поэтому я изготовил Путь для привязки, чтобы уведомление об изменениях работало.

Я написал следующий код, который подтверждает, что привязка индексатора обновляется при изменении фактического базового словаря:

ViewModel

public class BindingTestViewModel : AppViewModelBase, IBindingTestViewModel

    {
        private Dictionary<string, object> _data = new Dictionary<string, object>();

        public BindingTestViewModel()
        {
            _data.Add("test","1");
            _data.Add("test2", "21");
        }

        public object this[string index]
        {
            get
            {
                return _data[index];
            }
            set
            {
                _data[index] = value;
                NotifyOfPropertyChange(() => Data);

            }
        }

        public object Data
        {
            get
            {
                return this;
            }
        }

        public void Refresh()
        {
            _data = new Dictionary<string, object>
                {
                    {"test", "2"}, {"test2", "22"}
                };
            NotifyOfPropertyChange(() => Data);
        }

    }

Мой XAML:

<navigation:Page.Resources>
    <Converters:IndexConverter x:Name="IndexConverter"></Converters:IndexConverter>
</navigation:Page.Resources>

<Grid x:Name="LayoutRoot">
    <TextBox Height="23"
             HorizontalAlignment="Left"
             Margin="288,206,0,0"
             Name="textBox1"
             Text="{Binding Path=Data,Converter={StaticResource IndexConverter},ConverterParameter=test}"
             VerticalAlignment="Top" Width="120" />
    <Button x:Name="ReloadDict" Click="ReloadDict_Click" Width="50" Height="30" Content="Refresh" VerticalAlignment="Top"></Button>
</Grid>

Конвертер:

public class IndexConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
           object parameter, CultureInfo culture)
    {
        var vm = value as BindingTestViewModel;
        var index = parameter as string;
        return vm[index];
    } 


    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
0 голосов
/ 23 сентября 2011

Мне не обязательно отвечать на свои вопросы, но я нашел решение своей проблемы.Я смог сохранить исходный поток и устранить особенности привязки данных SL4.И код кажется немного чище.

То, что сводится к тому, что я больше не заменяю дочерний объект.Кажется, это ключ.Вместо этого я создаю один экземпляр и позволяю ему управлять изменением внутреннего списка элементов по мере необходимости.Дочерний объект уведомляет родителя, когда он изменился, чтобы родитель мог вызвать событие PropertyChanged.Ниже приведен краткий пример того, как я заставил его работать:

public class ViewModel
{
    // ...
    public Messages Messages
    {
        get
        {
            if (_messages == null)
            {
                lock (_messagesLock)
                {
                    if (_messages == null)
                    {
                        _messages = new Messages();
                        _messages.ListChanged += (s, e) =>
                        {
                            NotifyPropertyChanged("Messages");
                        };
                    }
                }
            }

            return _messages;
        }
    }
}

public class Messages
{
    // ...

    public String this[String name]
    {
        get
        {
            if (_innerDictionary == null)
            {
                _innerDictionary = new Dictionary<String, String>();
                LoadMessagesAsync();
            }
            return _innerDictionary[name];
        }
    }

    // ...

    private void LoadMessagesAsync()
    {
        // Do the service call
        _innerDictionary = theResult;
        NotifyListChanged();
    }

    // ...

    public event EventHandler ListChanged;
}

Для краткости я упустил очевидные части.

...