Browse Source

Implement SelectedValue

pull/10180/head
amwx 4 years ago
parent
commit
3913fb333e
  1. 251
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

251
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -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;
} }
} }
} }

Loading…
Cancel
Save