@ -1,6 +1,5 @@
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Collections.Specialized ;
using System.ComponentModel ;
using System.Diagnostics.CodeAnalysis ;
@ -8,7 +7,6 @@ using System.Linq;
using Avalonia.Controls.Selection ;
using Avalonia.Data ;
using Avalonia.Input ;
using Avalonia.Input.Platform ;
using Avalonia.Interactivity ;
using Avalonia.Metadata ;
using Avalonia.Threading ;
@ -187,14 +185,21 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public int SelectedIndex
{
get = >
get
{
// When a Begin/EndInit/DataContext update is in place we return the value to be
// updated here, even though it's not yet active and the property changed notification
// has not yet been raised. If we don't do this then the old value will be written back
// to the source when two-way bound, and the update value will be lost.
_ updateState ? . SelectedIndex . HasValue = = true ?
_ updateState . SelectedIndex . Value :
Selection . SelectedIndex ;
if ( _ updateState is not null )
{
return _ updateState . SelectedIndex . HasValue ?
_ updateState . SelectedIndex . Value :
TryGetExistingSelection ( ) ? . SelectedIndex ? ? - 1 ;
}
return Selection . SelectedIndex ;
}
set
{
if ( _ updateState is object )
@ -213,11 +218,18 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public object? SelectedItem
{
get = >
// See SelectedIndex setter for more information.
_ updateState ? . SelectedItem . HasValue = = true ?
_ updateState . SelectedItem . Value :
Selection . SelectedItem ;
get
{
// See SelectedIndex getter for more information.
if ( _ updateState is not null )
{
return _ updateState . SelectedItem . HasValue ?
_ updateState . SelectedItem . Value :
TryGetExistingSelection ( ) ? . SelectedItem ;
}
return Selection . SelectedItem ;
}
set
{
if ( _ updateState is object )
@ -270,6 +282,7 @@ namespace Avalonia.Controls.Primitives
{
return _ updateState . SelectedItems . Value ;
}
else if ( Selection is InternalSelectionModel ism )
{
var result = ism . WritableSelectedItems ;
@ -456,10 +469,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree ( VisualTreeAttachmentEventArgs e )
{
base . OnAttachedToVisualTree ( e ) ;
if ( Selection ? . AnchorIndex is int index )
{
AutoScrollToSelectedItemIfNecessary ( index ) ;
}
AutoScrollToSelectedItemIfNecessary ( GetAnchorIndex ( ) ) ;
}
/// <inheritdoc />
@ -470,10 +481,8 @@ namespace Avalonia.Controls.Primitives
void ExecuteScrollWhenLayoutUpdated ( object? sender , EventArgs e )
{
LayoutUpdated - = ExecuteScrollWhenLayoutUpdated ;
if ( Selection ? . AnchorIndex is int index )
{
AutoScrollToSelectedItemIfNecessary ( index ) ;
}
AutoScrollToSelectedItemIfNecessary ( GetAnchorIndex ( ) ) ;
}
if ( AutoScrollToSelectedItem )
@ -482,6 +491,15 @@ namespace Avalonia.Controls.Primitives
}
}
internal int GetAnchorIndex ( )
{
var selection = _ updateState is not null ? TryGetExistingSelection ( ) : Selection ;
return selection ? . AnchorIndex ? ? - 1 ;
}
private ISelectionModel ? TryGetExistingSelection ( )
= > _ updateState ? . Selection . HasValue = = true ? _ updateState . Selection . Value : _ selection ;
protected internal override void PrepareContainerForItemOverride ( Control container , object? item , int index )
{
// Ensure that the selection model is created at this point so that accessing it in
@ -634,10 +652,7 @@ namespace Avalonia.Controls.Primitives
if ( change . Property = = AutoScrollToSelectedItemProperty )
{
if ( Selection ? . AnchorIndex is int index )
{
AutoScrollToSelectedItemIfNecessary ( index ) ;
}
AutoScrollToSelectedItemIfNecessary ( GetAnchorIndex ( ) ) ;
}
else if ( change . Property = = SelectionModeProperty & & _ selection is object )
{
@ -671,7 +686,7 @@ namespace Avalonia.Controls.Primitives
return ;
}
var value = change . GetNewValue < IBinding > ( ) ;
var value = change . GetNewValue < IBinding ? > ( ) ;
if ( value is null )
{
// Clearing SelectedValueBinding makes the SelectedValue the item itself
@ -921,11 +936,10 @@ namespace Avalonia.Controls.Primitives
if ( e . PropertyName = = nameof ( ISelectionModel . AnchorIndex ) )
{
_ hasScrolledToSelectedItem = false ;
if ( Selection ? . AnchorIndex is int index )
{
KeyboardNavigation . SetTabOnceActiveElement ( this , ContainerFromIndex ( index ) ) ;
AutoScrollToSelectedItemIfNecessary ( index ) ;
}
var anchorIndex = GetAnchorIndex ( ) ;
KeyboardNavigation . SetTabOnceActiveElement ( this , ContainerFromIndex ( anchorIndex ) ) ;
AutoScrollToSelectedItemIfNecessary ( anchorIndex ) ;
}
else if ( e . PropertyName = = nameof ( ISelectionModel . SelectedIndex ) & & _ oldSelectedIndex ! = SelectedIndex )
{
@ -1279,9 +1293,17 @@ namespace Avalonia.Controls.Primitives
state . SelectedItem = item ;
}
// SelectedIndex vs SelectedItem:
// - If only one has a value, use it
// - If both have a value, prefer the one having a "non-empty" value, e.g. not -1 nor null
// - If both have a "non-empty" value, prefer the index
if ( state . SelectedIndex . HasValue )
{
SelectedIndex = state . SelectedIndex . Value ;
var selectedIndex = state . SelectedIndex . Value ;
if ( selectedIndex > = 0 | | ! state . SelectedItem . HasValue )
SelectedIndex = selectedIndex ;
else
SelectedItem = state . SelectedItem . Value ;
}
else if ( state . SelectedItem . HasValue )
{
@ -1338,39 +1360,12 @@ namespace Avalonia.Controls.Primitives
// - Both the old and new SelectionModels have the incorrect Source
private class UpdateState
{
private Optional < int > _ selectedIndex ;
private Optional < object? > _ selectedItem ;
private Optional < object? > _ selectedValue ;
public int UpdateCount { get ; set ; }
public Optional < ISelectionModel > Selection { get ; set ; }
public Optional < IList ? > SelectedItems { get ; set ; }
public Optional < int > SelectedIndex
{
get = > _ selectedIndex ;
set
{
_ selectedIndex = value ;
_ selectedItem = default ;
}
}
public Optional < object? > SelectedItem
{
get = > _ selectedItem ;
set
{
_ selectedItem = value ;
_ selectedIndex = default ;
}
}
public Optional < object? > SelectedValue
{
get = > _ selectedValue ;
set = > _ selectedValue = value ;
}
public Optional < int > SelectedIndex { get ; set ; }
public Optional < object? > SelectedItem { get ; set ; }
public Optional < object? > SelectedValue { get ; set ; }
}
/// <summary>