盡力到目前爲止使用下面的輔助類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