Прокрутите список WPF ListBox к SelectedItem, установленному в коде в модели представления - PullRequest
35 голосов
/ 12 января 2012

У меня есть представление XAML со списком:

<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}"
                       SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
                       ScrollSelectedItem="{Binding SelectedFoo}">
    <!-- data templates, etc. -->
</control:ListBoxScroll>

Выбранный элемент привязан к свойству на мой взгляд. Когда пользователь выбирает элемент в списке, мое свойство SelectedFoo в модели представления обновляется. Когда я устанавливаю свойство SelectedFoo в моей модели представления, в поле списка выбирается правильный элемент.

Проблема в том, что если SelectedFoo, установленный в коде, в данный момент не виден, мне нужно дополнительно вызвать ScrollIntoView в списке. Поскольку мой ListBox находится внутри представления, а моя логика находится внутри модели представления ... Я не смог найти удобный способ сделать это. Поэтому я расширил ListBoxScroll:

class ListBoxScroll : ListBox
{
    public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
        "ScrollSelectedItem",
        typeof(object),
        typeof(ListBoxScroll),
        new FrameworkPropertyMetadata(
            null,
            FrameworkPropertyMetadataOptions.AffectsRender, 
            new PropertyChangedCallback(onScrollSelectedChanged)));
    public object ScrollSelectedItem
    {
        get { return (object)GetValue(ScrollSelectedItemProperty); }
        set { SetValue(ScrollSelectedItemProperty, value); }
    }

    private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var listbox = d as ListBoxScroll;
        listbox.ScrollIntoView(e.NewValue);
    }
}

Он в основном предоставляет новое свойство зависимости ScrollSelectedItem, которое я связываю со свойством SelectedFoo в моей модели представления. Затем я подключаюсь к измененному обратному вызову свойства зависимого свойства и прокручиваю вновь выбранный элемент в поле зрения.

Кто-нибудь еще знает о более простом способе вызова функций для пользовательских элементов управления в представлении XAML, которое поддерживается моделью представления? Это что-то вроде беготни:

  1. создать зависимое свойство
  2. добавить обратный вызов в свойство измененного обратного вызова
  3. обработка вызова функции внутри статического обратного вызова

Было бы неплохо исправить логику в методе ScrollSelectedItem { set {, но структура зависимостей, похоже, подкралась и умудряется работать, фактически не вызывая ее.

Ответы [ 8 ]

49 голосов
/ 12 января 2012

Вы пытались использовать Поведение ... Вот ScrollInViewBehavior. Я использовал его для ListView и DataGrid ..... Я думаю, что он должен работать для ListBox ......

Вы должны добавить ссылку на System.Windows.Interactivity, чтобы использовать Behavior<T> class

Поведение

public class ScrollIntoViewForListBox : Behavior<ListBox>
{
    /// <summary>
    ///  When Beahvior is attached
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
    }

    /// <summary>
    /// On Selection Changed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void AssociatedObject_SelectionChanged(object sender,
                                           SelectionChangedEventArgs e)
    {
        if (sender is ListBox)
        {
            ListBox listBox = (sender as ListBox);
            if (listBox .SelectedItem != null)
            {
                listBox.Dispatcher.BeginInvoke(
                    (Action) (() =>
                                  {
                                      listBox.UpdateLayout();
                                      if (listBox.SelectedItem !=
                                          null)
                                          listBox.ScrollIntoView(
                                              listBox.SelectedItem);
                                  }));
            }
        }
    }
    /// <summary>
    /// When behavior is detached
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.SelectionChanged -=
            AssociatedObject_SelectionChanged;

    }
}

Использование

Добавить псевдоним XAML как xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

тогда в вашем Control

        <ListBox ItemsSource="{Binding Path=MyList}"
                  SelectedItem="{Binding Path=MyItem,
                                         Mode=TwoWay}"
                  SelectionMode="Single">
            <i:Interaction.Behaviors>
                <Behaviors:ScrollIntoViewForListBox />
            </i:Interaction.Behaviors>
        </ListBox>

Теперь Когда свойство "MyItem" установлено в ViewModel, список будет прокручиваться при повторном выборе изменений.

34 голосов
/ 12 января 2012

После просмотра ответов возникла общая тема: внешние классы прослушивают событие SelectionChanged в ListBox.Это заставило меня понять, что подход с зависимым свойством был излишним, и я мог просто заставить подкласс слушать себя:

