尽力到目前为止使用下面的辅助类ObservableGroupingCollection
public class ObservableGroupingCollection<K, T> where K : IComparable
{
public ObservableGroupingCollection(ObservableCollection<T> collection)
{
_rootCollection = collection;
_rootCollection.CollectionChanged += _rootCollection_CollectionChanged;
}
ObservableCollection<T> _rootCollection;
private void _rootCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleCollectionChanged(e);
}
ObservableCollection<Grouping<K, T>> _items;
public ObservableCollection<Grouping<K, T>> Items
{
get { return _items; }
}
IComparer<T> _sortOrder;
Func<T, K> _groupFunction;
public void ArrangeItems(IComparer<T> sortorder, Func<T, K> group)
{
_sortOrder = sortorder;
_groupFunction = group;
var temp = _rootCollection
.OrderBy(i => i, _sortOrder)
.GroupBy(_groupFunction)
.ToList()
.Select(g => new Grouping<K, T>(g.Key, g));
_items = new ObservableCollection<Grouping<K, T>>(temp);
}
private void HandleCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var item = (T)(e.NewItems[0]);
var value = _groupFunction.Invoke(item);
// find matching group if exists
var existingGroup = _items.FirstOrDefault(g => g.Key.Equals(value));
if (existingGroup == null)
{
var newlist = new List<T>();
newlist.Add(item);
// find first group where Key is greater than this key
var insertBefore = _items.FirstOrDefault(g => ((g.Key).CompareTo(value)) > 0);
if (insertBefore == null)
{
// not found - add new group to end of list
_items.Add(new Grouping<K, T>(value, newlist));
}
else
{
// insert new group at this index
_items.Insert(_items.IndexOf(insertBefore), new Grouping<K, T>(value, newlist));
}
}
else
{
// find index to insert new item in existing group
int index = existingGroup.ToList().BinarySearch(item, _sortOrder);
if (index < 0)
{
existingGroup.Insert(~index, item);
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
var item = (T)(e.OldItems[0]);
var value = _groupFunction.Invoke(item);
var existingGroup = _items.FirstOrDefault(g => g.Key.Equals(value));
if (existingGroup != null)
{
// find existing item and remove
var targetIndex = existingGroup.IndexOf(item);
existingGroup.RemoveAt(targetIndex);
// remove group if zero items
if (existingGroup.Count == 0)
{
_items.Remove(existingGroup);
}
}
}
}
}
,其中通用分组类(其本身暴露了一个ObservableCollection)来自本文
http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping
为了使工作演示: -
从新的UWP空白应用程序中,添加上面的ObservableGroupingCollection类。然后,在相同的命名空间添加另一个类文件,并添加以下所有类别
// Data models
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string State { get; set; }
}
public class DataPool
{
public static string GenerateFirstName(Random random)
{
List<string> names = new List<string>() { "Lilly", "Mukhtar", "Sophie", "Femke", "Abdul-Rafi", "Mariana", "Aarif", "Sara", "Ibadah", "Fakhr", "Ilene", "Sardar", "Hanna", "Julie", "Iain", "Natalia", "Henrik", "Rasa", "Quentin", "Gadi", "Pernille", "Ishtar", "Jimmy", "Justine", "Lale", "Elize", "Randy", "Roshanara", "Rajab", "Marcus", "Mark", "Alima", "Francisco", "Thaqib", "Andreas", "Marianna", "Amalie", "Rodney", "Dena", "Amar", "Anna", "Nasreen", "Reema", "Tomas", "Filipa", "Frank", "Bari'ah", "Parvaiz", "Jibran", "Tomas", "Elli", "Carlos", "Diego", "Henrik", "Aruna", "Vahid", "Eliana", "Roxanne", "Amanda", "Ingrid", "Wesley", "Malika", "Basim", "Eisa", "Alina", "Andreas", "Deeba", "Diya", "Parveen", "Bakr", "Celine", "Daniel", "Mattheus", "Edmee", "Hedda", "Maria", "Maja", "Alhasan", "Alina", "Hedda", "Vanja", "Robin", "Victor", "Aaftab", "Guilherme", "Maria", "Kai", "Sabien", "Abdel", "Jason", "Bahaar", "Vasco", "Jibran", "Parsa", "Catalina", "Fouad", "Colette", "John", "Fred", "James", "Harry", "Ben", "Steven", "Philip", "Dougal", "Jasper", "Elliott", "Charles", "Gerty", "Sarah", "Sonya", "Svetlana", "Dita", "Karen", "Christine", "Angela", "Heather", "Spence", "Graham", "David", "Bernie", "Darren", "Lester", "Vince", "Colin", "Bernhard", "Dieter", "Norman", "William", "Nigel", "Nick", "Nikki", "Trent", "Devon", "Steven", "Eric", "Derek", "Raymond", "Craig" };
return names[random.Next(0, names.Count)];
}
public static string GenerateLastName(Random random)
{
List<string> lastnames = new List<string>() { "Carlson", "Attia", "Quincey", "Hollenberg", "Khoury", "Araujo", "Hakimi", "Seegers", "Abadi", "Krommenhoek", "Siavashi", "Kvistad", "Vanderslik", "Fernandes", "Dehmas", "Sheibani", "Laamers", "Batlouni", "Lyngvær", "Oveisi", "Veenhuizen", "Gardenier", "Siavashi", "Mutlu", "Karzai", "Mousavi", "Natsheh", "Nevland", "Lægreid", "Bishara", "Cunha", "Hotaki", "Kyvik", "Cardoso", "Pilskog", "Pennekamp", "Nuijten", "Bettar", "Borsboom", "Skistad", "Asef", "Sayegh", "Sousa", "Miyamoto", "Medeiros", "Kregel", "Shamoun", "Behzadi", "Kuzbari", "Ferreira", "Barros", "Fernandes", "Xuan", "Formosa", "Nolette", "Shahrestaani", "Correla", "Amiri", "Sousa", "Fretheim", "Van", "Hamade", "Baba", "Mustafa", "Bishara", "Formo", "Hemmati", "Nader", "Hatami", "Natsheh", "Langen", "Maloof", "Patel", "Berger", "Ostrem", "Bardsen", "Kramer", "Bekken", "Salcedo", "Holter", "Nader", "Bettar", "Georgsen", "Cuninho", "Zardooz", "Araujo", "Batalha", "Antunes", "Vanderhoorn", "Srivastava", "Trotter", "Siavashi", "Montes", "Sherzai", "Vanderschans", "Neves", "Sarraf", "Kuiters", "Hestoe", "Cornwall", "Paisley", "Cooper", "Jakoby", "Smith", "Davies", "Jonas", "Bowers", "Fernandez", "Perez", "Black", "White", "Keller", "Hernandes", "Clinton", "Merryweather", "Freeman", "Anguillar", "Goodman", "Hardcastle", "Emmott", "Kirkby", "Thatcher", "Jamieson", "Spender", "Harte", "Pinkman", "Winterman", "Knight", "Taylor", "Wentworth", "Manners", "Walker", "McPherson", "Elder", "McDonald", "Macintosh", "Decker", "Takahashi", "Wagoner" };
return lastnames[random.Next(0, lastnames.Count)];
}
public static string GenerateState(Random random)
{
List<string> states = new List<string>() { "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "District Of Columbia", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming" };
return states[random.Next(0, states.Count)];
}
}
public class Cache
{
public Cache()
{
InitializeCacheData();
SimulateLiveChanges(new TimeSpan(0, 0, 1));
}
public ObservableCollection<Contact> Contacts { get; set; }
private static Random rnd = new Random();
private void InitializeCacheData()
{
Contacts = new ObservableCollection<Contact>();
var i = 0;
while (i < 5)
{
Contacts.Add(new Contact()
{
FirstName = DataPool.GenerateFirstName(rnd),
LastName = DataPool.GenerateLastName(rnd),
State = DataPool.GenerateState(rnd)
});
i++;
}
}
private async void SimulateLiveChanges(TimeSpan MyInterval)
{
double MyIntervalSeconds = MyInterval.TotalSeconds;
while (true)
{
await Task.Delay(MyInterval);
//int addOrRemove = rnd.Next(1, 10);
//if (addOrRemove > 3)
//{
// add item
Contacts.Add(new Contact()
{
FirstName = DataPool.GenerateFirstName(rnd),
LastName = DataPool.GenerateLastName(rnd),
State = DataPool.GenerateState(rnd)
});
//}
//else
//{
// // remove random item
// if (Contacts.Count > 0)
// {
// Contacts.RemoveAt(rnd.Next(0, Contacts.Count - 1));
// }
//}
}
}
}
// ViewModel
public class ViewModel : BaseViewModel
{
public ViewModel()
{
_groupingCollection = new ObservableGroupingCollection<string, Contact>(new Cache().Contacts);
_groupingCollection.ArrangeItems(new StateSorter(), (x => x.State));
NotifyPropertyChanged("GroupedContacts");
}
ObservableGroupingCollection<string, Contact> _groupingCollection;
public ObservableCollection<Grouping<string, Contact>> GroupedContacts
{
get
{
return _groupingCollection.Items;
}
}
// swap grouping commands
private ICommand _groupByStateCommand;
public ICommand GroupByStateCommand
{
get
{
if (_groupByStateCommand == null)
{
_groupByStateCommand = new RelayCommand(
param => GroupByState(),
param => true);
}
return _groupByStateCommand;
}
}
private void GroupByState()
{
_groupingCollection.ArrangeItems(new StateSorter(), (x => x.State));
NotifyPropertyChanged("GroupedContacts");
}
private ICommand _groupByNameCommand;
public ICommand GroupByNameCommand
{
get
{
if (_groupByNameCommand == null)
{
_groupByNameCommand = new RelayCommand(
param => GroupByName(),
param => true);
}
return _groupByNameCommand;
}
}
private void GroupByName()
{
_groupingCollection.ArrangeItems(new NameSorter(), (x => x.LastName.First().ToString()));
NotifyPropertyChanged("GroupedContacts");
}
}
// View Model helpers
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
// Sorter classes
public class NameSorter : Comparer<Contact>
{
public override int Compare(Contact x, Contact y)
{
int result = x.LastName.First().CompareTo(y.LastName.First());
if (result != 0)
{
return result;
}
else
{
result = x.LastName.CompareTo(y.LastName);
if (result != 0)
{
return result;
}
else
{
return x.FirstName.CompareTo(y.FirstName);
}
}
}
}
public class StateSorter : Comparer<Contact>
{
public override int Compare(Contact x, Contact y)
{
int result = x.State.CompareTo(y.State);
if (result != 0)
{
return result;
}
else
{
result = x.LastName.CompareTo(y.LastName);
if (result != 0)
{
return result;
}
else
{
return x.FirstName.CompareTo(y.FirstName);
}
}
}
}
// Grouping class
// credit
// http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping
public class Grouping<K, T> : ObservableCollection<T>
{
public K Key { get; private set; }
public Grouping(K key, IEnumerable<T> items)
{
Key = key;
foreach (var item in items)
{
this.Items.Add(item);
}
}
}
最后,编辑的MainPage如下
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Page.Resources>
<CollectionViewSource
x:Key="cvs"
Source="{Binding GroupedContacts}"
IsSourceGrouped="True" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Source={StaticResource cvs}}"
x:Name="targetListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding LastName}" />
<TextBlock Text="{Binding FirstName}" Grid.Column="1" />
<TextBlock Text="{Binding State}" Grid.Column="2" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="Gainsboro">
<TextBlock FontWeight="Bold"
FontSize="14"
Margin="10,2"
Text="{Binding Key}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Content="Group By Initial" Command="{Binding GroupByNameCommand}" />
<Button Content="Group By State" Command="{Binding GroupByStateCommand}" />
</StackPanel>
</Grid>
HandleCollectionChanged方法只能处理添加/删除到目前为止,将打破,如果NotifyCollectionChangedEventArgs参数包含多个项目(现有的ObservableCollection类一次只通知一个更改)
所以它工作正常,但它都感觉有点哈克。
改进建议非常受欢迎。
为什么不听基础集合的事件,并根据需要更新分组集合?它可能需要比重建工作更多的努力来找出究竟在哪里放置它,但我会想象这就是CollectionViewSource会做的 –
谢谢。你是对的。我基本上是问我们是否必须自己做所有事情,或者是否有更好的选择来利用现有ObservableCollection类的力量。这是一个基本要求,我觉得编写管道代码每次都必须是重新发明轮子的情况,并且平台必须内置适当的方法。也许不是...... – Nick