Explorar o código

TextConverter property for finer control over NumericUpDown text display (#8839)

* Added TextConverter property to NumericUpDown

* Added a hex NumericUpDown sample to ControlCatalog

* Requested changes to conversion calls

* Fixed NumericUpDown::OnTextConverterChanged logic
hacklex %!s(int64=3) %!d(string=hai) anos
pai
achega
0b01a51c9e

+ 34 - 0
samples/ControlCatalog/Converter/HexConverter.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Data.Converters;
+
+namespace ControlCatalog.Converter;
+
+public class HexConverter : IValueConverter
+{
+    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        var str = value?.ToString();
+        if (str == null)
+            return AvaloniaProperty.UnsetValue;
+        if (int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int x))
+            return (decimal)x;
+        return AvaloniaProperty.UnsetValue;
+
+    }
+
+    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        try
+        {
+            if (value is decimal d)
+                return ((int)d).ToString("X8");
+            return AvaloniaProperty.UnsetValue;
+        }
+        catch
+        {
+            return AvaloniaProperty.UnsetValue;
+        }
+    }
+}

+ 12 - 0
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -1,6 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:sys="clr-namespace:System;assembly=netstandard"
+             xmlns:converter="clr-namespace:ControlCatalog.Converter"
              x:Class="ControlCatalog.Pages.NumericUpDownPage">
   <StackPanel Orientation="Vertical" Spacing="4"
               MaxWidth="800">
@@ -97,6 +98,17 @@
           </DataValidationErrors.Error>
         </NumericUpDown>
       </StackPanel>
+
+      <StackPanel Orientation="Vertical" Margin="10">
+        <Label Target="HexUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown in HEX mode:</Label>
+        <NumericUpDown x:Name="HexUpDown" Value="0"
+                       VerticalAlignment="Center">
+          <NumericUpDown.TextConverter>
+            <converter:HexConverter></converter:HexConverter>
+          </NumericUpDown.TextConverter>
+        </NumericUpDown>
+        
+      </StackPanel>
     </WrapPanel>
 
   </StackPanel>

+ 61 - 0
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Data;
+using Avalonia.Data.Converters;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
@@ -96,6 +97,13 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<NumericUpDown, string?>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
                 defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
+        /// <summary>
+        /// Defines the <see cref="TextConverter"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, IValueConverter?> TextConverterProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, IValueConverter?>(nameof(TextConverter),
+                updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
+
         /// <summary>
         /// Defines the <see cref="Value"/> property.
         /// </summary>
@@ -125,6 +133,7 @@ namespace Avalonia.Controls
 
         private decimal? _value;
         private string? _text;
+        private IValueConverter? _textConverter;
         private bool _internalValueSet;
         private bool _clipValueToMinMax;
         private bool _isSyncingTextAndValueProperties;
@@ -235,6 +244,8 @@ namespace Avalonia.Controls
 
         /// <summary>
         /// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
+        /// Note that Hex style does not work with decimal. 
+        /// For hexadecimal display, use <see cref="TextConverter"/>.
         /// </summary>
         public NumberStyles ParsingNumberStyle
         {
@@ -251,6 +262,17 @@ namespace Avalonia.Controls
             set { SetAndRaise(TextProperty, ref _text, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the custom bidirectional Text-Value converter.
+        /// Non-null converter overrides <see cref="ParsingNumberStyle"/>, providing finer control over 
+        /// string representation of the underlying value.
+        /// </summary>
+        public IValueConverter? TextConverter
+        {
+            get { return _textConverter; }
+            set { SetAndRaise(TextConverterProperty, ref _textConverter, value); }
+        }
+
         /// <summary>
         /// Gets or sets the value.
         /// </summary>
@@ -319,6 +341,7 @@ namespace Avalonia.Controls
             MaximumProperty.Changed.Subscribe(OnMaximumChanged);
             MinimumProperty.Changed.Subscribe(OnMinimumChanged);
             TextProperty.Changed.Subscribe(OnTextChanged);
+            TextConverterProperty.Changed.Subscribe(OnTextConverterChanged);
             ValueProperty.Changed.Subscribe(OnValueChanged);
         }
 
@@ -485,6 +508,19 @@ namespace Avalonia.Controls
                 SyncTextAndValueProperties(true, Text);
             }
         }
+        
+        /// <summary>
+        /// Called when the <see cref="Text"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnTextConverterChanged(IValueConverter? oldValue, IValueConverter? newValue)
+        {
+            if (IsInitialized)
+            {
+                SyncTextAndValueProperties(false, null);
+            }
+        }
 
         /// <summary>
         /// Called when the <see cref="Value"/> property value changed.
@@ -612,6 +648,10 @@ namespace Avalonia.Controls
         /// <returns></returns>
         private string? ConvertValueToText()
         {
+            if (TextConverter != null)
+            {
+                return TextConverter.ConvertBack(Value, typeof(string), null, CultureInfo.CurrentCulture)?.ToString();
+            }
             //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
             if (FormatString.Contains("{0"))
             {
@@ -788,6 +828,21 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Called when the <see cref="TextConverter"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnTextConverterChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (IValueConverter?)e.OldValue;
+                var newValue = (IValueConverter?)e.NewValue;
+                upDown.OnTextConverterChanged(oldValue, newValue);
+            }
+        }
+
+
         /// <summary>
         /// Called when the <see cref="Value"/> property value changed.
         /// </summary>
@@ -1012,6 +1067,12 @@ namespace Avalonia.Controls
                 return null;
             }
             
+            if (TextConverter != null)
+            {
+                var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);
+                return (decimal?)valueFromText;
+            }
+
             if (IsPercent(FormatString))
             {
                 result = ParsePercent(text, NumberFormat);