9 changed files with 819 additions and 745 deletions
File diff suppressed because it is too large
@ -0,0 +1,259 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Specialized; |
||||
|
using System.ComponentModel; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.Controls.Selection; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Controls.Utils |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Synchronizes an <see cref="ISelectionModel"/> with a list of SelectedItems.
|
||||
|
/// </summary>
|
||||
|
internal class SelectedItemsSync |
||||
|
{ |
||||
|
private IList? _selectedItems; |
||||
|
private bool _updatingItems; |
||||
|
private bool _updatingModel; |
||||
|
private bool _initializeOnSourceAssignment; |
||||
|
|
||||
|
public SelectedItemsSync(ISelectionModel model) |
||||
|
{ |
||||
|
model = model ?? throw new ArgumentNullException(nameof(model)); |
||||
|
Model = model; |
||||
|
} |
||||
|
|
||||
|
public ISelectionModel Model { get; private set; } |
||||
|
|
||||
|
public IList GetOrCreateSelectedItems() |
||||
|
{ |
||||
|
if (_selectedItems == null) |
||||
|
{ |
||||
|
var items = new AvaloniaList<object?>(Model.SelectedItems); |
||||
|
items.CollectionChanged += ItemsCollectionChanged; |
||||
|
Model.SelectionChanged += SelectionModelSelectionChanged; |
||||
|
_selectedItems = items; |
||||
|
} |
||||
|
|
||||
|
return _selectedItems; |
||||
|
} |
||||
|
|
||||
|
public void SetSelectedItems(IList? items) |
||||
|
{ |
||||
|
items ??= new AvaloniaList<object>(); |
||||
|
|
||||
|
if (items.IsFixedSize) |
||||
|
{ |
||||
|
throw new NotSupportedException( |
||||
|
"Cannot assign fixed size selection to SelectedItems."); |
||||
|
} |
||||
|
|
||||
|
if (_selectedItems is INotifyCollectionChanged incc) |
||||
|
{ |
||||
|
incc.CollectionChanged -= ItemsCollectionChanged; |
||||
|
} |
||||
|
|
||||
|
if (_selectedItems == null) |
||||
|
{ |
||||
|
Model.SelectionChanged += SelectionModelSelectionChanged; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_updatingModel = true; |
||||
|
_selectedItems = items; |
||||
|
|
||||
|
if (Model.Source is object) |
||||
|
{ |
||||
|
using (Model.BatchUpdate()) |
||||
|
{ |
||||
|
Model.Clear(); |
||||
|
Add(items); |
||||
|
} |
||||
|
} |
||||
|
else if (!_initializeOnSourceAssignment) |
||||
|
{ |
||||
|
Model.PropertyChanged += SelectionModelPropertyChanged; |
||||
|
_initializeOnSourceAssignment = true; |
||||
|
} |
||||
|
|
||||
|
if (_selectedItems is INotifyCollectionChanged incc2) |
||||
|
{ |
||||
|
incc2.CollectionChanged += ItemsCollectionChanged; |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_updatingModel = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SetModel(ISelectionModel model) |
||||
|
{ |
||||
|
model = model ?? throw new ArgumentNullException(nameof(model)); |
||||
|
|
||||
|
if (_selectedItems != null) |
||||
|
{ |
||||
|
Model.PropertyChanged -= SelectionModelPropertyChanged; |
||||
|
Model.SelectionChanged -= SelectionModelSelectionChanged; |
||||
|
Model = model; |
||||
|
Model.SelectionChanged += SelectionModelSelectionChanged; |
||||
|
_initializeOnSourceAssignment = false; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_updatingItems = true; |
||||
|
_selectedItems.Clear(); |
||||
|
|
||||
|
foreach (var i in model.SelectedItems) |
||||
|
{ |
||||
|
_selectedItems.Add(i); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_updatingItems = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||
|
{ |
||||
|
if (_updatingItems) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (_selectedItems == null) |
||||
|
{ |
||||
|
throw new AvaloniaInternalException("CollectionChanged raised but we don't have items."); |
||||
|
} |
||||
|
|
||||
|
void Remove() |
||||
|
{ |
||||
|
foreach (var i in e.OldItems) |
||||
|
{ |
||||
|
var index = IndexOf(Model.Source, i); |
||||
|
|
||||
|
if (index != -1) |
||||
|
{ |
||||
|
Model.Deselect(index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using var operation = Model.BatchUpdate(); |
||||
|
|
||||
|
_updatingModel = true; |
||||
|
|
||||
|
switch (e.Action) |
||||
|
{ |
||||
|
case NotifyCollectionChangedAction.Add: |
||||
|
Add(e.NewItems); |
||||
|
break; |
||||
|
case NotifyCollectionChangedAction.Remove: |
||||
|
Remove(); |
||||
|
break; |
||||
|
case NotifyCollectionChangedAction.Replace: |
||||
|
Remove(); |
||||
|
Add(e.NewItems); |
||||
|
break; |
||||
|
case NotifyCollectionChangedAction.Reset: |
||||
|
Model.Clear(); |
||||
|
Add(_selectedItems); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_updatingModel = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Add(IList newItems) |
||||
|
{ |
||||
|
foreach (var i in newItems) |
||||
|
{ |
||||
|
var index = IndexOf(Model.Source, i); |
||||
|
|
||||
|
if (index != -1) |
||||
|
{ |
||||
|
Model.Select(index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (_initializeOnSourceAssignment && |
||||
|
_selectedItems != null && |
||||
|
e.PropertyName == nameof(ISelectionModel.Source)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_updatingModel = true; |
||||
|
Add(_selectedItems); |
||||
|
_initializeOnSourceAssignment = false; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_updatingModel = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) |
||||
|
{ |
||||
|
if (_updatingModel) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (_selectedItems == null) |
||||
|
{ |
||||
|
throw new AvaloniaInternalException("SelectionModelChanged raised but we don't have items."); |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var deselected = e.DeselectedItems.ToList(); |
||||
|
var selected = e.SelectedItems.ToList(); |
||||
|
|
||||
|
_updatingItems = true; |
||||
|
|
||||
|
foreach (var i in deselected) |
||||
|
{ |
||||
|
_selectedItems.Remove(i); |
||||
|
} |
||||
|
|
||||
|
foreach (var i in selected) |
||||
|
{ |
||||
|
_selectedItems.Add(i); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_updatingItems = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int IndexOf(object? source, object? item) |
||||
|
{ |
||||
|
if (source is IList l) |
||||
|
{ |
||||
|
return l.IndexOf(item); |
||||
|
} |
||||
|
else if (source is ItemsSourceView v) |
||||
|
{ |
||||
|
return v.IndexOf(item); |
||||
|
} |
||||
|
|
||||
|
return -1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue