WinUI 3. 使用 MVVM 以编程方式滚动列表视图

问题描述 投票:0回答:3

我正在尝试使用两个按钮(每个方向一个)和 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 模式。

listview mvvm scroll winui-3
3个回答
1
投票

在把我的问题抛在脑后并用新的眼光看待它之后,我找到了以下解决方案。但是我不知道它的效率,和/或它是否是糟糕的编码原则。

我所做的,是通过

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 将按钮绑定到视图。


0
投票

在我的

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;
        }
    }

0
投票

如果您想要一种真正 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>
© www.soinside.com 2019 - 2024. All rights reserved.