使用MVVM时,我们正在处理视图(而viewmodel仍然存在)。
我的问题是在创建新视图时如何恢复ListView
状态尽可能接近视图处理时的视图?
ScrollIntoView仅适用于部分。我只能滚动到单个项目,它可以在顶部或底部,无法控制项目在视图中的显示位置。
我有multi-selection(和水平滚动条,但这相当不重要),有人可能会选择几个项目,也许可以进一步滚动(不改变选择)。
理想情况下,ScrollViewer
ListView
属性的ListView
属于viewmodel可以做到,但我担心会直接问到XY问题(不确定this是否适用)。此外,在我看来这对于wpf来说是一个非常普遍的事情,但也许我没有正确地制定谷歌查询,因为我无法找到相关的ScrollViewer
+ MVVM
+ ScrollIntoView
组合
这可能吗?
我遇到了ListView
和数据模板(MVVM)的问题,而且工作方式相当丑陋。使用ScrollIntoView
恢复ListView
状态听起来有误。应该有另一种方式。今天谷歌引导我找到我自己未回答的问题。
我正在寻找恢复public class ViewModel
{
public class Item
{
public string Text { get; set; }
public bool IsSelected { get; set; }
public static implicit operator Item(string text) => new Item() { Text = text };
}
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>
{
"Item 1",
"Item 2",
"Item 3 long enough to use horizontal scroll",
"Item 4",
"Item 5",
new Item {Text = "Item 6", IsSelected = true }, // select something
"Item 7",
"Item 8",
"Item 9",
};
}
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
void Button_Click(object sender, RoutedEventArgs e) => DataContext = DataContext == null ? _vm : null;
}
州的解决方案。请考虑关注mcve:
<StackPanel>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<ListView Width="100" Height="100" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<Button Content="Click"
Click="Button_Click" />
</StackPanel>
XAML:
ContentControl
这是一个DataContext
的窗口,其内容绑定到null
(按钮切换为ViewModel
或IsSelected
个实例)。
我已添加ListView
支持(尝试选择一些项目,隐藏/显示ListView
将恢复该项目。)
目标是:展示100x100
,滚动(它的ListView
大小,以便内容更大)垂直和/或水平,点击按钮隐藏,点击按钮显示和此时ScrollViewer
应恢复其状态(即context.Configuration.LazyLoadingEnabled = false;
)的位置。
答案 0 :(得分:3)
我不认为你可以手动滚动滚动查看器到以前的位置 - 有或没有MVVM。 因此,您需要以某种方式存储scrollviewer的偏移量,并在加载视图时将其恢复。
您可以采用实用的MVVM方法并将其存储在viewmodel上,如下所示:WPF & MVVM: Save ScrollViewer Postion And Set When Reloading。 如果需要,它可以用附加的属性/行为进行装饰以便重复使用。
或者你可以完全忽略MVVM并将其完全保留在视图端:
编辑:根据您的代码更新了示例:
观点:
<Window x:Class="RestorableView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RestorableView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="list" ItemsSource="{Binding Items}" ScrollViewer.HorizontalScrollBarVisibility="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Content="MVVM Based" x:Name="MvvmBased" Click="MvvmBased_OnClick"/>
<Button Content="View Based" x:Name="ViewBased" Click="ViewBased_OnClick" />
</StackPanel>
</Grid>
</Grid>
</Window>
代码隐藏有两个按钮来分别说明MVVM和仅查看方法
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
private void MvvmBased_OnClick(object sender, RoutedEventArgs e)
{
var scrollViewer = list.GetChildOfType<ScrollViewer>();
if (DataContext != null)
{
_vm.VerticalOffset = scrollViewer.VerticalOffset;
_vm.HorizontalOffset = scrollViewer.HorizontalOffset;
DataContext = null;
}
else
{
scrollViewer.ScrollToVerticalOffset(_vm.VerticalOffset);
scrollViewer.ScrollToHorizontalOffset(_vm.HorizontalOffset);
DataContext = _vm;
}
}
private void ViewBased_OnClick(object sender, RoutedEventArgs e)
{
var scrollViewer = list.GetChildOfType<ScrollViewer>();
if (DataContext != null)
{
View.State[typeof(MainWindow)] = new Dictionary<string, object>()
{
{ "ScrollViewer_VerticalOffset", scrollViewer.VerticalOffset },
{ "ScrollViewer_HorizontalOffset", scrollViewer.HorizontalOffset },
// Additional fields here
};
DataContext = null;
}
else
{
var persisted = View.State[typeof(MainWindow)];
if (persisted != null)
{
scrollViewer.ScrollToVerticalOffset((double)persisted["ScrollViewer_VerticalOffset"]);
scrollViewer.ScrollToHorizontalOffset((double)persisted["ScrollViewer_HorizontalOffset"]);
// Additional fields here
}
DataContext = _vm;
}
}
}
用于以“仅查看”方法保存值的视图类
public class View
{
private readonly Dictionary<string, Dictionary<string, object>> _views = new Dictionary<string, Dictionary<string, object>>();
private static readonly View _instance = new View();
public static View State => _instance;
public Dictionary<string, object> this[string viewKey]
{
get
{
if (_views.ContainsKey(viewKey))
{
return _views[viewKey];
}
return null;
}
set
{
_views[viewKey] = value;
}
}
public Dictionary<string, object> this[Type viewType]
{
get
{
return this[viewType.FullName];
}
set
{
this[viewType.FullName] = value;
}
}
}
public static class Extensions
{
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
对于基于MVVM的方法,VM具有Horizontal / VerticalOffset属性
public class ViewModel
{
public class Item
{
public string Text { get; set; }
public bool IsSelected { get; set; }
public static implicit operator Item(string text) => new Item() { Text = text };
}
public ViewModel()
{
for (int i = 0; i < 50; i++)
{
var text = "";
for (int j = 0; j < i; j++)
{
text += "Item " + i;
}
Items.Add(new Item() { Text = text });
}
}
public double HorizontalOffset { get; set; }
public double VerticalOffset { get; set; }
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
}
因此,实际上难以访问ScrollViewer的偏移属性,这需要引入一个遍历可视树的扩展方法。在写原始答案时我没有意识到这一点。
答案 1 :(得分:0)
您可以尝试在ListView中添加SelectedValue并使用Behavior to Autoscroll。 这是代码:
对于ViewModel:
public class ViewModel
{
public ViewModel()
{
// select something
SelectedValue = Items[5];
}
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>
{
"Item 1",
"Item 2",
"Item 3 long enough to use horizontal scroll",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9"
};
// To save which item is selected
public Item SelectedValue { get; set; }
public class Item
{
public string Text { get; set; }
public bool IsSelected { get; set; }
public static implicit operator Item(string text) => new Item {Text = text};
}
}
对于XAML :
<ListView Width="100" Height="100" ItemsSource="{Binding Items}" SelectedValue="{Binding SelectedValue}" local:ListBoxAutoscrollBehavior.Autoscroll="True">
行为:
public static class ListBoxAutoscrollBehavior
{
public static readonly DependencyProperty AutoscrollProperty = DependencyProperty.RegisterAttached(
"Autoscroll", typeof (bool), typeof (ListBoxAutoscrollBehavior),
new PropertyMetadata(default(bool), AutoscrollChangedCallback));
private static readonly Dictionary<ListBox, SelectionChangedEventHandler> handlersDict =
new Dictionary<ListBox, SelectionChangedEventHandler>();
private static void AutoscrollChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var listBox = dependencyObject as ListBox;
if (listBox == null)
{
throw new InvalidOperationException("Dependency object is not ListBox.");
}
if ((bool) args.NewValue)
{
Subscribe(listBox);
listBox.Unloaded += ListBoxOnUnloaded;
listBox.Loaded += ListBoxOnLoaded;
}
else
{
Unsubscribe(listBox);
listBox.Unloaded -= ListBoxOnUnloaded;
listBox.Loaded -= ListBoxOnLoaded;
}
}
private static void Subscribe(ListBox listBox)
{
if (handlersDict.ContainsKey(listBox))
{
return;
}
var handler = new SelectionChangedEventHandler((sender, eventArgs) => ScrollToSelect(listBox));
handlersDict.Add(listBox, handler);
listBox.SelectionChanged += handler;
ScrollToSelect(listBox);
}
private static void Unsubscribe(ListBox listBox)
{
SelectionChangedEventHandler handler;
handlersDict.TryGetValue(listBox, out handler);
if (handler == null)
{
return;
}
listBox.SelectionChanged -= handler;
handlersDict.Remove(listBox);
}
private static void ListBoxOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var listBox = (ListBox) sender;
if (GetAutoscroll(listBox))
{
Subscribe(listBox);
}
}
private static void ListBoxOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
var listBox = (ListBox) sender;
if (GetAutoscroll(listBox))
{
Unsubscribe(listBox);
}
}
private static void ScrollToSelect(ListBox datagrid)
{
if (datagrid.Items.Count == 0)
{
return;
}
if (datagrid.SelectedItem == null)
{
return;
}
datagrid.ScrollIntoView(datagrid.SelectedItem);
}
public static void SetAutoscroll(DependencyObject element, bool value)
{
element.SetValue(AutoscrollProperty, value);
}
public static bool GetAutoscroll(DependencyObject element)
{
return (bool) element.GetValue(AutoscrollProperty);
}
}