class ListBoxScroll : ListBox
{
    public ListBoxScroll() : base()
    {
        SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged);
    }

    void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ScrollIntoView(SelectedItem);
    }
}

Я чувствую, что это самое простое решение, которое делает то, что я хочу.

Похвальное упоминание идет на adcool2007 за воспитание поведения.Вот пара статей для тех, кто заинтересован:

http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

Я думаю, что для общих поведений, которые будут добавлены к нескольким различным пользовательским элементам управления (например, поведение при нажатии,поведение перетаскивания, поведение анимации и т. д.), тогда присоединенное поведение имеет большой смысл.Причина, по которой я не хочу использовать их в данном конкретном случае, заключается в том, что реализация поведения (вызов ScrollIntoView) не является общим действием, которое может произойти с любым элементом управления, кроме ListBox.

16 голосов
/ 12 января 2012

Поскольку это проблема строго View, нет причины, по которой вы не можете иметь обработчик событий в коде вашего представления для этой цели.Прослушайте ListBox.SelectionChanged и используйте его для прокрутки вновь выбранного элемента в поле зрения.

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ((ListBox)sender).ScrollIntoView(e.AddedItems[0]);
}

Вам также не нужен производный ListBox для этого.Просто используйте стандартный элемент управления, и когда значение ListBox.SelectedItem изменится (как описано в исходном вопросе), вышеописанный обработчик будет выполнен, и элемент будет прокручен в поле зрения.написать прикрепленное свойство, которое прослушивает ICollectionView.CurrentChanged, а затем вызывает ListBox.ScrollIntoView для нового текущего элемента.Это более «многоразовый» подход, если вам нужна эта функциональность для нескольких списков.Вы можете найти хороший пример, чтобы начать: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/

11 голосов
/ 05 апреля 2013

Я знаю, что это старый вопрос, но мой недавний поиск той же проблемы привел меня к этому.Я хотел использовать поведенческий подход, но не хотел, чтобы зависимость от Blend SDK просто дала мне Behavior<T>, поэтому вот мое решение без него:

public static class ListBoxBehavior
{
    public static bool GetScrollSelectedIntoView(ListBox listBox)
    {
        return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty);
    }

    public static void SetScrollSelectedIntoView(ListBox listBox, bool value)
    {
        listBox.SetValue(ScrollSelectedIntoViewProperty, value);
    }

    public static readonly DependencyProperty ScrollSelectedIntoViewProperty =
        DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior),
                                            new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged));

    private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var selector = d as Selector;
        if (selector == null) return;

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

        if ((bool) e.NewValue)
        {
            selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
        }
        else
        {
            selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
        }
    }

    private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e)
    {
        if (!(sender is ListBox)) return;

        var listBox = (sender as ListBox);
        if (listBox.SelectedItem != null)
        {
            listBox.Dispatcher.BeginInvoke(
                (Action)(() =>
                    {
                        listBox.UpdateLayout();
                        if (listBox.SelectedItem !=null)
                            listBox.ScrollIntoView(listBox.SelectedItem);
                    }));
        }
    }
}

, а затем использование просто

<ListBox ItemsSource="{Binding Path=MyList}"
         SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
         SelectionMode="Single" 
         behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
7 голосов
/ 08 апреля 2014

Попробуйте это:

private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    lstBox.ScrollIntoView(lstBox.SelectedItem);
}
6 голосов
/ 22 сентября 2016

Я использую это (на мой взгляд) простое и понятное решение

listView.SelectionChanged += (s, e) => 
    listView.ScrollIntoView(listView.SelectedItem);

, где listView - имя элемента управления ListView в xaml, SelectedItem зависит от моего MVVM и кода вставлено в конструктор в файле xaml.cs.

3 голосов
/ 22 марта 2016

После связывания различных методов я обнаружил, что следующее является самым простым и лучшим

lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
1 голос
/ 06 апреля 2016

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

Когда ваше приложение инициализируется ...

    internal static void RegisterFrameworkExtensionEvents()
    {
        EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem));
    }

    //avoid "async void" unless used in event handlers (or logical equivalent)
    private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e)
    {
        if (sender is ListBox)
        {
            var lb = sender as ListBox;
            if (lb.SelectedItem != null)
            {
                await lb.Dispatcher.BeginInvoke((Action)delegate
                {
                    lb.UpdateLayout();
                    if (lb.SelectedItem != null)
                        lb.ScrollIntoView(lb.SelectedItem);
                });
            }
        }
    }

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...