Merge pull request #588 from irihitech/selection

Fix various issues for SelectionList
This commit is contained in:
Dong Bin
2025-02-27 15:54:32 +08:00
committed by GitHub
3 changed files with 52 additions and 37 deletions

View File

@@ -12,10 +12,14 @@
x:DataType="vm:SelectionListDemoViewModel" x:DataType="vm:SelectionListDemoViewModel"
mc:Ignorable="d"> mc:Ignorable="d">
<StackPanel HorizontalAlignment="Left"> <StackPanel HorizontalAlignment="Left">
<u:SelectionList ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"> <u:SelectionList
Width="600"
HorizontalAlignment="Center"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<u:SelectionList.ItemsPanel> <u:SelectionList.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel HorizontalAlignment="Center" Orientation="Horizontal" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</u:SelectionList.ItemsPanel> </u:SelectionList.ItemsPanel>
</u:SelectionList> </u:SelectionList>
@@ -36,9 +40,9 @@
<DataTemplate> <DataTemplate>
<Panel Height="40"> <Panel Height="40">
<TextBlock <TextBlock
Classes.Active="{Binding $parent[u:SelectionListItem].IsSelected, Mode=OneWay}"
Margin="8,0" Margin="8,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Classes.Active="{Binding $parent[u:SelectionListItem].IsSelected, Mode=OneWay}"
Text="{Binding}"> Text="{Binding}">
<TextBlock.Styles> <TextBlock.Styles>
<Style Selector="TextBlock.Active"> <Style Selector="TextBlock.Active">
@@ -50,6 +54,14 @@
</DataTemplate> </DataTemplate>
</u:SelectionList.ItemTemplate> </u:SelectionList.ItemTemplate>
</u:SelectionList> </u:SelectionList>
<Button Command="{Binding Clear}">Clear</Button> <SplitButton Content="Button With Selection">
<SplitButton.Flyout>
<Flyout>
<u:SelectionList ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
</Flyout>
</SplitButton.Flyout>
</SplitButton>
<Button Margin="0 12" Command="{Binding Clear}">Clear</Button>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -14,33 +14,34 @@ using Irihi.Avalonia.Shared.Helpers;
namespace Ursa.Controls; namespace Ursa.Controls;
[TemplatePart(PART_Indicator, typeof(ContentPresenter))] [TemplatePart(PART_Indicator, typeof(ContentPresenter))]
public class SelectionList: SelectingItemsControl public class SelectionList : SelectingItemsControl
{ {
public const string PART_Indicator = "PART_Indicator"; public const string PART_Indicator = "PART_Indicator";
private static readonly FuncTemplate<Panel?> DefaultPanel = new(() => new StackPanel()); private static readonly FuncTemplate<Panel?> DefaultPanel = new(() => new StackPanel());
public static readonly StyledProperty<Control?> IndicatorProperty =
AvaloniaProperty.Register<SelectionList, Control?>(
nameof(Indicator));
private ImplicitAnimationCollection? _implicitAnimations; private ImplicitAnimationCollection? _implicitAnimations;
private ContentPresenter? _indicator; private ContentPresenter? _indicator;
public static readonly StyledProperty<Control?> IndicatorProperty = AvaloniaProperty.Register<SelectionList, Control?>( static SelectionList()
nameof(Indicator)); {
SelectionModeProperty.OverrideMetadata<SelectionList>(
new StyledPropertyMetadata<SelectionMode>(
SelectionMode.Single,
coerce: (_, _) => SelectionMode.Single)
);
SelectedItemProperty.Changed.AddClassHandler<SelectionList, object?>((list, args) =>
list.OnSelectedItemChanged(args));
}
public Control? Indicator public Control? Indicator
{ {
get => GetValue(IndicatorProperty); get => GetValue(IndicatorProperty);
set => SetValue(IndicatorProperty, value); set => SetValue(IndicatorProperty, value);
} }
static SelectionList()
{
SelectionModeProperty.OverrideMetadata<SelectionList>(
new StyledPropertyMetadata<SelectionMode>(
defaultValue: SelectionMode.Single,
coerce: (_, _) => SelectionMode.Single)
);
SelectedItemProperty.Changed.AddClassHandler<SelectionList, object?>((list, args) =>
list.OnSelectedItemChanged(args));
}
private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs<object?> args) private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs<object?> args)
{ {
@@ -50,31 +51,36 @@ public class SelectionList: SelectingItemsControl
OpacityProperty.SetValue(0d, _indicator); OpacityProperty.SetValue(0d, _indicator);
return; return;
} }
var container = ContainerFromItem(newValue); var container = ContainerFromItem(newValue);
if (container is null) if (container is null)
{ {
OpacityProperty.SetValue(0d, _indicator); OpacityProperty.SetValue(0d, _indicator);
return; return;
} }
OpacityProperty.SetValue(1d, _indicator); OpacityProperty.SetValue(1d, _indicator);
InvalidateMeasure(); InvalidateMeasure();
InvalidateArrange(); // InvalidateArrange();
} }
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
var size = base.ArrangeOverride(finalSize); var size = base.ArrangeOverride(finalSize);
if(_indicator is not null && SelectedItem is not null) if (_indicator is not null && SelectedItem is not null)
{ {
var container = ContainerFromItem(SelectedItem); var container = ContainerFromItem(SelectedItem);
if (container is null) return size; if (container is null) return size;
_indicator.Arrange(container.Bounds); var bounds = container.Bounds;
if (ItemsPanelRoot?.Bounds.Position is { } p) bounds = bounds.Translate(p);
_indicator.Arrange(bounds);
} }
else else
{ {
// This is a hack. The indicator is not visible, so we arrange it to a 1x1 rectangle // This is a hack. The indicator is not visible, so we arrange it to a 1x1 rectangle
_indicator?.Arrange(new Rect(new Point(), new Size(1, 1))); _indicator?.Arrange(new Rect(new Point(), new Size(1, 1)));
} }
return size; return size;
} }
@@ -91,19 +97,21 @@ public class SelectionList: SelectingItemsControl
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_indicator= e.NameScope.Find<ContentPresenter>(PART_Indicator); _indicator = e.NameScope.Find<ContentPresenter>(PART_Indicator);
EnsureIndicatorAnimation();
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)
{ {
base.OnLoaded(e); base.OnLoaded(e);
if(_indicator is not null && SelectedItem is not null) EnsureIndicatorAnimation();
if (_indicator is not null && SelectedItem is not null)
{ {
var container = ContainerFromItem(SelectedItem); var container = ContainerFromItem(SelectedItem);
if (container is null) return; if (container is null) return;
_indicator.Opacity = 1; _indicator.Opacity = 1;
_indicator.Arrange(container.Bounds); var bounds = container.Bounds;
if (ItemsPanelRoot?.Bounds.Position is { } p) bounds = bounds.Translate(p);
_indicator.Arrange(bounds);
} }
} }
@@ -113,10 +121,7 @@ public class SelectionList: SelectingItemsControl
{ {
_indicator.Opacity = 0; _indicator.Opacity = 0;
SetUpAnimation(); SetUpAnimation();
if (ElementComposition.GetElementVisual(_indicator) is { } v) if (ElementComposition.GetElementVisual(_indicator) is { } v) v.ImplicitAnimations = _implicitAnimations;
{
v.ImplicitAnimations = _implicitAnimations;
}
} }
} }
@@ -126,7 +131,7 @@ public class SelectionList: SelectingItemsControl
Selection.Clear(); Selection.Clear();
Selection.Select(index); Selection.Select(index);
} }
private void SetUpAnimation() private void SetUpAnimation()
{ {
if (_implicitAnimations != null) return; if (_implicitAnimations != null) return;
@@ -151,15 +156,13 @@ public class SelectionList: SelectingItemsControl
_implicitAnimations[nameof(CompositionVisual.Size)] = sizeAnimation; _implicitAnimations[nameof(CompositionVisual.Size)] = sizeAnimation;
_implicitAnimations[nameof(CompositionVisual.Opacity)] = opacityAnimation; _implicitAnimations[nameof(CompositionVisual.Opacity)] = opacityAnimation;
} }
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration; var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
if (e.Key.ToNavigationDirection() is { } direction && direction.IsDirectional()) if (e.Key.ToNavigationDirection() is { } direction && direction.IsDirectional())
{
e.Handled |= MoveSelection(direction, WrapSelection); e.Handled |= MoveSelection(direction, WrapSelection);
}
base.OnKeyDown(e); base.OnKeyDown(e);
} }
} }