使用 Josh Smith 的 WPF MVVM 演示应用程序实现 ListView 过滤器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12188623/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 05:09:22  来源:igfitidea点击:

Implementing a ListView Filter with Josh Smith's WPF MVVM Demo App

c#wpflistviewmvvm

提问by Alex Naish

I've been trying to expand Josh Smith's demo MVVM application in order to better understand the principals behind it and I've hit a wall when trying to implement a filter function on a View using a ListView.

我一直在尝试扩展 Josh Smith 的演示 MVVM 应用程序,以便更好地理解其背后的原理,并且在尝试使用 ListView 在视图上实现过滤器功能时遇到了障碍。

I have spent a few hours researching and dabbling but its just not working.

我花了几个小时研究和涉足,但它只是不工作。

My first step was to bind a textbox in my view to a property in my ViewModel:

我的第一步是将视图中的文本框绑定到 ViewModel 中的属性:

<TextBox Height="25" Name="txtFilter" Width="150" Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}"/>

This matches in my VM:

这在我的 VM 中匹配:

public string Filter
    {
        get { return this.filter; }
        set
        {
            this.filter = value;
            OnFilterChanged();
        }
    }

My VM used a ObservableCollection for the datasource but I've tried to convert it into an ICollectionView after reading tutorials:

我的 VM 使用 ObservableCollection 作为数据源,但我在阅读教程后尝试将其转换为 ICollectionView:

internal ObservableCollection<StaffViewModel> InnerStaff { get; set; }
    internal CollectionViewSource CvsStaff { get; set; }
    public ICollectionView AllStaff
    {
        get { return CvsStaff.View; }
    }

In my constructor I have specified:

在我的构造函数中,我指定了:

CvsStaff = new CollectionViewSource();
CvsStaff.Source = this.InnerStaff;
CvsStaff.Filter += ApplyFilter;

When my Filter Property gets updated it calls OnFilterChanged which is:

当我的过滤器属性更新时,它会调用 OnFilterChanged,即:

private void OnFilterChanged()
    {
        CvsStaff.View.Refresh();
    }

My ApplyFilter Function is:

我的 ApplyFilter 函数是:

void ApplyFilter(object sender, FilterEventArgs e)
    {
        StaffViewModel svm = (StaffViewModel)e.Item;

        if (this.Filter.Length == 0)
        {
            e.Accepted = true;
        }
        else
        {
            e.Accepted = svm.LastName.Contains(Filter);
        }
    }

Is there a silly mistake that I've made that anyone can help me spot? I'm fairly new to WPF and the MVVM pattern so I'm still learning!

我犯了一个愚蠢的错误,任何人都可以帮助我发现吗?我对 WPF 和 MVVM 模式还很陌生,所以我还在学习!

EDIT

编辑

In the View I bind the collection with:

在视图中,我将集合绑定到:

<CollectionViewSource
  x:Key="StaffGroup"
  Source="{Binding Path=AllStaff}"
  />

and the ListView is as such:

ListView 是这样的:

<ListView
      Name="staffList"
      AlternationCount="2" 
      DataContext="{StaticResource StaffGroup}" 
      ItemContainerStyle="{StaticResource StaffItemStyle}"
      ItemsSource="{Binding}"
        Grid.Row="1">

回答by SDK

The binding is incorrect. You need to make a few changes. The first thing is to make sure that the DataContext is set correctly. Typically you'll do this on a parent of the ListView and not set it directly on the ListView control. This could be a UserControl / Window / etc.

绑定不正确。您需要进行一些更改。第一件事是确保正确设置了 DataContext。通常,您将在 ListView 的父级上执行此操作,而不是直接在 ListView 控件上设置它。这可能是 UserControl / Window / 等。

So assuming you have a view model:

所以假设你有一个视图模型:

public class MainViewModel
{
    public MainViewModel()
    {
        //Create some fake data 
        InnerStaff = new ObservableCollection<StaffViewModel>();
        InnerStaff.Add(new StaffViewModel {FirstName = "Sue", LastName = "Bucknell"});
        InnerStaff.Add(new StaffViewModel {FirstName = "James", LastName = "Bucknell"});
        InnerStaff.Add(new StaffViewModel {FirstName = "John", LastName = "Harrod"});

        CvsStaff = new CollectionViewSource();
        CvsStaff.Source = this.InnerStaff;
        CvsStaff.Filter += ApplyFilter;
    }

    private string filter;

    public string Filter
    {
        get { return this.filter; }
        set
        {
            this.filter = value;
            OnFilterChanged();
        }
    }

    private void OnFilterChanged()
    {
        CvsStaff.View.Refresh();
    }

    internal ObservableCollection<StaffViewModel> InnerStaff { get; set; }
    internal CollectionViewSource CvsStaff { get; set; }
    public ICollectionView AllStaff
    {
        get { return CvsStaff.View; }
    }

    void ApplyFilter(object sender, FilterEventArgs e)
    {
        StaffViewModel svm = (StaffViewModel)e.Item;

        if (string.IsNullOrWhiteSpace(this.Filter) || this.Filter.Length == 0)
        {
            e.Accepted = true;
        }
        else
        {
            e.Accepted = svm.LastName.Contains(Filter);
        }
    }
}

And assuming you have a Window MainWindow.cs (code behind) you could (for this example) hook up the DataContext here.

假设您有一个 Window MainWindow.cs(代码隐藏),您可以(对于本示例)在此处连接 DataContext。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

Then you have a few choices for doing your binding, you could specify your CollectionViewSource in XAML or in code, but you've done both. i.e. the xaml one, with the key x:key="StaffGroup" and the VM one CvsStaff. Let's say we get rid of the xaml one completely and use the VM one, which is setup correctly. Then you would bind using the ItemsSource property, like so:

然后您有几个选择来进行绑定,您可以在 XAML 或代码中指定 CollectionViewSource,但您已经完成了这两项工作。即 xaml 之一,键 x:key="StaffGroup" 和 VM 一个 CvsStaff。假设我们完全摆脱了 xaml 并使用了正确设置的 VM 。然后您将使用 ItemsSource 属性进行绑定,如下所示:

<ListView Name="staffList" 
      AlternationCount="2" 
      ItemsSource="{Binding AllStaff}" 
      Grid.Row="1" />

Also small thing, I've changed the Filter to check for nulls and whitespace. You may also need to change it to be case-insensitive.

同样是小事,我更改了过滤器以检查空值和空格。您可能还需要将其更改为不区分大小写。

One other thing that I haven't mentioned here but is crucial is to implement INotifyPropertyChanged on your StaffViewModel - I assume you have, if not here's some code. You would typically also do this on most of your view models, to notify the view of changes to properties.

我在这里没有提到但至关重要的另一件事是在 StaffViewModel 上实现 INotifyPropertyChanged - 我假设你有,如果没有,这里有一些代码。您通常也会在大多数视图模型上执行此操作,以通知视图属性的更改。

internal class StaffViewModel : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
        }
    }
    public override string ToString()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}