2017-07-27 2 views
0

J'ai un ListView qui peut être filtrée à l'aide d'une zone de texte:Obtenez le nombre d'éléments affichés dans le ListView WPF MVVM

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

De l'avis code-behind je procédez comme suit:

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

Maintenant, j'ai placé une étiquette dans la vue et je voudrais que cette étiquette montre le nombre d'éléments actuellement affichés dans la liste.

Comment puis-je le faire? J'ai trouvé des choses comme this mais je ne comprends pas du tout ce qu'est RowViewModelsCollectionView. Dans ce lien, il est suggéré de se lier comme ci-dessous:

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

Quelqu'un pourrait-il me expliquer ou fournir un exemple très peu et simple sur la façon de le faire?

FINAL MISE A JOUR:

Voir modèle:

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

Voir (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}"/> 

vue code-behind (xaml.cs) :

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

Il est le filtrage ok quand je tape dans la zone de texte de recherche et listview est correctement mis à jour, mais le nombre d'enregistrements est toujours 0.

Qu'est-ce que je fais mal?

ATTEMPT2: Ci-dessous une autre tentative ne fonctionne pas. Si j'attache mon listivew à la vue déclarée en mode modèle, aucun élément n'est affiché. Si j'attache listview à LV dans la vue de modèle alors les articles sont montrés, et quand je filtre dans ma zone de recherche filtre ok, listview est mis à jour mais le nombre de rangées montrées dans la listview reste toujours à 0.

Notes:

  • J'utilise .NET 3.5 Visual studio 2008.
  • dois-je définir Afficher en écriture en vue du modèle parce que je ne mets pas en vue constructeur de modèle, je l'ai mis à la place dans la méthode LoadData dans modèle de vue . LoadData est appelée à partir du constructeur view code-behind.

Voir Modèle:

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 

Voir le code-behing (XAML.cs):

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

Voir (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"> 

TENTATIVE 3: Enfin j'ai le faire fonctionner. La solution est la même chose que ESSAI2 mais en faisant des changements ci-dessous:

J'ai remplacé ceci:

public CollectionView View { get; set; } 

par celui-ci:

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

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

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

Tout le reste reste le même que dans ESSAI2. Dans View View.Count et l'affectation de View en ItemsSource à ma listview fonctionne maintenant parfaitement. dans l'autre question

+0

Ce serait beaucoup plus facile si vous étiez * en fait * en utilisant le modèle MVVM, comme votre titre l'indique. Il suffit de lier votre TextBox à CollectionViewSource.Count. Je recommande d'abord de l'adapter au modèle MVVM réel, ou si vous ne voulez pas suivre cette route, mettez à jour le 'TextBox' de' Count' sur le 'CollectionViewSource' local. –

+0

J'utilise MVVM mais je suis totalement perdu. Qu'est-ce que collectionViewSource? Je suis nouveau dans WPF. Ma listview est attachée à une collection observablecollection. – user1624552

+0

La toute première ligne de votre code posté utilise la valeur par défaut 'CollectionView' de' CollectionViewSource', c'est ce dont je parle. –

Répondre

1

Voici un exemple entièrement fonctionnel avec le CollectionView dans le modèle de vue, et le nombre de filtres passant automatiquement au contrôle lié. Il utilise my mvvm library pour la classe de base ViewModel pour fournir INotifyPropertyChanged, mais vous devriez facilement être en mesure de substituer votre propre système, je ne fais rien de spécial avec elle.

Le code source peut être téléchargé à partir here

application screenshot

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> 

modèles Vue:

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);} 
     } 
    } 
} 
+0

J'ai essayé de l'adapter à mon code mais je ne travaillais pas. voir ma dernière mise à jour ATTEMPT 2. – user1624552

+0

On dirait que je l'ai résolu, voir ma dernière mise à jour Attempt3. Maintenant, je le mets à l'essai, et cela semble fonctionner. Si je vois que cela continue à fonctionner après avoir fait d'autres tests, je reviendrai ici et je le dirai. Et je voterai cette réponse puisque vous m'avez beaucoup aidé et que cet exemple a été un excellent exemple simple et lisible. Ceci me guide pour faire fonctionner mon code. Sinon, une partie de mon test échoue, je vais aussi revenir pour expliquer le problème est apparu le cas échéant. – user1624552

+0

Cela fonctionne parfaitement, merci. – user1624552

1

Vous devez utiliser

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

au lieu de

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

RowViewModelsCollectionView est le même que ModelView est dans votre cas.

Modifier

Count est une propriété de la CollectionView

Pour plus d'informations ont un regard sur le MSDN

Edit 2

Lorsque vous ne voulez pas le faire via XAML comme dans mon exemple, vous devez mettre en œuvre INotifyPropertyChanged un nd augmenter ceci chaque fois que la propriété liée est changée car autrement l'interface ne recevra pas la modification. Dans votre cas: vous devez composer le OnPropertyChanged("NumberOfRecords"); dans votre méthode de filtrage. Mais il serait plus facile de le faire via xaml comme je l'ai écrit plus tôt.

+0

mais qu'est-ce que Count? une propriété entière dans mon modèle de vue? si oui, et quelle valeur j'attribue à compter? Pourriez-vous élaborer un peu plus la réponse? – user1624552

+1

C'est une propriété de 'CollectionView' qui vient de' CollectionViewSource'. Il devrait vraiment être directement en XAML, ou sur le viewmodel, pas en code-behind. C'est ce qui le rend difficile pour vous. –

+0

@ user1624552 comme Bradley a dit que c'est une propriété de 'CollectionView'. J'ai ajouté cette information dans ma réponse –

0

Ceci est réellement beaucoup plus facile lorsque vous suivez correctement MVVM. Le CollectionView est déclaré dans le code XAML ou en tant que propriété dans le modèle de vue. Cela vous permet de lier directement à CollectionView.Count.

Voici un exemple de la façon de placer le CollectionViewSource en XAML d'une de mes applications:

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

Il ne lie pas vraiment à Count, mais il pourrait facilement le faire avec:

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

pour le faire dans le viewmodel, vous serait tout simplement créer une propriété de lecture seule ICollectionView, et le mettre égal à CollectionViewSource.GetDefaultView(SomeObservableCollection‌​), puis se lier à cela.

+0

Je ne comprends pas du tout tout ce code xaml .... Je ne sais pas comment l'adapter à mon listivew. Quoi qu'il en soit, comment pourrais-je le déclarer du code dans le modèle de vue? – user1624552

+0

Ok, '' déclare un 'CollectionView' comme ressource de 'UserControl' dans XAML, pensez-y comme une variable nommée' TimeEntriesSource '. Ce 'CollectionViewSource' est lié' ObservableCollection' nommé 'TimeEntries' du viewmodel. '' est un objet ItemsControl qui est lié à la ressource CollectionViewSource dans le code XAML. Tout filtrage ou regroupement appliqué le 'CollectionViewSource' ... –

+0

... sera reflété dans' ItemsControl'. Toute modification de 'ObservableCollection' passera par' CollectionViewSource', et dans 'ItemsControl' également. –