diff --git a/demo/Ursa.Demo/Pages/EnumSelectorDemo.axaml b/demo/Ursa.Demo/Pages/EnumSelectorDemo.axaml index 2105b49..b35256d 100644 --- a/demo/Ursa.Demo/Pages/EnumSelectorDemo.axaml +++ b/demo/Ursa.Demo/Pages/EnumSelectorDemo.axaml @@ -1,19 +1,30 @@ - + - - - - - + + + + + + diff --git a/demo/Ursa.Demo/ViewModels/EnumSelectorDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/EnumSelectorDemoViewModel.cs index 20d3297..06be6d4 100644 --- a/demo/Ursa.Demo/ViewModels/EnumSelectorDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/EnumSelectorDemoViewModel.cs @@ -1,5 +1,7 @@ using System; using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Windows.Input; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Data; @@ -7,6 +9,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; namespace Ursa.Demo.ViewModels; @@ -50,6 +53,15 @@ public class EnumSelectorDemoViewModel: ObservableObject typeof(Key), typeof(KeyModifiers), typeof(RoutingStrategies), + typeof(CustomEnum), }; } +} + +public enum CustomEnum +{ + [Description("是")] + Yes, + [Description("否")] + No, } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/EnumSelector.axaml b/src/Ursa.Themes.Semi/Controls/EnumSelector.axaml index 064d1a7..b468e53 100644 --- a/src/Ursa.Themes.Semi/Controls/EnumSelector.axaml +++ b/src/Ursa.Themes.Semi/Controls/EnumSelector.axaml @@ -4,10 +4,29 @@ xmlns:u="https://irihi.tech/ursa"> + - + + + diff --git a/src/Ursa/Controls/EnumSelector/EnumSelector.cs b/src/Ursa/Controls/EnumSelector/EnumSelector.cs index 4acdfcb..821e6fe 100644 --- a/src/Ursa/Controls/EnumSelector/EnumSelector.cs +++ b/src/Ursa/Controls/EnumSelector/EnumSelector.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -5,6 +6,12 @@ using Avalonia.Data; namespace Ursa.Controls; +public class EnumItemTuple +{ + public string? DisplayName { get; set; } + public object? Value { get; set; } +} + public class EnumSelector: TemplatedControl { public static readonly StyledProperty EnumTypeProperty = AvaloniaProperty.Register( @@ -23,45 +30,129 @@ public class EnumSelector: TemplatedControl } public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register( - nameof(Value), defaultBindingMode: BindingMode.TwoWay); + nameof(Value), defaultBindingMode: BindingMode.TwoWay, coerce:OnValueCoerce); + + private static object? OnValueCoerce(AvaloniaObject o, object? value) + { + if (o is not EnumSelector selector) return null; + if (value is null) return null; + if (value.GetType() != selector.EnumType) return null; + var first = selector.Values.FirstOrDefault(a => Equals(a.Value, value)); + if (first is null) return null; + return value; + } + public object? Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); } + + private EnumItemTuple? _selectedValue; + + public static readonly DirectProperty SelectedValueProperty = AvaloniaProperty.RegisterDirect( + nameof(SelectedValue), o => o.SelectedValue, (o, v) => o.SelectedValue = v); + + public EnumItemTuple? SelectedValue + { + get => _selectedValue; + private set => SetAndRaise(SelectedValueProperty, ref _selectedValue, value); + } - public static readonly DirectProperty?> ValuesProperty = AvaloniaProperty.RegisterDirect?>( + public static readonly DirectProperty?> ValuesProperty = AvaloniaProperty.RegisterDirect?>( nameof(Values), o => o.Values); - private IList? _values; - internal IList? Values + private IList? _values; + internal IList? Values { get => _values; private set => SetAndRaise(ValuesProperty, ref _values, value); } - + + public static readonly StyledProperty DisplayDescriptionProperty = AvaloniaProperty.Register( + nameof(DisplayDescription)); + + public bool DisplayDescription + { + get => GetValue(DisplayDescriptionProperty); + set => SetValue(DisplayDescriptionProperty, value); + } static EnumSelector() { EnumTypeProperty.Changed.AddClassHandler((o, e) => o.OnTypeChanged(e)); + SelectedValueProperty.Changed.AddClassHandler((o, e) => o.OnSelectedValueChanged(e)); + ValueProperty.Changed.AddClassHandler((o, e) => o.OnValueChanged(e)); } + private void OnValueChanged(AvaloniaPropertyChangedEventArgs args) + { + if (_updateFromComboBox) return; + var newValue = args.NewValue.Value; + if (newValue is null) + { + SetCurrentValue(SelectedValueProperty, null); + } + else + { + if (newValue.GetType() != EnumType) + { + SetCurrentValue(SelectedValueProperty, null); + } + var tuple = Values?.FirstOrDefault(x => Equals(x.Value, newValue)); + SetCurrentValue(SelectedValueProperty, tuple); + } + } + + private bool _updateFromComboBox; + + private void OnSelectedValueChanged(AvaloniaPropertyChangedEventArgs args) + { + _updateFromComboBox = true; + var newValue = args.NewValue.Value; + SetCurrentValue(ValueProperty, newValue?.Value); + _updateFromComboBox = false; + } + + + private void OnTypeChanged(AvaloniaPropertyChangedEventArgs args) { + Values?.Clear(); var newType = args.GetNewValue(); if (newType is null || !newType.IsEnum) { return; } + Values = GenerateItemTuple(); + } - var values = Enum.GetValues(newType); - List list = new(); + private List GenerateItemTuple() + { + if (EnumType is null) return new List(); + var values = Enum.GetValues(EnumType); + List list = new(); + var fields = EnumType.GetFields(); foreach (var value in values) { - if (value.GetType() == newType) - list.Add(value); + if (value.GetType() == EnumType) + { + var displayName = value.ToString(); + var field = EnumType.GetField(displayName); + var description = field?.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault(); + if (description is not null) + { + displayName = ((DescriptionAttribute) description).Description; + } + list.Add(new EnumItemTuple() + { + DisplayName = displayName, + Value = value + }); + } } - Values = list; + + return list; } } \ No newline at end of file