Поведение взаимодействия не вызывается с привязкой ListBox ItemSource - PullRequest
0 голосов
/ 09 февраля 2020

Это мой первый набег в WPF, и я пытался внимательно следить за MVVM, чтобы все было правильно. Контекст здесь такой, что у меня есть представление, которое должно отображать различные наборы сообщений, все из которых хранятся в ObservableCollection<T>.

Это код в моем представлении (это UserControl, который размещается в другое представление, поэтому я могу перемещаться между различными представлениями во время выполнения)

Пространство имен "i" равно xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

<ListBox ItemsSource="{Binding Messages}">
    <i:Interaction.Behaviors>
        <behaviours:ScrollOnNewItemBehaviour />
    </i:Interaction.Behaviors>
    <ListBox.ItemTemplate>
        <DataTemplate DataType="entities:DisplayedUserMessage">
            <!-- Removed for brevity -->
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Это код поведения (собранный из других вопросов SO) Я просматривал, пытаясь разобраться с этой концепцией):

public sealed class ScrollOnNewItemBehaviour : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged += OnCollectionChanged;
    }

    private void OnUnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            var border = (Border)VisualTreeHelper.GetChild(AssociatedObject, 0);
            var scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);

            // Only scroll when we're scrolled to the bottom of the listbox
            if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
            {
                scrollViewer.ScrollToBottom();
            }
        }
    }
}

Итак, вот где возникает моя конкретная проблема c - привязка работает просто отлично. Когда я изменяю _selectedChannel (я удалил нерелевантный код из представленной ниже модели представления), представление обновляется новыми сообщениями (_messages - это словарь, содержащий различные экземпляры ObservableCollection), а когда я добавляю в них новые сообщения, пользовательский интерфейс также обновления.

Проблема в том, что ни в коем случае не срабатывает поведение, которое я зарегистрировал в ListBox, что является проблемой, так как я полагаюсь на это, чтобы держать прокрутку. Мое лучшее предположение состояло в том, что, возможно, он не поддерживает связанный ItemSource, а тот факт, что ItemSource изначально имеет значение null (словарь будет заполняться асинхронно, поэтому по умолчанию он не установлен) означает, что он не был зарегистрирован должным образом / должен быть перерегистрировать каждый раз, когда привязка обновляется?

public MessagesViewModel : ViewModelBase
{
    private ObservableCollection<DisplayedUserMessage> _displayedMessages;
    private Channel _selectedChannel;

    public IList<DisplayedUserMessage> Messages
    {
        get
        {
            return _displayedMessages;
        }
        set
        {
            if (_displayedMessages == value)
            {
                return;
            }
            _displayedMessages = value;
            NotifyPropertyChanged();
        }
    }

    public Channel SelectedChannel
    {
        get
        {
            return _selectedChannel;
        }
        set
        {
            if (_selectedChannel == value)
            {
                return;
            }
            _selectedChannel = value;
            Messages = _messages[_selectedChannel.Id];
            NotifyPropertyChanged();
        }
    }
}

Поведение работает, если оно выполняется (я проверил, что оно не работает с точками останова), поэтому, если у кого-то есть идея относительно того, что я должен изменить чтобы заставить это работать с изменением ItemSources, пожалуйста, дайте мне знать!

1 Ответ

0 голосов
/ 09 февраля 2020

Вы можете подписаться на PropertyChanged событие AssociatedObject.DataContext и подождать, пока свойство Messages не изменится на допустимое значение.

Или вы инициализируете Messages пустой коллекцией, и после создания элементов добавьте их в Messages (это повысит CollectionChanged для каждого добавленного элемента).

Третье решение - подписаться на событие ItemsControl.ItemContainerGenerator.ItemsChanged. Он вызывается всякий раз, когда изменяется свойство ItemsControl.Items или коллекция.

Вы можете использовать это событие вместо того, чтобы полагаться на источник привязки ItemsSource для реализации INotifyCollectionChanged. Вам также не нужно требовать DataContext для реализации INotifyPropertyChanged.
Это сделает ваше поведение более обобщенным c и может использоваться повторно, поскольку теперь оно может работать с любым ItemsControl.
Это означает прослушивание ItemsControl.ItemContainerGenerator.ItemsChanged равно INotifyCollectionChanged.CollectionChanged:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var itemsControl = AssociatedObject as ItemsControl;
    if (itemsControl == null) return;

    itemsControl.ItemContainerGenerator.ItemsChanged += OnCollectionChanged;
}

private void OnCollectionChanged(object sender, ItemsChangedEventArgs e)
{
  if (e.Action == NotifyCollectionChangedAction.Add)
  {
    var border = (Border)VisualTreeHelper.GetChild(AssociatedObject, 0);
    var scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);

    // Only scroll when we're scrolled to the bottom of the listbox
    if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
    {
      scrollViewer.ScrollToBottom();
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...