35 changed files with 1795 additions and 791 deletions
@ -1,7 +1,7 @@ |
|||||
<Project> |
<Project> |
||||
|
|
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="All" /> |
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
|
|
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="2.80.2-preview.33" /> |
<PackageReference Include="SkiaSharp" Version="2.80.2" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.80.2-preview.33" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.80.2" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,35 +1,25 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
x:Class="ControlCatalog.Pages.ListBoxPage"> |
x:Class="ControlCatalog.Pages.ListBoxPage"> |
||||
<StackPanel Orientation="Vertical" Spacing="4"> |
<DockPanel> |
||||
<TextBlock Classes="h1">ListBox</TextBlock> |
<StackPanel DockPanel.Dock="Top" Margin="4"> |
||||
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock> |
<TextBlock Classes="h1">ListBox</TextBlock> |
||||
|
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock> |
||||
<StackPanel Orientation="Horizontal" |
|
||||
Margin="0,16,0,0" |
|
||||
HorizontalAlignment="Center" |
|
||||
Spacing="16"> |
|
||||
<StackPanel Orientation="Vertical" Spacing="8"> |
|
||||
<ListBox Items="{Binding Items}" |
|
||||
Selection="{Binding Selection}" |
|
||||
AutoScrollToSelectedItem="True" |
|
||||
SelectionMode="{Binding SelectionMode}" |
|
||||
Width="250" |
|
||||
Height="350"/> |
|
||||
|
|
||||
<Button Command="{Binding AddItemCommand}">Add</Button> |
|
||||
|
|
||||
<Button Command="{Binding RemoveItemCommand}">Remove</Button> |
|
||||
|
|
||||
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button> |
|
||||
|
|
||||
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}"> |
|
||||
<ComboBoxItem>Single</ComboBoxItem> |
|
||||
<ComboBoxItem>Multiple</ComboBoxItem> |
|
||||
<ComboBoxItem>Toggle</ComboBoxItem> |
|
||||
<ComboBoxItem>AlwaysSelected</ComboBoxItem> |
|
||||
</ComboBox> |
|
||||
</StackPanel> |
|
||||
</StackPanel> |
</StackPanel> |
||||
</StackPanel> |
<StackPanel DockPanel.Dock="Right" Margin="4"> |
||||
|
<CheckBox IsChecked="{Binding Multiple}">Multiple</CheckBox> |
||||
|
<CheckBox IsChecked="{Binding Toggle}">Toggle</CheckBox> |
||||
|
<CheckBox IsChecked="{Binding AlwaysSelected}">AlwaysSelected</CheckBox> |
||||
|
<CheckBox IsChecked="{Binding AutoScrollToSelectedItem}">AutoScrollToSelectedItem</CheckBox> |
||||
|
</StackPanel> |
||||
|
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="4"> |
||||
|
<Button Command="{Binding AddItemCommand}">Add</Button> |
||||
|
<Button Command="{Binding RemoveItemCommand}">Remove</Button> |
||||
|
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button> |
||||
|
</StackPanel> |
||||
|
<ListBox Items="{Binding Items}" |
||||
|
Selection="{Binding Selection}" |
||||
|
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}" |
||||
|
SelectionMode="{Binding SelectionMode}"/> |
||||
|
</DockPanel> |
||||
</UserControl> |
</UserControl> |
||||
|
|||||
@ -0,0 +1,8 @@ |
|||||
|
<Application |
||||
|
xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="Sandbox.App"> |
||||
|
<Application.Styles> |
||||
|
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml"/> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Sandbox |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
public override void OnFrameworkInitializationCompleted() |
||||
|
{ |
||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) |
||||
|
{ |
||||
|
desktopLifetime.MainWindow = new MainWindow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' |
||||
|
x:Class="Sandbox.MainWindow"> |
||||
|
</Window> |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Sandbox |
||||
|
{ |
||||
|
public class MainWindow : Window |
||||
|
{ |
||||
|
public MainWindow() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
this.AttachDevTools(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.ReactiveUI; |
||||
|
|
||||
|
namespace Sandbox |
||||
|
{ |
||||
|
public class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
AppBuilder.Configure<App>() |
||||
|
.UsePlatformDetect() |
||||
|
.UseReactiveUI() |
||||
|
.LogToDebug() |
||||
|
.StartWithClassicDesktopLifetime(args); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\build\SampleApp.props" /> |
||||
|
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> |
||||
|
<Import Project="..\..\build\BuildTargets.targets" /> |
||||
|
</Project> |
||||
@ -0,0 +1,273 @@ |
|||||
|
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 bool _ignoreModelChanges; |
||||
|
private bool _ignoreSelectedItemsChanges; |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
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 = true; |
||||
|
|
||||
|
using (BatchUpdate()) |
||||
|
{ |
||||
|
Clear(); |
||||
|
Add(_writableSelectedItems); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_ignoreModelChanges = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
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 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 = 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: |
||||
|
Clear(); |
||||
|
Add(_writableSelectedItems); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_ignoreModelChanges = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,283 +0,0 @@ |
|||||
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 : IDisposable |
|
||||
{ |
|
||||
private ISelectionModel _selectionModel; |
|
||||
private IList _selectedItems; |
|
||||
private bool _updatingItems; |
|
||||
private bool _updatingModel; |
|
||||
|
|
||||
public SelectedItemsSync(ISelectionModel model) |
|
||||
{ |
|
||||
_selectionModel = model ?? throw new ArgumentNullException(nameof(model)); |
|
||||
_selectedItems = new AvaloniaList<object?>(); |
|
||||
SyncSelectedItemsWithSelectionModel(); |
|
||||
SubscribeToSelectedItems(_selectedItems); |
|
||||
SubscribeToSelectionModel(model); |
|
||||
} |
|
||||
|
|
||||
public ISelectionModel SelectionModel |
|
||||
{ |
|
||||
get => _selectionModel; |
|
||||
set |
|
||||
{ |
|
||||
if (_selectionModel != value) |
|
||||
{ |
|
||||
value = value ?? throw new ArgumentNullException(nameof(value)); |
|
||||
UnsubscribeFromSelectionModel(_selectionModel); |
|
||||
_selectionModel = value; |
|
||||
SubscribeToSelectionModel(_selectionModel); |
|
||||
SyncSelectedItemsWithSelectionModel(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IList SelectedItems |
|
||||
{ |
|
||||
get => _selectedItems; |
|
||||
set |
|
||||
{ |
|
||||
value ??= new AvaloniaList<object?>(); |
|
||||
|
|
||||
if (_selectedItems != value) |
|
||||
{ |
|
||||
if (value.IsFixedSize) |
|
||||
{ |
|
||||
throw new NotSupportedException( |
|
||||
"Cannot assign fixed size selection to SelectedItems."); |
|
||||
} |
|
||||
|
|
||||
UnsubscribeFromSelectedItems(_selectedItems); |
|
||||
_selectedItems = value; |
|
||||
SubscribeToSelectedItems(_selectedItems); |
|
||||
SyncSelectionModelWithSelectedItems(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
UnsubscribeFromSelectedItems(_selectedItems); |
|
||||
UnsubscribeFromSelectionModel(_selectionModel); |
|
||||
} |
|
||||
|
|
||||
private void SyncSelectedItemsWithSelectionModel() |
|
||||
{ |
|
||||
_updatingItems = true; |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
_selectedItems.Clear(); |
|
||||
|
|
||||
if (_selectionModel.Source is object) |
|
||||
{ |
|
||||
foreach (var i in _selectionModel.SelectedItems) |
|
||||
{ |
|
||||
_selectedItems.Add(i); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
_updatingItems = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void SyncSelectionModelWithSelectedItems() |
|
||||
{ |
|
||||
_updatingModel = true; |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
if (_selectionModel.Source is object) |
|
||||
{ |
|
||||
using (_selectionModel.BatchUpdate()) |
|
||||
{ |
|
||||
SelectionModel.Clear(); |
|
||||
Add(_selectedItems); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
_updatingModel = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void SelectedItemsCollectionChanged(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(SelectionModel.Source, i); |
|
||||
|
|
||||
if (index != -1) |
|
||||
{ |
|
||||
SelectionModel.Deselect(index); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
using var operation = SelectionModel.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: |
|
||||
SelectionModel.Clear(); |
|
||||
Add(_selectedItems); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
_updatingModel = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void Add(IList newItems) |
|
||||
{ |
|
||||
foreach (var i in newItems) |
|
||||
{ |
|
||||
var index = IndexOf(SelectionModel.Source, i); |
|
||||
|
|
||||
if (index != -1) |
|
||||
{ |
|
||||
SelectionModel.Select(index); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void SelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e) |
|
||||
{ |
|
||||
if (e.PropertyName == nameof(ISelectionModel.Source)) |
|
||||
{ |
|
||||
if (_selectedItems.Count > 0) |
|
||||
{ |
|
||||
SyncSelectionModelWithSelectedItems(); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
SyncSelectedItemsWithSelectionModel(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void SelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) |
|
||||
{ |
|
||||
if (_updatingModel || _selectionModel.Source is null) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
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 void SelectionModelSourceReset(object sender, EventArgs e) |
|
||||
{ |
|
||||
SyncSelectionModelWithSelectedItems(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private void SubscribeToSelectedItems(IList selectedItems) |
|
||||
{ |
|
||||
if (selectedItems is INotifyCollectionChanged incc) |
|
||||
{ |
|
||||
incc.CollectionChanged += SelectedItemsCollectionChanged; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void SubscribeToSelectionModel(ISelectionModel model) |
|
||||
{ |
|
||||
model.PropertyChanged += SelectionModelPropertyChanged; |
|
||||
model.SelectionChanged += SelectionModelSelectionChanged; |
|
||||
model.SourceReset += SelectionModelSourceReset; |
|
||||
} |
|
||||
|
|
||||
private void UnsubscribeFromSelectedItems(IList selectedItems) |
|
||||
{ |
|
||||
if (selectedItems is INotifyCollectionChanged incc) |
|
||||
{ |
|
||||
incc.CollectionChanged -= SelectedItemsCollectionChanged; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void UnsubscribeFromSelectionModel(ISelectionModel model) |
|
||||
{ |
|
||||
model.PropertyChanged -= SelectionModelPropertyChanged; |
|
||||
model.SelectionChanged -= SelectionModelSelectionChanged; |
|
||||
model.SourceReset -= SelectionModelSourceReset; |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,254 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Specialized; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.Controls.Selection; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests.Selection |
||||
|
{ |
||||
|
public class InternalSelectionModelTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Selecting_Item_Adds_To_WritableSelectedItems() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.Select(0); |
||||
|
|
||||
|
Assert.Equal(new[] { "foo" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Selecting_Duplicate_On_Model_Adds_To_WritableSelectedItems() |
||||
|
{ |
||||
|
var target = CreateTarget(source: new[] { "foo", "bar", "baz", "foo", "bar", "baz" }); |
||||
|
|
||||
|
target.SelectRange(1, 4); |
||||
|
|
||||
|
Assert.Equal(new[] { "bar", "baz", "foo", "bar" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Deselecting_On_Model_Removes_SelectedItem() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.Deselect(1); |
||||
|
|
||||
|
Assert.Equal(new[] { "baz" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Deselecting_Duplicate_On_Model_Removes_SelectedItem() |
||||
|
{ |
||||
|
var target = CreateTarget(source: new[] { "foo", "bar", "baz", "foo", "bar", "baz" }); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.Select(4); |
||||
|
target.Deselect(4); |
||||
|
|
||||
|
Assert.Equal(new[] { "baz", "bar" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Adding_To_WritableSelectedItems_Selects_On_Model() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.WritableSelectedItems.Add("foo"); |
||||
|
|
||||
|
Assert.Equal(new[] { 0, 1, 2 }, target.SelectedIndexes); |
||||
|
Assert.Equal(new[] { "bar", "baz", "foo" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Removing_From_WritableSelectedItems_Deselects_On_Model() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.WritableSelectedItems.Remove("baz"); |
||||
|
|
||||
|
Assert.Equal(new[] { 1 }, target.SelectedIndexes); |
||||
|
Assert.Equal(new[] { "bar" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Replacing_SelectedItem_Updates_Model() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.WritableSelectedItems[0] = "foo"; |
||||
|
|
||||
|
Assert.Equal(new[] { 0, 2 }, target.SelectedIndexes); |
||||
|
Assert.Equal(new[] { "foo", "baz" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_WritableSelectedItems_Updates_Model() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.WritableSelectedItems.Clear(); |
||||
|
|
||||
|
Assert.Empty(target.SelectedIndexes); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_WritableSelectedItems_Updates_Model() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
var oldItems = target.WritableSelectedItems; |
||||
|
|
||||
|
var newItems = new AvaloniaList<string> { "foo", "baz" }; |
||||
|
target.WritableSelectedItems = newItems; |
||||
|
|
||||
|
Assert.Equal(new[] { 0, 2 }, target.SelectedIndexes); |
||||
|
Assert.Same(newItems, target.WritableSelectedItems); |
||||
|
Assert.NotSame(oldItems, target.WritableSelectedItems); |
||||
|
Assert.Equal(new[] { "foo", "baz" }, newItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Items_To_Null_Clears_Selection() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
target.WritableSelectedItems = null; |
||||
|
|
||||
|
Assert.Empty(target.SelectedIndexes); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Items_To_Null_Creates_Empty_Items() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
var oldItems = target.WritableSelectedItems; |
||||
|
|
||||
|
target.WritableSelectedItems = null; |
||||
|
|
||||
|
Assert.NotNull(target.WritableSelectedItems); |
||||
|
Assert.NotSame(oldItems, target.WritableSelectedItems); |
||||
|
Assert.IsType<AvaloniaList<object>>(target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Adds_Null_WritableSelectedItems_When_Source_Is_Null() |
||||
|
{ |
||||
|
var target = CreateTarget(nullSource: true); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
Assert.Equal(new object[] { null, null }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Updates_WritableSelectedItems_When_Source_Changes_From_Null() |
||||
|
{ |
||||
|
var target = CreateTarget(nullSource: true); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
Assert.Equal(new object[] { null, null }, target.WritableSelectedItems); |
||||
|
|
||||
|
target.Source = new[] { "foo", "bar", "baz" }; |
||||
|
Assert.Equal(new[] { "bar", "baz" }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Updates_WritableSelectedItems_When_Source_Changes_To_Null() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectRange(1, 2); |
||||
|
Assert.Equal(new[] { "bar", "baz" }, target.WritableSelectedItems); |
||||
|
|
||||
|
target.Source = null; |
||||
|
Assert.Equal(new object[] { null, null }, target.WritableSelectedItems); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void WritableSelectedItems_Can_Be_Set_Before_Source() |
||||
|
{ |
||||
|
var target = CreateTarget(nullSource: true); |
||||
|
var items = new AvaloniaList<string> { "foo", "bar", "baz" }; |
||||
|
var WritableSelectedItems = new AvaloniaList<string> { "bar" }; |
||||
|
|
||||
|
target.WritableSelectedItems = WritableSelectedItems; |
||||
|
target.Source = items; |
||||
|
|
||||
|
Assert.Equal(1, target.SelectedIndex); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Does_Not_Accept_Fixed_Size_Items() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
Assert.Throws<NotSupportedException>(() => |
||||
|
target.WritableSelectedItems = new[] { "foo", "bar", "baz" }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Restores_Selection_On_Items_Reset() |
||||
|
{ |
||||
|
var items = new ResettingCollection(new[] { "foo", "bar", "baz" }); |
||||
|
var target = CreateTarget(source: items); |
||||
|
|
||||
|
target.SelectedIndex = 1; |
||||
|
items.Reset(new[] { "baz", "foo", "bar" }); |
||||
|
|
||||
|
Assert.Equal(2, target.SelectedIndex); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Preserves_Selection_On_Source_Changed() |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
target.SelectedIndex = 1; |
||||
|
target.Source = new[] { "baz", "foo", "bar" }; |
||||
|
|
||||
|
Assert.Equal(2, target.SelectedIndex); |
||||
|
} |
||||
|
|
||||
|
private static InternalSelectionModel CreateTarget( |
||||
|
bool singleSelect = false, |
||||
|
IList source = null, |
||||
|
bool nullSource = false) |
||||
|
{ |
||||
|
source ??= !nullSource ? new[] { "foo", "bar", "baz" } : null; |
||||
|
|
||||
|
var result = new InternalSelectionModel |
||||
|
{ |
||||
|
SingleSelect = singleSelect, |
||||
|
}; |
||||
|
|
||||
|
((ISelectionModel)result).Source = source; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private class ResettingCollection : List<string>, INotifyCollectionChanged |
||||
|
{ |
||||
|
public ResettingCollection(IEnumerable<string> items) |
||||
|
{ |
||||
|
AddRange(items); |
||||
|
} |
||||
|
|
||||
|
public void Reset(IEnumerable<string> items) |
||||
|
{ |
||||
|
Clear(); |
||||
|
AddRange(items); |
||||
|
CollectionChanged?.Invoke( |
||||
|
this, |
||||
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
||||
|
} |
||||
|
|
||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,278 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Collections.Specialized; |
|
||||
using Avalonia.Collections; |
|
||||
using Avalonia.Controls.Selection; |
|
||||
using Avalonia.Controls.Utils; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Controls.UnitTests.Utils |
|
||||
{ |
|
||||
public class SelectedItemsSyncTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void Initial_Items_Are_From_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
Assert.Equal(new[] { "bar", "baz" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Selecting_On_Model_Adds_Item() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
target.SelectionModel.Select(0); |
|
||||
|
|
||||
Assert.Equal(new[] { "bar", "baz", "foo" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Selecting_Duplicate_On_Model_Adds_Item() |
|
||||
{ |
|
||||
var target = CreateTarget(new[] { "foo", "bar", "baz", "foo", "bar", "baz" }); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
target.SelectionModel.Select(4); |
|
||||
|
|
||||
Assert.Equal(new[] { "bar", "baz", "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Deselecting_On_Model_Removes_Item() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
target.SelectionModel.Deselect(1); |
|
||||
|
|
||||
Assert.Equal(new[] { "baz" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Deselecting_Duplicate_On_Model_Removes_Item() |
|
||||
{ |
|
||||
var target = CreateTarget(new[] { "foo", "bar", "baz", "foo", "bar", "baz" }); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
target.SelectionModel.Select(4); |
|
||||
target.SelectionModel.Deselect(4); |
|
||||
|
|
||||
Assert.Equal(new[] { "baz", "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Reassigning_Model_Resets_Items() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
var newModel = new SelectionModel<string> |
|
||||
{ |
|
||||
Source = (string[])target.SelectionModel.Source, |
|
||||
SingleSelect = false |
|
||||
}; |
|
||||
|
|
||||
newModel.Select(0); |
|
||||
newModel.Select(1); |
|
||||
|
|
||||
target.SelectionModel = newModel; |
|
||||
|
|
||||
Assert.Equal(new[] { "foo", "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Reassigning_Model_Tracks_New_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
var newModel = new SelectionModel<string> |
|
||||
{ |
|
||||
Source = (string[])target.SelectionModel.Source, |
|
||||
SingleSelect = false |
|
||||
}; |
|
||||
|
|
||||
target.SelectionModel = newModel; |
|
||||
|
|
||||
newModel.Select(0); |
|
||||
newModel.Select(1); |
|
||||
|
|
||||
Assert.Equal(new[] { "foo", "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Adding_To_Items_Selects_On_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
items.Add("foo"); |
|
||||
|
|
||||
Assert.Equal(new[] { 0, 1, 2 }, target.SelectionModel.SelectedIndexes); |
|
||||
Assert.Equal(new[] { "bar", "baz", "foo" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Removing_From_Items_Deselects_On_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
items.Remove("baz"); |
|
||||
|
|
||||
Assert.Equal(new[] { 1 }, target.SelectionModel.SelectedIndexes); |
|
||||
Assert.Equal(new[] { "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Replacing_Item_Updates_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
items[0] = "foo"; |
|
||||
|
|
||||
Assert.Equal(new[] { 0, 2 }, target.SelectionModel.SelectedIndexes); |
|
||||
Assert.Equal(new[] { "foo", "baz" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Clearing_Items_Updates_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
items.Clear(); |
|
||||
|
|
||||
Assert.Empty(target.SelectionModel.SelectedIndexes); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Setting_Items_Updates_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var oldItems = target.SelectedItems; |
|
||||
|
|
||||
var newItems = new AvaloniaList<string> { "foo", "baz" }; |
|
||||
target.SelectedItems = newItems; |
|
||||
|
|
||||
Assert.Equal(new[] { 0, 2 }, target.SelectionModel.SelectedIndexes); |
|
||||
Assert.Same(newItems, target.SelectedItems); |
|
||||
Assert.NotSame(oldItems, target.SelectedItems); |
|
||||
Assert.Equal(new[] { "foo", "baz" }, newItems); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Setting_Items_Subscribes_To_Model() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var items = new AvaloniaList<string> { "foo", "baz" }; |
|
||||
|
|
||||
target.SelectedItems = items; |
|
||||
target.SelectionModel.Select(1); |
|
||||
|
|
||||
Assert.Equal(new[] { "foo", "baz", "bar" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Setting_Items_To_Null_Creates_Empty_Items() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
var oldItems = target.SelectedItems; |
|
||||
|
|
||||
target.SelectedItems = null; |
|
||||
|
|
||||
var newItems = Assert.IsType<AvaloniaList<object>>(target.SelectedItems); |
|
||||
|
|
||||
Assert.NotSame(oldItems, newItems); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Handles_Null_Model_Source() |
|
||||
{ |
|
||||
var model = new SelectionModel<string> { SingleSelect = false }; |
|
||||
model.Select(1); |
|
||||
|
|
||||
var target = new SelectedItemsSync(model); |
|
||||
var items = target.SelectedItems; |
|
||||
|
|
||||
Assert.Empty(items); |
|
||||
|
|
||||
model.Select(2); |
|
||||
model.Source = new[] { "foo", "bar", "baz" }; |
|
||||
|
|
||||
Assert.Equal(new[] { "bar", "baz" }, items); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Does_Not_Accept_Fixed_Size_Items() |
|
||||
{ |
|
||||
var target = CreateTarget(); |
|
||||
|
|
||||
Assert.Throws<NotSupportedException>(() => |
|
||||
target.SelectedItems = new[] { "foo", "bar", "baz" }); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Selected_Items_Can_Be_Set_Before_SelectionModel_Source() |
|
||||
{ |
|
||||
var model = new SelectionModel<string>(); |
|
||||
var target = new SelectedItemsSync(model); |
|
||||
var items = new AvaloniaList<string> { "foo", "bar", "baz" }; |
|
||||
var selectedItems = new AvaloniaList<string> { "bar" }; |
|
||||
|
|
||||
target.SelectedItems = selectedItems; |
|
||||
model.Source = items; |
|
||||
|
|
||||
Assert.Equal(1, model.SelectedIndex); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Restores_Selection_On_Items_Reset() |
|
||||
{ |
|
||||
var items = new ResettingCollection(new[] { "foo", "bar", "baz" }); |
|
||||
var model = new SelectionModel<string> { Source = items }; |
|
||||
var target = new SelectedItemsSync(model); |
|
||||
|
|
||||
model.SelectedIndex = 1; |
|
||||
items.Reset(new[] { "baz", "foo", "bar" }); |
|
||||
|
|
||||
Assert.Equal(2, model.SelectedIndex); |
|
||||
} |
|
||||
|
|
||||
private static SelectedItemsSync CreateTarget( |
|
||||
IEnumerable<string> items = null) |
|
||||
{ |
|
||||
items ??= new[] { "foo", "bar", "baz" }; |
|
||||
|
|
||||
var model = new SelectionModel<string> { Source = items, SingleSelect = false }; |
|
||||
model.SelectRange(1, 2); |
|
||||
|
|
||||
var target = new SelectedItemsSync(model); |
|
||||
return target; |
|
||||
} |
|
||||
|
|
||||
private class ResettingCollection : List<string>, INotifyCollectionChanged |
|
||||
{ |
|
||||
public ResettingCollection(IEnumerable<string> items) |
|
||||
{ |
|
||||
AddRange(items); |
|
||||
} |
|
||||
|
|
||||
public void Reset(IEnumerable<string> items) |
|
||||
{ |
|
||||
Clear(); |
|
||||
AddRange(items); |
|
||||
CollectionChanged?.Invoke( |
|
||||
this, |
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
|
||||
} |
|
||||
|
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue