2
votes

I'm trying to implement the MVVM pattern in WPF. I've followed Jeremy Alles's Very simple MVVM demo application. I have a ListBox that has a binding to an ObservableCollection:

<ListBox
    Name="myListBox"
    IsSynchronizedWithCurrentItem="True"
    ItemsSource="{Binding Persons}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <views:PersonsView />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

I added an ICollectionView to manage the selected item on the ListBox. It also allows me to have two buttons that allow me to select the previous and next items in the list.

private void GoToPrevious()
{
    this.collectionView.MoveCurrentToPrevious();
}
private void GoToNext()
{
    this.collectionView.MoveCurrentToNext();
}

It all works great, however, when the selected item is below the displayed area of the listbox, the listbox's scrollbar doesn't move accordingly.

How can I synchronize the scrollbar/display area of the ListBox with the selected item?

1

1 Answers

6
votes

I've found the answer. I needed to use

myListBoxItem.BringIntoView();

The problem was that I didn't wanted to add any code-behind, as I am implementing MVVM.

The solution is using Attached Behaviors. Josh Smith has a great article about this: Introduction to Attached Behaviors in WPF.

I adedd a Setter to the style of the items in the ListBox:

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter
            Property="custom:ListBoxItemBehavior.IsBroughtIntoViewWhenSelected"
            Value="True" />
    </Style>
</ListBox.ItemContainerStyle>

And added the following class (only changed TreeView from Josh's article to ListBox):

public static class ListBoxItemBehavior
{
    #region IsBroughtIntoViewWhenSelected

    public static bool GetIsBroughtIntoViewWhenSelected(ListBoxItem listBoxItem)
    {
        return (bool)listBoxItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
    }

    public static void SetIsBroughtIntoViewWhenSelected(
      ListBoxItem listBoxItem, bool value)
    {
        listBoxItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
    }

    public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
        DependencyProperty.RegisterAttached(
        "IsBroughtIntoViewWhenSelected",
        typeof(bool),
        typeof(ListBoxItemBehavior),
        new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));

    static void OnIsBroughtIntoViewWhenSelectedChanged(
      DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        ListBoxItem item = depObj as ListBoxItem;
        if (item == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            item.Selected += OnListBoxItemSelected;
        else
            item.Selected -= OnListBoxItemSelected;
    }

    static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
    {
        // Only react to the Selected event raised by the ListBoxItem
        // whose IsSelected property was modified.  Ignore all ancestors
        // who are merely reporting that a descendant's Selected fired.
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        ListBoxItem item = e.OriginalSource as ListBoxItem;
        if (item != null)
            item.BringIntoView();
    }

    #endregion // IsBroughtIntoViewWhenSelected
}

It works!!