获取列表视图中当前显示的项目数 WPF MVVM

Get the number of items currently displayed in the listview WPF MVVM

我有一个可以使用文本框过滤的列表视图:

<TextBox TextChanged="txtFilter_TextChanged" Name="FilterLv"/>

在视图代码隐藏中,我执行以下操作:

    CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.lv.ItemsSource);
    view.Filter = UserFilter;

    private bool UserFilter(object item)
    {
        if (String.IsNullOrEmpty(FilterLv.Text))
            return true;
        else
        {
            DataModel m = (item as DataModel);
            bool result = (m.Name.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0 ||
                         //m.Surname.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0);

            return result;
        }
    }

    private void Filter_TextChanged(object sender, TextChangedEventArgs e)
    {
        CollectionViewSource.GetDefaultView(this.lv.ItemsSource).Refresh();
    }

现在我在视图中放置了一个标签,我希望这个标签显示列表视图中当前显示的项目数。

我该怎么做?我发现了 this 之类的东西,但我根本不明白什么是 RowViewModelsCollectionView。在这个link中建议绑定如下:

<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>

任何人都可以向我解释或提供一个非常简单的例子来说明如何做吗?

最终更新

查看型号:

public class TestViewModel
{
// lv is populated later in code
public ObservableCollection<DataModel> lv = new ObservableCollection<DataModel>();

    public ObservableCollection<DataModel> LV
    {
        get
        {
            return this.lv;
        }

        private set
        {
            this.lv= value;
            OnPropertyChanged("LV");
        }
    }

private CollectionView view;

public TestViewModel()
{
        this.view = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
        view.Filter = UserFilter;
}

private string textFilter;
public string TextFilter
{
        get
        {
            return this.textFilter;
        }

        set
        {
            this.textFilter= value;
            OnPropertyChanged("TextFilter");

            if (String.IsNullOrEmpty(value))
                this.view.Filter = null;
            else
                this.view.Filter = UserFilter;
        }
}

private bool UserFilter(object item)
{
    if (String.IsNullOrEmpty(this.TextFilter))
        return true;
    else
    {
        DataModel m = (item as DataModel);
        bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                     //m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);

        return result;
    }
}



    /// <summary>
    /// Número de registros en la listview.
    /// </summary>
    public int NumberOfRecords
    {
        get
        {
            return this.view.Count;
        }
    }
}

查看 (xaml):

 <!-- search textbox - filter -->
 <TextBox TextChanged="txtFilter_TextChanged"
          Text="{Binding TextFilter,  UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

 <!-- label to show the number of records -->
<Label Content="{Binding NumberOfRecords}"/>

查看代码隐藏 (xaml.cs):

    private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
    {          
        CollectionViewSource.GetDefaultView((DataContext as TestViewModel).LV).Refresh();
    }

当我在搜索文本框中输入时过滤正常并且列表视图正确更新但记录数始终为 0。

我做错了什么?

ATTEMPT2: 下面的另一个尝试不起作用。如果我将 listivew 附加到模型视图中声明的视图,则不会显示任何项目。如果我在模型视图中将列表视图附加到 LV,则会显示项目,当我通过搜索文本框进行过滤时,它会过滤正常,列表视图会更新,但列表视图中显示的行数始终保持为 0。

备注:

查看模型:

namespace MyTest.Example
{
public Class TestViewModel : INotifyPropertyChanged // Implementations not here to simplify the code here.
{
private ObservableCollection<DataModel> lv;
public ObservableCollection<DataModel> LV
{
     get
     {
         return this.lv;
     }

     private set
     {
         this.lv = value;
         OnPropertyChanged("LV");
     }
}

public CollectionView View { get; set; }

public TestViewModel()
{
     this.LV = new ObservableCollection<DataModel>();
            // this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
            // this.View.Filter = UserFilter;
}

private string textFilter = string.Empty;
public string TextFilter
{
      get
      {
           return this.textFilter ;
      }

      set
      {
           this.textFilter = value;
           OnPropertyChanged("TextFilter");

           this.View.Refresh();
      }
}

private bool UserFilter(object item)
{
    if (String.IsNullOrEmpty(this.TextFilter))
        return true;
    else
    {
        DataModel m = (item as DataModel);
        bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                     //m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);

        return result;
    }
}

public void LoadData()
{
    this.LV = LoadDataFromDB();
    this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
    this.View.Filter = UserFilter;
}
} // End Class
} // End namespace

查看代码隐藏 (xaml.cs):

namespace MyTest.Example
{
  public Class TestView
  {
       public TestView()
       {
            InitializeComponent();
            (DataContext as TestViewModel).LoadData();
       }       
  }
}

查看 (xaml):

xmlns:vm="clr-namespace:MyTest.Example"

 <!-- search textbox - filter -->
 <TextBox Text="{Binding Path=TextFilter,  UpdateSourceTrigger=PropertyChanged}">

 <!-- label to show the number of records -->
<Label Content="{Binding Path=View.Count}" ContentStringFormat="No. Results: {0}"/>

<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=View}" SelectionMode="Extended" AlternationCount="2">

尝试 3: 最后我让它工作了。解决方案与 ATTEMPT2 相同,但进行了以下更改:

我已经替换了这个:

public CollectionView View { get; set; }

来自这个:

    private CollectionView view;
    public CollectionView View {
        get
        {
            return this.view;
        }

        private set
        {
            if (this.view == value)
            {
                return;
            }

            this.view = value;
            OnPropertyChanged("View");
        }
    }

