csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
8.4 KiB
314 lines
8.4 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using Avalonia.Collections;
|
|
|
|
#nullable enable
|
|
|
|
namespace Avalonia.Controls.Selection
|
|
{
|
|
internal class InternalSelectionModel : SelectionModel<object?>
|
|
{
|
|
private IList? _writableSelectedItems;
|
|
private int _ignoreModelChanges;
|
|
private bool _ignoreSelectedItemsChanges;
|
|
private bool _isResetting;
|
|
|
|
public InternalSelectionModel()
|
|
{
|
|
SelectionChanged += OnSelectionChanged;
|
|
SourceReset += OnSourceReset;
|
|
}
|
|
|
|
[AllowNull]
|
|
public IList WritableSelectedItems
|
|
{
|
|
get
|
|
{
|
|
if (_writableSelectedItems is null)
|
|
{
|
|
_writableSelectedItems = new AvaloniaList<object?>();
|
|
SubscribeToSelectedItems();
|
|
}
|
|
|
|
return _writableSelectedItems;
|
|
}
|
|
set
|
|
{
|
|
value ??= new AvaloniaList<object?>();
|
|
|
|
if (value.IsFixedSize)
|
|
{
|
|
throw new NotSupportedException("Cannot assign fixed size selection to SelectedItems.");
|
|
}
|
|
|
|
if (_writableSelectedItems != value)
|
|
{
|
|
UnsubscribeFromSelectedItems();
|
|
_writableSelectedItems = value;
|
|
SyncFromSelectedItems();
|
|
SubscribeToSelectedItems();
|
|
|
|
if (ItemsView is null)
|
|
{
|
|
SetInitSelectedItems(value);
|
|
}
|
|
|
|
RaisePropertyChanged(nameof(WritableSelectedItems));
|
|
}
|
|
}
|
|
}
|
|
|
|
private protected override void SetSource(IEnumerable? value)
|
|
{
|
|
if (Source == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
object?[]? oldSelection = null;
|
|
|
|
if (Source is object && value is object)
|
|
{
|
|
oldSelection = new object?[WritableSelectedItems.Count];
|
|
WritableSelectedItems.CopyTo(oldSelection, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
_ignoreSelectedItemsChanges = true;
|
|
base.SetSource(value);
|
|
}
|
|
finally
|
|
{
|
|
_ignoreSelectedItemsChanges = false;
|
|
}
|
|
|
|
if (oldSelection is null)
|
|
{
|
|
SyncToSelectedItems();
|
|
}
|
|
else
|
|
{
|
|
foreach (var i in oldSelection)
|
|
{
|
|
var index = ItemsView!.IndexOf(i);
|
|
Select(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SyncToSelectedItems()
|
|
{
|
|
if (_writableSelectedItems is object)
|
|
{
|
|
try
|
|
{
|
|
_ignoreSelectedItemsChanges = true;
|
|
_writableSelectedItems.Clear();
|
|
|
|
foreach (var i in base.SelectedItems)
|
|
{
|
|
_writableSelectedItems.Add(i);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_ignoreSelectedItemsChanges = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SyncFromSelectedItems()
|
|
{
|
|
if (Source is null || _writableSelectedItems is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
++_ignoreModelChanges;
|
|
|
|
using (BatchUpdate())
|
|
{
|
|
Clear();
|
|
|
|
for (var i = 0; i < _writableSelectedItems.Count; ++i)
|
|
{
|
|
var index = IndexOf(Source, _writableSelectedItems[i]);
|
|
|
|
if (index != -1)
|
|
{
|
|
Select(index);
|
|
}
|
|
else
|
|
{
|
|
_writableSelectedItems.RemoveAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
--_ignoreModelChanges;
|
|
}
|
|
}
|
|
|
|
private void SubscribeToSelectedItems()
|
|
{
|
|
if (_writableSelectedItems is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged += OnSelectedItemsCollectionChanged;
|
|
}
|
|
}
|
|
|
|
private void UnsubscribeFromSelectedItems()
|
|
{
|
|
if (_writableSelectedItems is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged += OnSelectedItemsCollectionChanged;
|
|
}
|
|
}
|
|
|
|
private void OnSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
|
|
{
|
|
if (_ignoreModelChanges > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var items = WritableSelectedItems;
|
|
var deselected = e.DeselectedItems.ToList();
|
|
var selected = e.SelectedItems.ToList();
|
|
|
|
_ignoreSelectedItemsChanges = true;
|
|
|
|
foreach (var i in deselected)
|
|
{
|
|
items.Remove(i);
|
|
}
|
|
|
|
foreach (var i in selected)
|
|
{
|
|
items.Add(i);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_ignoreSelectedItemsChanges = false;
|
|
}
|
|
}
|
|
|
|
private protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.Action == NotifyCollectionChangedAction.Reset)
|
|
{
|
|
++_ignoreModelChanges;
|
|
_isResetting = true;
|
|
}
|
|
|
|
base.OnSourceCollectionChanged(e);
|
|
}
|
|
|
|
protected override void OnSourceCollectionChangeFinished()
|
|
{
|
|
base.OnSourceCollectionChangeFinished();
|
|
|
|
if (_isResetting)
|
|
{
|
|
--_ignoreModelChanges;
|
|
}
|
|
}
|
|
|
|
private void OnSourceReset(object sender, EventArgs e) => SyncFromSelectedItems();
|
|
|
|
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (_ignoreSelectedItemsChanges)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_writableSelectedItems == null)
|
|
{
|
|
throw new AvaloniaInternalException("CollectionChanged raised but we don't have items.");
|
|
}
|
|
|
|
void Remove()
|
|
{
|
|
foreach (var i in e.OldItems)
|
|
{
|
|
var index = IndexOf(Source, i);
|
|
|
|
if (index != -1)
|
|
{
|
|
Deselect(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
using var operation = BatchUpdate();
|
|
|
|
++_ignoreModelChanges;
|
|
|
|
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:
|
|
Clear();
|
|
Add(_writableSelectedItems);
|
|
break;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
--_ignoreModelChanges;
|
|
}
|
|
}
|
|
|
|
private void Add(IList newItems)
|
|
{
|
|
foreach (var i in newItems)
|
|
{
|
|
var index = IndexOf(Source, i);
|
|
|
|
if (index != -1)
|
|
{
|
|
Select(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|