2012-06-04 139 views
3

我正在使用依賴項屬性GroupDescription根據列表視圖項目源的屬性對WPF列表視圖中的項目進行分組。當綁定屬性發生更改時,ListView分組不會更新

我的問題是,只有在GroupDescription值被更改並且沒有改變列表視圖源中的項目的綁定屬性時,分組纔會更新。

在下面的示例中,GroupDescription設置爲城市,導致城市:「漢堡」的組描述。但是,如果項目城市屬性發生更改,則列表視圖中的分組不會更新,這意味着在「城市:漢堡」組內將出現城市「柏林」的項目。

分組只在GroupDescription更新後才更新。我試圖找到使用PersonPropertyChanged方法的工作,該方法將GroupDescription更改爲PersonId並立即返回City。然而,這種解決方法會導致列表視圖始終跳回頂部,以防滾動位置例如位於列表視圖的中間或末尾。使用具有不斷變化屬性的條目列表視圖來處理列表視圖時,這可能非常煩人。

有沒有一種方法可以在項目屬性發生更改而沒有列表視圖跳回頂端後「更新」分組?

非常感謝您的幫助! 托馬斯

GroupingListView.cs

using System.Windows.Controls; 
using System.Windows; 
using System.Windows.Data; 

namespace WpfApplication 
{ 
    /// <summary> 
    /// Enhanced list view based on WPF ListView with dependency properties for GroupDescriptions 
    /// </summary> 
    public class GroupingListView : ListView 
    { 
     /// <summary> 
     /// Dependency property for group descriptions 
     /// </summary> 
     public string GroupDescription 
     { 
      get { return (string)GetValue(GroupDescriptionProperty); } 
      set { SetValue(GroupDescriptionProperty, value); } 
     } 

     /// <summary> 
     /// Using a DependencyProperty as the backing store for GroupDescription. This enables animation, styling, binding, etc... 
     /// </summary> 
     public static readonly DependencyProperty GroupDescriptionProperty = 
      DependencyProperty.Register("GroupDescription", 
             typeof(string), 
             typeof(GroupingListView), 
             new UIPropertyMetadata(string.Empty, GroupDescriptionChanged)); 

     private static void GroupDescriptionChanged(DependencyObject source, DependencyPropertyChangedEventArgs args) 
     { 
      var control = source as GroupingListView; 
      // Stop if source is not of type DetailedListView 
      if (control == null) return; 

      // Stop if myView is not available, myView can not group, groupdescription missing\ 
      // or the argument is empty 
      var myView = (CollectionView)CollectionViewSource.GetDefaultView(control.ItemsSource); 
      if (myView == null || !myView.CanGroup || (string) args.NewValue == string.Empty || 
       myView.GroupDescriptions == null) 
      { 
       return; 
      } 
      myView.GroupDescriptions.Clear(); 
      // If a group description already 
      if(myView.GroupDescriptions.Count > 0) 
      { 
       var prop = myView.GroupDescriptions[0] as PropertyGroupDescription; 
       if(prop != null) 
       { 
        if(!prop.PropertyName.Equals((string)args.NewValue)) 
        { 
         myView.GroupDescriptions.Clear(); 
        } 
       } 
      } 

      // Stop if at this point a group description still exists. This means the newValue is 
      // equal to the old value and nothing needs to be changed 
      if (myView.GroupDescriptions.Count != 0) return; 

      // If this code is reached newValue is different than the current groupDescription value 
      // therefore the newValue has to be added as PropertyGroupDescription 
      var groupDescription = new PropertyGroupDescription((string)args.NewValue); 
      // Clear and add the description only if it's not already existing 
      myView.GroupDescriptions.Add(groupDescription); 
     } 
    } 
} 

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication="clr-namespace:WpfApplication" 
     Title="MainWindow" Height="300" Width="300"> 
    <StackPanel> 
     <Button Content="Change" Click="btnChangeCity" Height="22"/><Button Content="Change back" Click="btnChangeCityBack" Height="22"/> 
     <WpfApplication:GroupingListView ItemsSource="{Binding Persons}" Height="200" 
             GroupDescription="{Binding GroupDescription}" x:Name="GroupingListView"> 
      <ListView.GroupStyle> 
       <GroupStyle> 
        <GroupStyle.HeaderTemplate> 
         <DataTemplate> 
          <Grid HorizontalAlignment="Stretch"> 
           <Border HorizontalAlignment="Stretch" BorderBrush="Transparent" BorderThickness="1" CornerRadius="3"> 
            <Border HorizontalAlignment="Stretch" BorderBrush="LightGray" BorderThickness="0,0,0,1" CornerRadius="0"> 
             <StackPanel Orientation="Horizontal"> 
              <TextBlock Foreground="LightGray" Text="{Binding GroupDescription, ElementName=GroupingListView}"/> 
              <TextBlock Foreground="LightGray" Text=" : "/> 
              <TextBlock Foreground="LightGray" Text="{Binding Name}" HorizontalAlignment="Stretch"/> 
             </StackPanel> 
            </Border> 
           </Border> 
          </Grid> 
         </DataTemplate> 
        </GroupStyle.HeaderTemplate> 
       </GroupStyle> 
      </ListView.GroupStyle> 
      <ListView.View> 
       <GridView> 
        <GridViewColumn Header="PersonId" Width="100"> 
         <GridViewColumn.CellTemplate> 
          <DataTemplate> 
           <TextBlock TextTrimming="CharacterEllipsis" Text="{Binding PersonId, Mode=Default}"/> 
          </DataTemplate> 
         </GridViewColumn.CellTemplate> 
        </GridViewColumn> 
        <GridViewColumn Header="City" Width="100"> 
         <GridViewColumn.CellTemplate> 
          <DataTemplate> 
           <TextBlock TextTrimming="CharacterEllipsis" Text="{Binding City, Mode=Default}"/> 
          </DataTemplate> 
         </GridViewColumn.CellTemplate> 
        </GridViewColumn> 
       </GridView> 
      </ListView.View> 
     </WpfApplication:GroupingListView> 
    </StackPanel> 
</Window> 

MainWindow.xaml.cs

using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.ComponentModel; 

namespace WpfApplication 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : INotifyPropertyChanged 
    { 
     public class Person : INotifyPropertyChanged 
     { 


      public Person(string personId, string city) 
      { 
       PersonId = personId; 
       City = city; 
      } 

      private string _personId; 
      private string _city; 

      public event PropertyChangedEventHandler PropertyChanged; 
      protected void OnPropertyChanged(string name) 
      { 
       PropertyChangedEventHandler pc = PropertyChanged; 
       if (pc != null) 
        pc(this, new PropertyChangedEventArgs(name)); 
      } 

      public string PersonId 
      { 
       get { return _personId; } 
       set { _personId = value; OnPropertyChanged("PersonId"); } 
      } 

      public string City 
      { 
       get { return _city; } 
       set { _city = value; OnPropertyChanged("City"); } 
      } 


     } 

     public ObservableCollection<Person> Persons { get; set; } 

     public string GroupDescription 
     { 
      get { return _groupDescription; } 
      set { _groupDescription = value; OnPropertyChanged("GroupDescription"); } 
     } 

     private string _groupDescription; 

     public MainWindow() 
     { 
      InitializeComponent(); 
      DataContext = this; 
      GroupDescription = "City"; 
      Persons = new ObservableCollection<Person>(); 
      Persons.CollectionChanged += PersonsCollectionChanged; 
      Persons.Add(new Person("1", "Hamburg")); 
      Persons.Add(new Person("2", "Hamburg")); 
      Persons.Add(new Person("3", "Hamburg")); 
      Persons.Add(new Person("4", "Hamburg")); 
      Persons.Add(new Person("5", "Hamburg")); 
      Persons.Add(new Person("6", "Hamburg")); 
      Persons.Add(new Person("7", "Hamburg")); 
      Persons.Add(new Person("8", "Hamburg")); 
      Persons.Add(new Person("9", "Berlin")); 
      Persons.Add(new Person("10", "Hamburg")); 
      Persons.Add(new Person("11", "Hamburg")); 
      Persons.Add(new Person("12", "Munich")); 
      Persons.Add(new Person("13", "Munich")); 
      OnPropertyChanged("Persons"); 
     } 

     public void PersonsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      if (e.Action == NotifyCollectionChangedAction.Remove) 
      { 
       foreach(Person item in e.OldItems) 
       { 
        //Removed items 
        item.PropertyChanged -= PersonPropertyChanged; 
       } 
      } 
      else if (e.Action == NotifyCollectionChangedAction.Add) 
      { 
       foreach(Person item in e.NewItems) 
       { 
        //Added items 
        item.PropertyChanged += PersonPropertyChanged; 
       }  
      }  
     } 

    public void PersonPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     //GroupDescription = "PersonId"; 
     //GroupDescription = "City"; 
    } 


     public event PropertyChangedEventHandler PropertyChanged; 
     protected void OnPropertyChanged(string name) 
     { 
      PropertyChangedEventHandler pc = PropertyChanged; 
      if (pc != null) 
       pc(this, new PropertyChangedEventArgs(name)); 
     } 

     private void btnChangeCity(object sender, System.Windows.RoutedEventArgs e) 
     { 
      Persons[0].City = "Berlin"; 
     } 

     private void btnChangeCityBack(object sender, System.Windows.RoutedEventArgs e) 
     { 
      Persons[0].City = "Hamburg"; 
     } 

    } 
} 

回答

8

我自己找到了一個工作解決方案。

內PersonPropertyChanged方法我稱之爲

ICollectionView view = CollectionViewSource.GetDefaultView(GroupingListView.ItemsSource); 
view.Refresh(); 

我不知道這是否是完美的解決方案,但它正在爲我的問題。

11

我意識到這是相當晚的一天,但如果您使用的是.NET4.5或更高版本,您可以使用Live Grouping功能,我認爲這些功能可以完全滿足您的需求。

例如,而不是直接結合ListViewItemsSourcePersons,則反而會結合到CollectionViewSource,其本身結合Persons

<Window.Resources> 
    <CollectionViewSource x:Key="PersonsViewSource" Source="{Binding Persons}" IsLiveGroupingRequested="True"> 
     <CollectionViewSource.GroupDescriptions> 
       <PropertyGroupDescription PropertyName="GroupName" /> 
     </CollectionViewSource.GroupDescriptions>    
    </CollectionViewSource> 
</Window.Resources> 

如以上那樣,你只添加屬性IsLiveGroupingRequested="True",並添加您要重新組合的屬性名稱。

當(通過您使用的INotifyPropertyChanged)的GroupName性質的變化,相關項目將自己移動到正確的組中的ListView,在不改變任何東西。

+0

適用於MVVM模式。 –

+0

拿我的upvote!應該是答案。 –

相關問題