Как управлять позицией прокрутки ListBox в приложении MVV MVVM
У меня есть большой ListBox с включенной вертикальной прокруткой, у моего MVVM есть New и Edit ICommands.
Я добавляю новый элемент в конец коллекции, но я хочу, чтобы полоса прокрутки также автоматически позиционировалась до конца, когда я вызываю свой MVVM-AddCommand.
Я также делаю элемент редактируемым (вызывая EditCommand с определенным элементом строки) из какой-либо другой части приложения, чтобы мой ListBoxItem перешел в режим редактирования с использованием DataTrigger, но как я приведу эту конкретную строку (ListBoxItem) в представление путем регулировки положения прокрутки.
Если я делаю это в стороне просмотра, я могу вызвать listBox.ScrollInToView(lstBoxItem).
Но каков наилучший способ решить эту общую проблему с прокруткой с точки зрения MVVM.
Ответы
Ответ 1
Я обычно устанавливаю IsSynchronizedWithCurrentItem="True"
на ListBox
. Затем я добавляю обработчик SelectionChanged
и всегда выношу выбранный элемент с таким кодом:
private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e)
{
Selector selector = sender as Selector;
if (selector is ListBox)
{
(selector as ListBox).ScrollIntoView(selector.SelectedItem);
}
}
Из моей виртуальной машины я могу получить представление коллекции по умолчанию и использовать один из методов MoveCurrent*()
, чтобы убедиться, что редактируемый элемент является текущим.
CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);
ПРИМЕЧАНИЕ. Отредактировано использование ListBox.ScrollIntoView()
для размещения виртуализации
Ответ 2
Использование этого в MVVM можно легко выполнить с помощью такого поведения:
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Jarloo.Sojurn.Behaviors
{
public sealed class ScrollIntoViewBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += ScrollIntoView;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= ScrollIntoView;
base.OnDetaching();
}
private void ScrollIntoView(object o, SelectionChangedEventArgs e)
{
ListBox b = (ListBox) o;
if (b == null)
return;
if (b.SelectedItem == null)
return;
ListBoxItem item = (ListBoxItem) ((ListBox) o).ItemContainerGenerator.ContainerFromItem(((ListBox) o).SelectedItem);
if (item != null) item.BringIntoView();
}
}
}
Затем в представлении "Просмотреть объявление" эта ссылка вверху:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
И сделайте следующее:
<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}">
<i:Interaction.Behaviors>
<behaviors:ScrollIntoViewBehavior />
</i:Interaction.Behaviors>
</ListBox>
Теперь, когда SelectedItem изменит поведение, вы вызовете вызов BringIntoView().
Ответ 3
Это форма прикрепленного свойства принятого ответа:
using System.Windows;
using System.Windows.Controls;
namespace CommonBehaviors
{
public static class ScrollCurrentItemIntoViewBehavior
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
typeof(bool), typeof(ScrollCurrentItemIntoViewBehavior),
new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToCurrentItemProperty);
}
public static void OnAutoScrollToCurrentItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = obj as ListBox;
if (listBox == null) return;
var newValue = (bool)e.NewValue;
if (newValue)
listBox.SelectionChanged += listBoxSelectionChanged;
else
listBox.SelectionChanged -= listBoxSelectionChanged;
}
static void listBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox == null || listBox.SelectedItem == null || listBox.Items == null) return;
listBox.Items.MoveCurrentTo(listBox.SelectedItem);
listBox.ScrollIntoView(listBox.SelectedItem);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToCurrentItemProperty, value);
}
}
}
Использование:
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">
Ответ 4
Если приведенный выше код не работает для вас, попробуйте
public class ListBoxExtenders : DependencyObject
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToSelectedItemProperty, value);
}
public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var listBox = s as ListBox;
if (listBox != null)
{
var listBoxItems = listBox.Items;
if (listBoxItems != null)
{
var newValue = (bool)e.NewValue;
var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));
if (newValue)
listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
else
listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
}
}
}
public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
{
if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
listBox.ScrollIntoView(listBox.Items[index]);
}
}
Использование в XAML
<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>