@ -6,6 +6,7 @@
using System ;
using System ;
using System.Collections.Generic ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.ComponentModel ;
using System.Linq ;
using Avalonia.Controls.Utils ;
using Avalonia.Controls.Utils ;
# nullable enable
# nullable enable
@ -19,7 +20,6 @@ namespace Avalonia.Controls
private IReadOnlyList < IndexPath > ? _ selectedIndicesCached ;
private IReadOnlyList < IndexPath > ? _ selectedIndicesCached ;
private IReadOnlyList < object? > ? _ selectedItemsCached ;
private IReadOnlyList < object? > ? _ selectedItemsCached ;
private SelectionModelChildrenRequestedEventArgs ? _ childrenRequestedEventArgs ;
private SelectionModelChildrenRequestedEventArgs ? _ childrenRequestedEventArgs ;
private SelectionModelSelectionChangedEventArgs ? _ selectionChangedEventArgs ;
public event EventHandler < SelectionModelChildrenRequestedEventArgs > ? ChildrenRequested ;
public event EventHandler < SelectionModelChildrenRequestedEventArgs > ? ChildrenRequested ;
public event PropertyChangedEventHandler ? PropertyChanged ;
public event PropertyChangedEventHandler ? PropertyChanged ;
@ -36,9 +36,12 @@ namespace Avalonia.Controls
get = > _ rootNode ? . Source ;
get = > _ rootNode ? . Source ;
set
set
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
using ( var operation = new Operation ( this ) )
{
ClearSelection ( resetAnchor : true ) ;
}
_ rootNode . Source = value ;
_ rootNode . Source = value ;
OnSelectionChanged ( ) ;
RaisePropertyChanged ( "Source" ) ;
RaisePropertyChanged ( "Source" ) ;
}
}
}
}
@ -55,12 +58,13 @@ namespace Avalonia.Controls
if ( value & & selectedIndices ! = null & & selectedIndices . Count > 0 )
if ( value & & selectedIndices ! = null & & selectedIndices . Count > 0 )
{
{
using var operation = new Operation ( this ) ;
// We want to be single select, so make sure there is only
// We want to be single select, so make sure there is only
// one selected item.
// one selected item.
var firstSelectionIndexPath = selectedIndices [ 0 ] ;
var firstSelectionIndexPath = selectedIndices [ 0 ] ;
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
ClearSelection ( resetAnchor : true ) ;
SelectWithPathImpl ( firstSelectionIndexPath , select : true , raiseSelectionChanged : false ) ;
SelectWithPathImpl ( firstSelectionIndexPath , select : true ) ;
// Setting SelectedIndex will raise SelectionChanged event.
SelectedIndex = firstSelectionIndexPath ;
SelectedIndex = firstSelectionIndexPath ;
}
}
@ -131,9 +135,9 @@ namespace Avalonia.Controls
if ( ! isSelected . HasValue | | ! isSelected . Value )
if ( ! isSelected . HasValue | | ! isSelected . Value )
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
using var operation = new Operation ( this ) ;
SelectWithPathImpl ( value , select : true , raiseSelectionChanged : fals e) ;
ClearSelection ( resetAnchor : tru e) ;
OnSelectionChanged ( ) ;
SelectWithPathImpl ( value , select : true ) ;
}
}
}
}
}
}
@ -289,7 +293,7 @@ namespace Avalonia.Controls
public void Dispose ( )
public void Dispose ( )
{
{
ClearSelection ( resetAnchor : false , raiseSelectionChanged : false ) ;
ClearSelection ( resetAnchor : false ) ;
_ rootNode ? . Dispose ( ) ;
_ rootNode ? . Dispose ( ) ;
_ selectedIndicesCached = null ;
_ selectedIndicesCached = null ;
_ selectedItemsCached = null ;
_ selectedItemsCached = null ;
@ -299,17 +303,41 @@ namespace Avalonia.Controls
public void SetAnchorIndex ( int groupIndex , int index ) = > AnchorIndex = new IndexPath ( groupIndex , index ) ;
public void SetAnchorIndex ( int groupIndex , int index ) = > AnchorIndex = new IndexPath ( groupIndex , index ) ;
public void Select ( int index ) = > SelectImpl ( index , select : true ) ;
public void Select ( int index )
{
using var operation = new Operation ( this ) ;
SelectImpl ( index , select : true ) ;
}
public void Select ( int groupIndex , int itemIndex ) = > SelectWithGroupImpl ( groupIndex , itemIndex , select : true ) ;
public void Select ( int groupIndex , int itemIndex )
{
using var operation = new Operation ( this ) ;
SelectWithGroupImpl ( groupIndex , itemIndex , select : true ) ;
}
public void SelectAt ( IndexPath index ) = > SelectWithPathImpl ( index , select : true , raiseSelectionChanged : true ) ;
public void SelectAt ( IndexPath index )
{
using var operation = new Operation ( this ) ;
SelectWithPathImpl ( index , select : true ) ;
}
public void Deselect ( int index ) = > SelectImpl ( index , select : false ) ;
public void Deselect ( int index )
{
using var operation = new Operation ( this ) ;
SelectImpl ( index , select : false ) ;
}
public void Deselect ( int groupIndex , int itemIndex ) = > SelectWithGroupImpl ( groupIndex , itemIndex , select : false ) ;
public void Deselect ( int groupIndex , int itemIndex )
{
using var operation = new Operation ( this ) ;
SelectWithGroupImpl ( groupIndex , itemIndex , select : false ) ;
}
public void DeselectAt ( IndexPath index ) = > SelectWithPathImpl ( index , select : false , raiseSelectionChanged : true ) ;
public void DeselectAt ( IndexPath index )
{
using var operation = new Operation ( this ) ;
SelectWithPathImpl ( index , select : false ) ;
}
public bool? IsSelected ( int index )
public bool? IsSelected ( int index )
{
{
@ -383,46 +411,56 @@ namespace Avalonia.Controls
public void SelectRangeFromAnchor ( int index )
public void SelectRangeFromAnchor ( int index )
{
{
using var operation = new Operation ( this ) ;
SelectRangeFromAnchorImpl ( index , select : true ) ;
SelectRangeFromAnchorImpl ( index , select : true ) ;
}
}
public void SelectRangeFromAnchor ( int endGroupIndex , int endItemIndex )
public void SelectRangeFromAnchor ( int endGroupIndex , int endItemIndex )
{
{
using var operation = new Operation ( this ) ;
SelectRangeFromAnchorWithGroupImpl ( endGroupIndex , endItemIndex , select : true ) ;
SelectRangeFromAnchorWithGroupImpl ( endGroupIndex , endItemIndex , select : true ) ;
}
}
public void SelectRangeFromAnchorTo ( IndexPath index )
public void SelectRangeFromAnchorTo ( IndexPath index )
{
{
using var operation = new Operation ( this ) ;
SelectRangeImpl ( AnchorIndex , index , select : true ) ;
SelectRangeImpl ( AnchorIndex , index , select : true ) ;
}
}
public void DeselectRangeFromAnchor ( int index )
public void DeselectRangeFromAnchor ( int index )
{
{
using var operation = new Operation ( this ) ;
SelectRangeFromAnchorImpl ( index , select : false ) ;
SelectRangeFromAnchorImpl ( index , select : false ) ;
}
}
public void DeselectRangeFromAnchor ( int endGroupIndex , int endItemIndex )
public void DeselectRangeFromAnchor ( int endGroupIndex , int endItemIndex )
{
{
using var operation = new Operation ( this ) ;
SelectRangeFromAnchorWithGroupImpl ( endGroupIndex , endItemIndex , false /* select */ ) ;
SelectRangeFromAnchorWithGroupImpl ( endGroupIndex , endItemIndex , false /* select */ ) ;
}
}
public void DeselectRangeFromAnchorTo ( IndexPath index )
public void DeselectRangeFromAnchorTo ( IndexPath index )
{
{
using var operation = new Operation ( this ) ;
SelectRangeImpl ( AnchorIndex , index , select : false ) ;
SelectRangeImpl ( AnchorIndex , index , select : false ) ;
}
}
public void SelectRange ( IndexPath start , IndexPath end )
public void SelectRange ( IndexPath start , IndexPath end )
{
{
using var operation = new Operation ( this ) ;
SelectRangeImpl ( start , end , select : true ) ;
SelectRangeImpl ( start , end , select : true ) ;
}
}
public void DeselectRange ( IndexPath start , IndexPath end )
public void DeselectRange ( IndexPath start , IndexPath end )
{
{
using var operation = new Operation ( this ) ;
SelectRangeImpl ( start , end , select : false ) ;
SelectRangeImpl ( start , end , select : false ) ;
}
}
public void SelectAll ( )
public void SelectAll ( )
{
{
using var operation = new Operation ( this ) ;
SelectionTreeHelper . Traverse (
SelectionTreeHelper . Traverse (
_ rootNode ,
_ rootNode ,
realizeChildren : true ,
realizeChildren : true ,
@ -433,13 +471,12 @@ namespace Avalonia.Controls
info . Node . SelectAll ( ) ;
info . Node . SelectAll ( ) ;
}
}
} ) ;
} ) ;
OnSelectionChanged ( ) ;
}
}
public void ClearSelection ( )
public void ClearSelection ( )
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : true ) ;
using var operation = new Operation ( this ) ;
ClearSelection ( resetAnchor : true ) ;
}
}
protected void OnPropertyChanged ( string propertyName )
protected void OnPropertyChanged ( string propertyName )
@ -452,9 +489,15 @@ namespace Avalonia.Controls
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( propertyName ) ) ;
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( propertyName ) ) ;
}
}
public void OnSelectionInvalidatedDueToCollectionChange ( )
public void OnSelectionInvalidatedDueToCollectionChange (
IEnumerable < object > ? removedItems )
{
{
OnSelectionChanged ( ) ;
var e = new SelectionModelSelectionChangedEventArgs (
Enumerable . Empty < IndexPath > ( ) ,
Enumerable . Empty < IndexPath > ( ) ,
removedItems ? ? Enumerable . Empty < object > ( ) ,
Enumerable . Empty < object > ( ) ) ;
OnSelectionChanged ( e ) ;
}
}
internal object? ResolvePath ( object data , SelectionNode sourceNode )
internal object? ResolvePath ( object data , SelectionNode sourceNode )
@ -496,7 +539,7 @@ namespace Avalonia.Controls
return resolved ;
return resolved ;
}
}
private void ClearSelection ( bool resetAnchor , bool raiseSelectionChanged )
private void ClearSelection ( bool resetAnchor )
{
{
SelectionTreeHelper . Traverse (
SelectionTreeHelper . Traverse (
_ rootNode ,
_ rootNode ,
@ -507,27 +550,17 @@ namespace Avalonia.Controls
{
{
AnchorIndex = default ;
AnchorIndex = default ;
}
}
if ( raiseSelectionChanged )
{
OnSelectionChanged ( ) ;
}
}
}
private void OnSelectionChanged ( )
private void OnSelectionChanged ( SelectionModelSelectionChangedEventArgs ? e = null )
{
{
_ selectedIndicesCached = null ;
_ selectedIndicesCached = null ;
_ selectedItemsCached = null ;
_ selectedItemsCached = null ;
// Raise SelectionChanged event
// Raise SelectionChanged event
if ( S electionChanged ! = null )
if ( e ! = null )
{
{
if ( _ selectionChangedEventArgs = = null )
SelectionChanged ? . Invoke ( this , e ) ;
{
_ selectionChangedEventArgs = new SelectionModelSelectionChangedEventArgs ( ) ;
}
SelectionChanged ( this , _ selectionChangedEventArgs ) ;
}
}
RaisePropertyChanged ( nameof ( SelectedIndex ) ) ;
RaisePropertyChanged ( nameof ( SelectedIndex ) ) ;
@ -544,7 +577,7 @@ namespace Avalonia.Controls
{
{
if ( _ singleSelect )
if ( _ singleSelect )
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
ClearSelection ( resetAnchor : true ) ;
}
}
var selected = _ rootNode . Select ( index , select ) ;
var selected = _ rootNode . Select ( index , select ) ;
@ -553,15 +586,13 @@ namespace Avalonia.Controls
{
{
AnchorIndex = new IndexPath ( index ) ;
AnchorIndex = new IndexPath ( index ) ;
}
}
OnSelectionChanged ( ) ;
}
}
private void SelectWithGroupImpl ( int groupIndex , int itemIndex , bool select )
private void SelectWithGroupImpl ( int groupIndex , int itemIndex , bool select )
{
{
if ( _ singleSelect )
if ( _ singleSelect )
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
ClearSelection ( resetAnchor : true ) ;
}
}
var childNode = _ rootNode . GetAt ( groupIndex , realizeChild : true ) ;
var childNode = _ rootNode . GetAt ( groupIndex , realizeChild : true ) ;
@ -571,17 +602,15 @@ namespace Avalonia.Controls
{
{
AnchorIndex = new IndexPath ( groupIndex , itemIndex ) ;
AnchorIndex = new IndexPath ( groupIndex , itemIndex ) ;
}
}
OnSelectionChanged ( ) ;
}
}
private void SelectWithPathImpl ( IndexPath index , bool select , bool raiseSelectionChanged )
private void SelectWithPathImpl ( IndexPath index , bool select )
{
{
bool selected = false ;
bool selected = false ;
if ( _ singleSelect )
if ( _ singleSelect )
{
{
ClearSelection ( resetAnchor : true , raiseSelectionChanged : false ) ;
ClearSelection ( resetAnchor : true ) ;
}
}
SelectionTreeHelper . TraverseIndexPath (
SelectionTreeHelper . TraverseIndexPath (
@ -601,11 +630,6 @@ namespace Avalonia.Controls
{
{
AnchorIndex = index ;
AnchorIndex = index ;
}
}
if ( raiseSelectionChanged )
{
OnSelectionChanged ( ) ;
}
}
}
private void SelectRangeFromAnchorImpl ( int index , bool select )
private void SelectRangeFromAnchorImpl ( int index , bool select )
@ -618,12 +642,7 @@ namespace Avalonia.Controls
anchorIndex = anchor . GetAt ( 0 ) ;
anchorIndex = anchor . GetAt ( 0 ) ;
}
}
bool selected = _ rootNode . SelectRange ( new IndexRange ( anchorIndex , index ) , select ) ;
_ rootNode . SelectRange ( new IndexRange ( anchorIndex , index ) , select ) ;
if ( selected )
{
OnSelectionChanged ( ) ;
}
}
}
private void SelectRangeFromAnchorWithGroupImpl ( int endGroupIndex , int endItemIndex , bool select )
private void SelectRangeFromAnchorWithGroupImpl ( int endGroupIndex , int endItemIndex , bool select )
@ -650,18 +669,12 @@ namespace Avalonia.Controls
endItemIndex = temp ;
endItemIndex = temp ;
}
}
var selected = false ;
for ( int groupIdx = startGroupIndex ; groupIdx < = endGroupIndex ; groupIdx + + )
for ( int groupIdx = startGroupIndex ; groupIdx < = endGroupIndex ; groupIdx + + )
{
{
var groupNode = _ rootNode . GetAt ( groupIdx , realizeChild : true ) ! ;
var groupNode = _ rootNode . GetAt ( groupIdx , realizeChild : true ) ! ;
int startIndex = groupIdx = = startGroupIndex ? startItemIndex : 0 ;
int startIndex = groupIdx = = startGroupIndex ? startItemIndex : 0 ;
int endIndex = groupIdx = = endGroupIndex ? endItemIndex : groupNode . DataCount - 1 ;
int endIndex = groupIdx = = endGroupIndex ? endItemIndex : groupNode . DataCount - 1 ;
selected | = groupNode . SelectRange ( new IndexRange ( startIndex , endIndex ) , select ) ;
groupNode . SelectRange ( new IndexRange ( startIndex , endIndex ) , select ) ;
}
if ( selected )
{
OnSelectionChanged ( ) ;
}
}
}
}
@ -691,8 +704,55 @@ namespace Avalonia.Controls
info . ParentNode ! . Select ( info . Path . GetAt ( info . Path . GetSize ( ) - 1 ) , select ) ;
info . ParentNode ! . Select ( info . Path . GetAt ( info . Path . GetSize ( ) - 1 ) , select ) ;
}
}
} ) ;
} ) ;
}
private void BeginOperation ( )
{
if ( SelectionChanged ! = null )
{
_ rootNode . BeginOperation ( ) ;
}
}
private void EndOperation ( )
{
static IEnumerable < T > ? Concat < T > ( IEnumerable < T > ? a , IEnumerable < T > b )
{
return a = = null ? b : a . Concat ( b ) ;
}
OnSelectionChanged ( ) ;
SelectionModelSelectionChangedEventArgs ? e = null ;
if ( SelectionChanged ! = null )
{
IEnumerable < IndexPath > ? selectedIndices = null ;
IEnumerable < IndexPath > ? deselectedIndices = null ;
IEnumerable < object > ? selectedItems = null ;
IEnumerable < object > ? deselectedItems = null ;
foreach ( var changes in _ rootNode . EndOperation ( ) )
{
if ( changes . HasChanges )
{
selectedIndices = Concat ( selectedIndices , changes . SelectedIndices ) ;
deselectedIndices = Concat ( deselectedIndices , changes . DeselectedIndices ) ;
selectedItems = Concat ( selectedItems , changes . SelectedItems ) ;
deselectedItems = Concat ( deselectedItems , changes . DeselectedItems ) ;
}
}
if ( selectedIndices ! = null | | deselectedIndices ! = null | |
selectedItems ! = null | | deselectedItems ! = null )
{
e = new SelectionModelSelectionChangedEventArgs (
deselectedIndices ? ? Enumerable . Empty < IndexPath > ( ) ,
selectedIndices ? ? Enumerable . Empty < IndexPath > ( ) ,
deselectedItems ? ? Enumerable . Empty < object > ( ) ,
selectedItems ? ? Enumerable . Empty < object > ( ) ) ;
}
}
OnSelectionChanged ( e ) ;
}
}
internal class SelectedItemInfo
internal class SelectedItemInfo
@ -706,5 +766,12 @@ namespace Avalonia.Controls
public SelectionNode Node { get ; }
public SelectionNode Node { get ; }
public IndexPath Path { get ; }
public IndexPath Path { get ; }
}
}
private struct Operation : IDisposable
{
private readonly SelectionModel _ manager ;
public Operation ( SelectionModel manager ) = > ( _ manager = manager ) . BeginOperation ( ) ;
public void Dispose ( ) = > _ manager . EndOperation ( ) ;
}
}
}
}
}