|
|
@ -7,6 +7,7 @@ using System.Linq; |
|
|
using System.Xml.Linq; |
|
|
using System.Xml.Linq; |
|
|
using Avalonia.Controls.Generators; |
|
|
using Avalonia.Controls.Generators; |
|
|
using Avalonia.Controls.Selection; |
|
|
using Avalonia.Controls.Selection; |
|
|
|
|
|
using Avalonia.Controls.Utils; |
|
|
using Avalonia.Data; |
|
|
using Avalonia.Data; |
|
|
using Avalonia.Input; |
|
|
using Avalonia.Input; |
|
|
using Avalonia.Input.Platform; |
|
|
using Avalonia.Input.Platform; |
|
|
@ -66,6 +67,19 @@ namespace Avalonia.Controls.Primitives |
|
|
(o, v) => o.SelectedItem = v, |
|
|
(o, v) => o.SelectedItem = v, |
|
|
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); |
|
|
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Defines the <see cref="SelectedValue"/> property
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly StyledProperty<object?> SelectedValueProperty = |
|
|
|
|
|
AvaloniaProperty.Register<SelectingItemsControl, object?>(nameof(SelectedValue), |
|
|
|
|
|
defaultBindingMode: BindingMode.TwoWay); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Defines the <see cref="SelectedValueBinding"/> property
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly StyledProperty<IBinding?> SelectedValueBindingProperty = |
|
|
|
|
|
AvaloniaProperty.Register<SelectingItemsControl, IBinding?>(nameof(SelectedValueBinding)); |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="SelectedItems"/> property.
|
|
|
/// Defines the <see cref="SelectedItems"/> property.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -129,6 +143,8 @@ namespace Avalonia.Controls.Primitives |
|
|
private bool _ignoreContainerSelectionChanged; |
|
|
private bool _ignoreContainerSelectionChanged; |
|
|
private UpdateState? _updateState; |
|
|
private UpdateState? _updateState; |
|
|
private bool _hasScrolledToSelectedItem; |
|
|
private bool _hasScrolledToSelectedItem; |
|
|
|
|
|
private BindingHelper? _bindingHelper; |
|
|
|
|
|
private bool _isSelectionChangeActive; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
|
|
|
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
|
|
|
@ -209,6 +225,19 @@ namespace Avalonia.Controls.Primitives |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
[AssignBinding] |
|
|
|
|
|
public IBinding? SelectedValueBinding |
|
|
|
|
|
{ |
|
|
|
|
|
get => GetValue(SelectedValueBindingProperty); |
|
|
|
|
|
set => SetValue(SelectedValueBindingProperty, value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public object? SelectedValue |
|
|
|
|
|
{ |
|
|
|
|
|
get => GetValue(SelectedValueProperty); |
|
|
|
|
|
set => SetValue(SelectedValueProperty, value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Gets or sets the selected items.
|
|
|
/// Gets or sets the selected items.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -609,6 +638,60 @@ namespace Avalonia.Controls.Primitives |
|
|
{ |
|
|
{ |
|
|
WrapFocus = WrapSelection; |
|
|
WrapFocus = WrapSelection; |
|
|
} |
|
|
} |
|
|
|
|
|
else if (change.Property == SelectedValueProperty) |
|
|
|
|
|
{ |
|
|
|
|
|
if (_isSelectionChangeActive) |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
if (_updateState is not null) |
|
|
|
|
|
{ |
|
|
|
|
|
_updateState.SelectedValue = change.NewValue; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
SelectItemWithValue(change.NewValue); |
|
|
|
|
|
} |
|
|
|
|
|
else if (change.Property == SelectedValueBindingProperty) |
|
|
|
|
|
{ |
|
|
|
|
|
var idx = SelectedIndex; |
|
|
|
|
|
|
|
|
|
|
|
// If no selection is active, don't do anything as SelectedValue is already null
|
|
|
|
|
|
if (idx == -1) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var value = change.GetNewValue<IBinding>(); |
|
|
|
|
|
if (value is null) |
|
|
|
|
|
{ |
|
|
|
|
|
// Clearing SelectedValueBinding makes the SelectedValue the item itself
|
|
|
|
|
|
SelectedValue = SelectedItem; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var selectedItem = SelectedItem; |
|
|
|
|
|
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = true; |
|
|
|
|
|
|
|
|
|
|
|
if (_bindingHelper is null) |
|
|
|
|
|
{ |
|
|
|
|
|
_bindingHelper = new BindingHelper(value); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
_bindingHelper.UpdateBinding(value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Re-evaluate SelectedValue with the new binding
|
|
|
|
|
|
SelectedValue = _bindingHelper.Evaluate(selectedItem); |
|
|
|
|
|
} |
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -815,6 +898,10 @@ namespace Avalonia.Controls.Primitives |
|
|
new BindingValue<IList?>(SelectedItems)); |
|
|
new BindingValue<IList?>(SelectedItems)); |
|
|
_oldSelectedItems = SelectedItems; |
|
|
_oldSelectedItems = SelectedItems; |
|
|
} |
|
|
} |
|
|
|
|
|
else if (e.PropertyName == nameof(ISelectionModel.Source)) |
|
|
|
|
|
{ |
|
|
|
|
|
ClearValue(SelectedValueProperty); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -845,6 +932,11 @@ namespace Avalonia.Controls.Primitives |
|
|
Mark(i, false); |
|
|
Mark(i, false); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!_isSelectionChangeActive) |
|
|
|
|
|
{ |
|
|
|
|
|
UpdateSelectedValueFromItem(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var route = BuildEventRoute(SelectionChangedEvent); |
|
|
var route = BuildEventRoute(SelectionChangedEvent); |
|
|
|
|
|
|
|
|
if (route.HasHandlers) |
|
|
if (route.HasHandlers) |
|
|
@ -871,6 +963,109 @@ namespace Avalonia.Controls.Primitives |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void SelectItemWithValue(object? value) |
|
|
|
|
|
{ |
|
|
|
|
|
if (ItemCount == 0 || _isSelectionChangeActive) |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = true; |
|
|
|
|
|
var si = FindItemWithValue(value); |
|
|
|
|
|
if (si != AvaloniaProperty.UnsetValue) |
|
|
|
|
|
{ |
|
|
|
|
|
SelectedItem = si; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
SelectedItem = null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private object FindItemWithValue(object? value) |
|
|
|
|
|
{ |
|
|
|
|
|
if (ItemCount == 0 || value is null) |
|
|
|
|
|
{ |
|
|
|
|
|
return AvaloniaProperty.UnsetValue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var items = Items; |
|
|
|
|
|
var binding = SelectedValueBinding; |
|
|
|
|
|
|
|
|
|
|
|
if (binding is null) |
|
|
|
|
|
{ |
|
|
|
|
|
// No SelectedValueBinding set, SelectedValue is the item itself
|
|
|
|
|
|
// Still verify the value passed in is in the Items list
|
|
|
|
|
|
var index = items!.IndexOf(value); |
|
|
|
|
|
|
|
|
|
|
|
if (index >= 0) |
|
|
|
|
|
{ |
|
|
|
|
|
return value; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
return AvaloniaProperty.UnsetValue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_bindingHelper ??= new BindingHelper(binding); |
|
|
|
|
|
|
|
|
|
|
|
// Matching UWP behavior, if duplicates are present, return the first item matching
|
|
|
|
|
|
// the SelectedValue provided
|
|
|
|
|
|
foreach (var item in items!) |
|
|
|
|
|
{ |
|
|
|
|
|
var itemValue = _bindingHelper.Evaluate(item); |
|
|
|
|
|
|
|
|
|
|
|
if (itemValue.Equals(value)) |
|
|
|
|
|
{ |
|
|
|
|
|
return item; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return AvaloniaProperty.UnsetValue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void UpdateSelectedValueFromItem() |
|
|
|
|
|
{ |
|
|
|
|
|
if (_isSelectionChangeActive) |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
var binding = SelectedValueBinding; |
|
|
|
|
|
var item = SelectedItem; |
|
|
|
|
|
|
|
|
|
|
|
if (binding is null || item is null) |
|
|
|
|
|
{ |
|
|
|
|
|
// No SelectedValueBinding, SelectedValue is Item itself
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = true; |
|
|
|
|
|
SelectedValue = item; |
|
|
|
|
|
} |
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_bindingHelper ??= new BindingHelper(binding); |
|
|
|
|
|
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = true; |
|
|
|
|
|
SelectedValue = _bindingHelper.Evaluate(item); |
|
|
|
|
|
} |
|
|
|
|
|
finally |
|
|
|
|
|
{ |
|
|
|
|
|
_isSelectionChangeActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private void AutoScrollToSelectedItemIfNecessary() |
|
|
private void AutoScrollToSelectedItemIfNecessary() |
|
|
{ |
|
|
{ |
|
|
if (AutoScrollToSelectedItem && |
|
|
if (AutoScrollToSelectedItem && |
|
|
@ -1037,6 +1232,13 @@ namespace Avalonia.Controls.Primitives |
|
|
Selection.Clear(); |
|
|
Selection.Clear(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (state.SelectedValue.HasValue) |
|
|
|
|
|
{ |
|
|
|
|
|
var item = FindItemWithValue(state.SelectedValue.Value); |
|
|
|
|
|
if (item != AvaloniaProperty.UnsetValue) |
|
|
|
|
|
state.SelectedItem = item; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (state.SelectedIndex.HasValue) |
|
|
if (state.SelectedIndex.HasValue) |
|
|
{ |
|
|
{ |
|
|
SelectedIndex = state.SelectedIndex.Value; |
|
|
SelectedIndex = state.SelectedIndex.Value; |
|
|
@ -1098,6 +1300,7 @@ namespace Avalonia.Controls.Primitives |
|
|
{ |
|
|
{ |
|
|
private Optional<int> _selectedIndex; |
|
|
private Optional<int> _selectedIndex; |
|
|
private Optional<object?> _selectedItem; |
|
|
private Optional<object?> _selectedItem; |
|
|
|
|
|
private Optional<object?> _selectedValue; |
|
|
|
|
|
|
|
|
public int UpdateCount { get; set; } |
|
|
public int UpdateCount { get; set; } |
|
|
public Optional<ISelectionModel> Selection { get; set; } |
|
|
public Optional<ISelectionModel> Selection { get; set; } |
|
|
@ -1122,6 +1325,54 @@ namespace Avalonia.Controls.Primitives |
|
|
_selectedIndex = default; |
|
|
_selectedIndex = default; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Optional<object?> SelectedValue |
|
|
|
|
|
{ |
|
|
|
|
|
get => _selectedValue; |
|
|
|
|
|
set |
|
|
|
|
|
{ |
|
|
|
|
|
_selectedValue = value; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Helper class for evaluating a binding from an Item and IBinding instance
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private class BindingHelper : StyledElement |
|
|
|
|
|
{ |
|
|
|
|
|
public BindingHelper(IBinding binding) |
|
|
|
|
|
{ |
|
|
|
|
|
UpdateBinding(binding); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<object> ValueProperty = |
|
|
|
|
|
AvaloniaProperty.Register<BindingHelper, object>("Value"); |
|
|
|
|
|
|
|
|
|
|
|
public object Evaluate(object? dataContext) |
|
|
|
|
|
{ |
|
|
|
|
|
dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); |
|
|
|
|
|
|
|
|
|
|
|
// Only update the DataContext if necessary
|
|
|
|
|
|
if (!dataContext.Equals(DataContext)) |
|
|
|
|
|
DataContext = dataContext; |
|
|
|
|
|
|
|
|
|
|
|
return GetValue(ValueProperty); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void UpdateBinding(IBinding binding) |
|
|
|
|
|
{ |
|
|
|
|
|
_lastBinding = binding; |
|
|
|
|
|
var ib = binding.Initiate(this, ValueProperty); |
|
|
|
|
|
if (ib is null) |
|
|
|
|
|
{ |
|
|
|
|
|
throw new InvalidOperationException("Unable to create binding"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BindingOperations.Apply(this, ValueProperty, ib, null); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private IBinding? _lastBinding; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|