【问题标题】:Get the number of items currently displayed in the listview WPF MVVM获取listview WPF MVVM中当前显示的项目数
【发布时间】:2017-07-27 12:44:47
【问题描述】:

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

<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。在此链接中建议绑定如下:

<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。

注意事项:

  • 我使用的是 NET 3.5 Visual Studio 2008。
  • 我需要在模型视图中将视图设置为可写,因为我没有设置它 在视图模型构造函数中,我将其设置在 LoadData 方法中 视图模型。 LoadData 从视图代码隐藏构造函数中调用。

查看模型

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 中并将 View 作为 ItemsSource 分配给我的列表视图现在一切正常。

【问题讨论】:

  • 如标题所述,如果您实际上使用 MVVM 模式,这会容易得多。只需将您的TextBox 绑定到CollectionViewSource.Count。我首先建议将其拟合到实际的 MVVM 模式,或者如果您不想走这条路,请在本地 CollectionViewSource 上从 Count 更新 TextBox
  • 我正在使用 MVVM,但我完全迷路了。什么是collectionViewSource?我是 WPF 的新手。我的列表视图附加到一个 observablecollection。
  • 您发布的代码的第一行使用来自CollectionViewSource 的默认CollectionView,这就是我所指的。
  • 抱歉,CountCollectionView 的属性,而不是 CollectionViewSource。你的view 变量是CollectionView,所以你可以从那里得到它。
  • @BradleyUffner 但“视图”在视图代码隐藏中,而不是在视图模型中。那么如何在xaml中将其绑定到视图中的标签上呢?

标签: wpf xaml listview collectionviewsource


【解决方案1】:

你应该使用

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

而不是

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

RowViewModelsCollectionView 在另一个问题中与ModelView 在您的情况下相同。

编辑

Count 是来自CollectionView 的属性

更多信息请查看MSDN

编辑 2

当您不想像我的示例中那样通过 XAML 执行此操作时,您必须实现 INotifyPropertyChanged 并在绑定属性更改时引发此问题,因为否则 UI 将无法获得更改。 在你的情况下:你必须在你的过滤方法中调用OnPropertyChanged("NumberOfRecords");。但是像我之前写的那样通过 xaml 来做会更容易。

【讨论】:

  • 但什么是计数?我的视图模型中的整数属性?如果是这样,我分配给计数的值是多少?你能详细说明一下答案吗?
  • 它是来自CollectionViewSourceCollectionView 的一个属性。它真的应该直接在 XAML 中,或者在视图模型上,而不是代码隐藏。这就是让您感到棘手的原因。
  • @user1624552 就像 Bradley 说它是来自 CollectionView 的属性。我在答案中添加了这些信息
  • 我弄错了,它在CollectionView,而不是CollectionViewSource,但你的view 变量是正确的类型。
  • @BradleyUffner 也一样,改了 :)
【解决方案2】:

这是一个在视图模型中使用CollectionView 的完整示例,过滤器计数自动流向绑定控件。它使用my mvvm library 作为基础ViewModel 类来提供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);}
        }
    }
}

【讨论】:

  • 我已尝试将其调整为我的代码,但没有工作。请参阅我的最后一次更新尝试 2。
  • 好像我已经解决了,看我上次更新Attempt3。现在我正在测试它,似乎它有效。如果我在进行更多测试后看到这继续有效,我会回到这里说出来。我会投票给这个答案,因为你帮了我很多,这个例子是一个伟大的、简单的、可读的例子。这指导我使我的代码工作。如果否则我的某些测试失败,我也会回来解释出现的问题(如果有的话)。
【解决方案3】:

如果正确遵循 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‌​),然后绑定到该属性。

【讨论】:

  • 我完全不明白这个 xaml 代码....我不知道如何使它适应我的列表。无论如何,我怎么能从视图模型中的代码中声明呢?
  • 好的,&lt;CollectionViewSource x:Key="TimeEntriesSource" Source="{Binding Path=TimeEntries}"&gt; 在 XAML 中将 CollectionView 声明为 UserControl 的资源,将其视为名为 TimeEntriesSource 的变量。 CollectionViewSource 绑定了来自视图模型的名为 TimeEntriesObservableCollection&lt;ItemsControl ItemsSource="{Binding Source={StaticResource TimeEntriesSource}}"&gt; 是绑定到 XAML 中的 CollectionViewSource 资源的 ItemsControl。任何过滤或分组都应用了CollectionViewSource...
  • ...将反映在ItemsControl 中。对ObservableCollection 的任何更改都将通过CollectionViewSource,并且也将流向ItemsControl
  • 在这个例子中,我是分组而不是排序,但它是完全相同的过程。如果您想根据某些内容动态过滤,您可以将CollectionViewSource 上的过滤器属性绑定到您的视图模型中的某些内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 2012-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-16
相关资源
最近更新 更多