我正在使用带有MVVM模式的ScrollViewer,并且ScrollViewer包含了一个项目列表,例如
<ScrollViewer>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
listview的项目绑定到viewmodel中的一组对象。我希望每当从集合中添加或删除项目时,scrollviewer都会滚动到顶部
我需要viewmodel来触发事件,而不是在视图的代码隐藏中使用ScrollToTop()
方法。
答案 0 :(得分:20)
恕我直言,最明智的方法是通过AttachedProperty
使用“行为”。 AttachedProperty
是一种扩展现有控件功能的机制。
首先,创建一个类来保存AtachedProperty
,例如:
public class ScrollViewerBehavior
{
public static bool GetAutoScrollToTop(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToTopProperty);
}
public static void SetAutoScrollToTop(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToTopProperty, value);
}
public static readonly DependencyProperty AutoScrollToTopProperty =
DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
{
var scrollViewer = o as ScrollViewer;
if (scrollViewer == null)
{
return;
}
if ((bool)e.NewValue)
{
scrollViewer.ScrollToTop();
SetAutoScrollToTop(o, false);
}
}));
}
此附加属性允许ScrollViewer
“神奇地”拥有Boolean
类型的新属性,就像您的XAML中的DependencyProperty
一样。如果将此属性绑定到ViewModel中的标准属性,例如:
private bool _reset;
public bool Reset
{
get { return _reset; }
set
{
_reset = value;
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
}
}
(同样,名称取决于您)然后将此Reset
属性设置为true
,您的ScrollViewer
将滚动到顶部。
我将AtachedProperty
命名为AutoScrollToTop,但名称对此并不重要。
XAML将类似于:
<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
注意:my
是您的ScrollViewerBehavior
班级所在的命名空间。例如:xmlns:my="clr-namespace:MyApp.Behaviors"
最后,您在ViewModel中唯一需要做的就是在您喜欢的时候设置Reset = true
,在您添加或删除集合中的元素时。
答案 1 :(得分:2)
创建一个新的ListView控件,扩展Listview并使用这个新的
sin6_scope_id
答案 2 :(得分:-1)
我还遇到了类似的场景,我需要以编程方式分配ScrollViewer的HorizontalOffset和VerticalOffset。恐怕没有直接约束机制。我做的是一种方式(相信我,我仍然不喜欢我遵循的方法,但我没有找到任何其他选择)。以下是我的建议:
勾选ScrollViewer的Loaded事件,将发送者对象强制转换为ScrollViewer并将其分配给DataContext中的属性(意味着您需要在DataContext中保留ScrollViewer属性,这将在UI中保存ScrollViewer的引用)。在ViewModel中连接ObservableCollection的CollectionChanged事件并使用ScrollViewer属性,您可以调用ScrollToTop()等方法。
这只是一种解决方法。我仍在寻找更好的解决方案。
答案 3 :(得分:-1)
在MVVM中执行此操作的最简单方法是在viewmodel中创建一个事件并从视图中订阅它。然后,在事件处理程序中,调用ScrollToTop
。
例如,每次修改集合时,都会从viewmodel触发事件,然后由视图决定是否对该事件作出反应并将列表滚动到顶部。
即使这涉及一些代码隐藏并且要求视图知道其视图模型的一部分,它也不会违反MVVM模式,与其他变通方法不同。
public interface IMyViewModel
{
event EventHandler MyCollectionChanged;
}
public class MyViewModel : IMyViewModel
{
public event EventHandler MyCollectionChanged;
// More viewmodel related stuff
protected virtual void OnMyCollectionChanged(EventArgs e)
{
if (MyCollectionChanged != null)
MyCollectionChanged(this, e);
}
}
public class MyWindow : Window
{
public MyWindow(IMyViewModel viewModel)
{
this.DataContext = viewModel;
InitializeComponent();
(this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
}
private void MyCollectionChangedEventHandler(object sender, EventArgs e)
{
// Do view related stuff
scrollViewer.ScrollToTop();
}
}
编辑:但当然,它可以提炼得更多。如果要避免使用代码隐藏,请查找DataEventTriggers。如果你不介意代码隐藏但是担心内存泄漏,请寻找弱事件。
最后,由于您想要的逻辑是100%视图相关的(每次添加或删除项目时都有ListView滚动),您还可以将其实现为行为/附加属性,或者扩展ListView 。这可能会让人感到更加复杂,但我鼓励你对这些选择进行一些思考。