其余的和ATTEMPT2一样。在视图 View.Count 中并将视图作为 ItemsSource 分配给我的列表视图现在一切正常。

你应该使用

<Label Content="{Binding ModelView.Count}"/>

而不是

<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>
另一个问题中的

RowViewModelsCollectionView 与您的情况中的 ModelView 相同。

编辑

CountCollectionView

的 属性

有关更多信息,请查看 MSDN

编辑 2

当你不想像我的例子那样通过 XAML 来做时,你必须实现 INotifyPropertyChanged 并在绑定 属性 改变时引发它,否则 UI 不会找零。 在你的情况下:你必须在你的过滤器方法中调用 OnPropertyChanged("NumberOfRecords"); 。但是像我之前写的那样通过 xaml 来做会更容易。

如果正确遵循 MVVM,这实际上会容易得多。 CollectionView 要么在 XAML 中声明,要么在视图模型中声明为 属性。这允许您直接绑定到 CollectionView.Count.

这是一个示例,说明如何通过我的一个应用程序将 CollectionViewSource 放置在 XAML 中:

<UserControl
    x:Class="ChronoPall.App.TimeEntryList.TimeEntryListView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:app="clr-namespace:ChronoPall.App"
    xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ChronoPall.App.TimeEntryList"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DataContext="{d:DesignInstance local:TimeEntryListViewVm}"
    d:DesignHeight="300"
    d:DesignWidth="300"
    mc:Ignorable="d">
    <UserControl.Resources>
        <CollectionViewSource x:Key="TimeEntriesSource" Source="{Binding Path=TimeEntries}">
            <CollectionViewSource.SortDescriptions>
                <componentModel:SortDescription Direction="Descending" PropertyName="StartTime.Date" />
                <componentModel:SortDescription Direction="Ascending" PropertyName="StartTime" />
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="EntryDate" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </UserControl.Resources>
    <Grid IsSharedSizeScope="True">
        <ScrollViewer VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Source={StaticResource TimeEntriesSource}}">
                <ItemsControl.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate DataType="{x:Type CollectionViewGroup}">
                                <local:TimeEntryListDayGroup />
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </ItemsControl.GroupStyle>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:TimeEntryListItem />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</UserControl>

它实际上并没有绑定到 Count,但它可以很容易地做到这一点:

<TextBlock Text="{Binding Path=Count, Source={StaticResource TimeEntriesSource}}/>

要在视图模型中执行此操作,您只需创建 ICollectionView 的只读 属性,并将其设置为等于 CollectionViewSource.GetDefaultView(SomeObservableCollection‌​),然后绑定到它。

这是一个完整的工作示例,其中包含视图模型中的 CollectionView,过滤器计数自动流向绑定控件。它使用 my mvvm library 作为基础 ViewModel class 来提供 INotifyPropertyChanged,但是您应该可以轻松地替换您自己的系统,我没有对它做任何特别的事情。

完整的源代码可以从here

下载

XAML:

<Window
    x:Class="FilterWithBindableCount.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:local="clr-namespace:FilterWithBindableCount"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    d:DataContext="{d:DesignInstance local:MainWindowVm}"
    mc:Ignorable="d">
    <Grid Margin="4">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Label
            Grid.Row="0"
            Grid.Column="0"
            Margin="4">
            Filter:
        </Label>
        <TextBox
            Grid.Row="0"
            Grid.Column="1"
            Margin="4"
            VerticalAlignment="Center"
            Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock
            Grid.Row="1"
            Grid.Column="0"
            Grid.ColumnSpan="2"
            Margin="4"
            Text="{Binding Path=PeopleView.Count, StringFormat={}Count: {0}}" />
        <DataGrid
            Grid.Row="3"
            Grid.Column="0"
            Grid.ColumnSpan="2"
            Margin="4"
            CanUserAddRows="False"
            CanUserSortColumns="True"
            ItemsSource="{Binding Path=PeopleView}" />
    </Grid>
</Window>

查看型号:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using AgentOctal.WpfLib;

namespace FilterWithBindableCount
{
    class MainWindowVm : ViewModel
    {
        public MainWindowVm()
        {    
            People = new ObservableCollection<PersonVm>();
            PeopleView = (CollectionView) CollectionViewSource.GetDefaultView(People);
            PeopleView.Filter = obj =>
            {
                var person = (PersonVm)obj;
                return person.FirstName.ToUpper().Contains(FilterText.ToUpper() ) || person.LastName.ToUpper().Contains(FilterText.ToUpper());
            };

            People.Add(new PersonVm() { FirstName = "Bradley", LastName = "Uffner" });
            People.Add(new PersonVm() { FirstName = "Fred", LastName = "Flintstone" });
            People.Add(new PersonVm() { FirstName = "Arnold", LastName = "Rimmer" });
            People.Add(new PersonVm() { FirstName = "Jean-Luc", LastName = "Picard" });
            People.Add(new PersonVm() { FirstName = "Poppa", LastName = "Smurf" });    
        }
        public ObservableCollection<PersonVm> People { get; }
        public CollectionView PeopleView { get; }

        private string _filterText = "";
        public string FilterText
        {
            get => _filterText;
            set
            {
                if (SetValue(ref _filterText, value))
                {
                    PeopleView.Refresh();
                }
            }
        }    
    }

    class PersonVm:ViewModel
    {
        private string _firstName;
        public string FirstName
        {
            get {return _firstName;}
            set {SetValue(ref _firstName, value);}
        }

        private string _lastName;
        public string LastName
        {
            get {return _lastName;}
            set {SetValue(ref _lastName, value);}
        }
    }
}