我正在尝试使用两个按钮(每个方向一个)和 MvvM 模式以编程方式滚动列表视图的内容。
到目前为止,我能找到的所有其他解决方案都使用
x:Name
和后面的代码来访问列表视图并调用 ScrollIntoView(Object)
函数。我以为我可以利用 VisualTreeHelper 和 FindChildren<T>
,但这也需要 x:Name
来查找并返回列表视图对象以供我的 ViewModel 使用。
Xaml:
<!-- ListView to scroll through: -->
<ListView
SelectionMode="None"
ItemsSource="{Binding ListOfListItems}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="UcViewModel:ListItemViewModel">
<Uc:ListItemUserControl/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- One of my two buttons: -->
<Button
Content="Scroll up"
Command="{Binding ScrollUp}"/>
视图模型:
private DelegateCommand _scrollUp;
public ICommand ScrollUp => _scrollUp ??= new DelegateCommand(PerformScrollUp);
private void PerformScrollUp(object commandParameter)
{
//Here i want to call ListView.BringIntoView(NextItem)
}
我使用的 MVVM 库是:
Microsoft.Toolkit.Mvvm
我也尝试阅读文档,但据我了解,我需要找到一种方法来访问我的 ViewModel 中的 ListView 对象,但我无法弄清楚如何实现这一点。我对 WinUI 3 和 C# 都比较陌生,所以如果有任何遗漏的信息,请指出,我会尽力提供所需的信息。
编辑:由于我对所有页面和 ViewModel 使用 DI,我确实相信不可能使用 x:Name 将列表视图简单地注入到代码后面的 ViewModel 构造函数中。也就是说,我希望尽可能保持代码隐藏不变,以遵循 MVVM 模式。
在把我的问题抛在脑后并用新的眼光看待它之后,我找到了以下解决方案。但是我不知道它的效率,和/或它是否是糟糕的编码原则。
我所做的,是通过
CommandParameter
将控件传递给视图模型,并使用 x:Bind 将其绑定到所需的控件名称。从技术上讲,这使用了对隐藏代码的绑定,但是其隐藏代码本身保持不变。
在xaml中:
<ScrollViewer
x:Name = "TargetScrollViewer">
<ListView>
//Disables listviews internal scrollviewer
//Implement rest of ListView code
</ListView>
</ScrollViewer>
<Button
Command = "{Binding ScrollUp}"
CommandParameter = "{x:Bind TargetScrollViewer}"/>
<Button
Command = "{Binding ScrollDown}"
CommandParameter = "{x:Bind TargetScrollViewer}"/>
许多人似乎建议使用列表视图“ScrollIntoView”,如果是这种情况,只需解析列表视图而不是滚动查看器作为命令参数。
在视图模型内部:
private DelegateCommand _scrollDown;
public ICommand ScrollDown => _scrollDown ??= new DelegateCommand(PerformScrollDown);
private void PerformScrollDown(object commandParameter)
{
var scrollcontrol = commandParameter as ScrollViewer;
scrollcontrol.ScrollToVerticalOffset(scrollcontrol.VerticalOffset + 72);
}
在函数内部,我只需将命令参数转换回 Scrollviewer,然后调用所需的函数。
在我的例子中,我使用“Syncfusion”作为 DelegateCommand 将按钮绑定到视图。
在我的
ViewModel
中,除了实现ItemsSource
之外,我还实现了SelectedItem
并将属性绑定到我的ListView
。在下面的示例中,我使用 MVVM 模式来实现类似控制台的应用程序(其中对控制台的更新会将行追加到集合中并更新选定的行/索引):
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> ConsoleLines = new ObservableCollection<string>();
public string? SelectedConsoleLine = null;
public int SelectedConsoleIndex = -1;
public event PropertyChangedEventHandler PropertyChanged;
}
然后,在我的 XAML 中,我可以配置我的 ListView:
<ListView x:Name="ConsoleView"
ItemTemplate="{StaticResource consoleTemplate}"
ItemsSource="{Binding ConsoleLines}"
SelectedItem="{Binding SelectedConsoleLine,Mode=TwoWay}">
</ListView>
在View的代码隐藏中,我们监听ViewModel的PropertyChange事件来实现滚动,例如
public MainPage()
{
BindingContext = viewModel = new MainViewModel();
InitializeComponent();
PrimesButton.Clicked += PrimesButton_Clicked;
ClearButton.Clicked += ClearButton_Clicked;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
// ConsoleView is ListView
case nameof(viewModel.SelectedConsoleLine):
ConsoleView.ScrollTo(viewModel.SelectedConsoleLine, ScrollToPosition.MakeVisible, true); // ListView
break;
// ConsoleView is CollectionView
//case nameof(ViewModel.SelectedConsoleIndex):
// ConsoleView.ScrollTo(viewModel.SelectedConsoleIndex);
// break;
}
}
如果您想要一种真正 MVVM 友好的方式来执行此操作,则可以使用附加的
DependencyProperty
。
public static class AttachedCommand
{
#region object ScrollTarget dependency property
public static readonly DependencyProperty ScrollTargetProperty = DependencyProperty.RegisterAttached(
"ScrollTarget",
typeof(object),
typeof(AttachedCommand),
new PropertyMetadata(
(object)null,
(obj, args) =>
{
if (args.NewValue == null)
return; // or scroll to top if you prefer
if (!(obj is ListViewBase lvb))
throw new InvalidOperationException($"ScrollTarget property should only be used with ListViewBase.");
lvb.ScrollIntoView(args.NewValue);
}));
public static object GetScrollTarget(DependencyObject obj)
{
return obj.GetValue(ScrollTargetProperty);
}
public static void SetScrollTarget(DependencyObject obj, object value)
{
obj.SetValue(ScrollTargetProperty, value);
}
#endregion
}
用途:
<ListView local:AttachedCommand.ScrollTarget="{Binding ItemToScrollTo}"
ItemsSource="{Binding ListOfListItems}"
<!-- etc -->
</ListView>