feat: finish int implementation.
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<u:IntUpDown />
|
<u:IntUpDown Name="input" Step="1" Value="2" EmptyInputValue="0" />
|
||||||
|
<TextBlock Text="{Binding #input.Value}" ></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -3,9 +3,36 @@
|
|||||||
xmlns:u="https://irihi.tech/ursa">
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
<!-- Add Resources Here -->
|
<!-- Add Resources Here -->
|
||||||
<ControlTheme x:Key="{x:Type u:NumericUpDown}" TargetType="{x:Type u:NumericUpDown}">
|
<ControlTheme x:Key="{x:Type u:NumericUpDown}" TargetType="{x:Type u:NumericUpDown}">
|
||||||
|
<Setter Property="NumericUpDown.VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="NumericUpDown.CornerRadius" Value="{DynamicResource NumericUpDownCornerRadius}" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate TargetType="u:NumericUpDown">
|
||||||
<Rectangle Fill="Red" Width="100" Height="100"></Rectangle>
|
<DataValidationErrors>
|
||||||
|
<ButtonSpinner
|
||||||
|
Name="PART_Spinner"
|
||||||
|
MinWidth="0"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
AllowSpin="{TemplateBinding AllowSpin}"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
>
|
||||||
|
<TextBox
|
||||||
|
Name="PART_TextBox"
|
||||||
|
Height="{TemplateBinding Height}"
|
||||||
|
MinHeight="{DynamicResource NumericUpDownWrapperDefaultHeight}"
|
||||||
|
AcceptsReturn="False"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}"
|
||||||
|
DataValidationErrors.Errors="{ReflectionBinding $parent[NumericUpDown].(DataValidationErrors.Errors)}"
|
||||||
|
FontSize="{TemplateBinding FontSize}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
IsReadOnly="{TemplateBinding IsReadOnly}"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
Theme="{DynamicResource NonErrorTextBox}"
|
||||||
|
Watermark="{TemplateBinding Watermark}" />
|
||||||
|
</ButtonSpinner>
|
||||||
|
</DataValidationErrors>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|||||||
@@ -1,48 +1,32 @@
|
|||||||
using Avalonia.Utilities;
|
using System.Globalization;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Utilities;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
public class IntUpDown: NumericUpDownBase<int>
|
public class IntUpDown : NumericUpDownBase<int>
|
||||||
{
|
{
|
||||||
protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown);
|
protected override Type StyleKeyOverride { get; } = typeof(NumericUpDown);
|
||||||
|
|
||||||
static IntUpDown()
|
static IntUpDown()
|
||||||
{
|
{
|
||||||
MaximumProperty.OverrideDefaultValue<IntUpDown>(int.MaxValue);
|
MaximumProperty.OverrideDefaultValue<IntUpDown>(int.MaxValue);
|
||||||
|
MinimumProperty.OverrideDefaultValue<IntUpDown>(int.MinValue);
|
||||||
StepProperty.OverrideDefaultValue<IntUpDown>(1);
|
StepProperty.OverrideDefaultValue<IntUpDown>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Increase()
|
protected override bool ParseText(string? text, out int? number)
|
||||||
{
|
{
|
||||||
Value += Step;
|
var result = int.TryParse(text, ParsingNumberStyle, NumberFormat, out var value);
|
||||||
|
number = value;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Decrease()
|
protected override string? ValueToString(int? value) => value?.ToString(FormatString, NumberFormat);
|
||||||
{
|
|
||||||
Value -= Step;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateTextToValue(string x)
|
|
||||||
{
|
|
||||||
if (int.TryParse(x, out var value))
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CommitInput()
|
protected override int Zero => 0;
|
||||||
{
|
|
||||||
// throw new NotImplementedException();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SyncTextAndValue()
|
protected override int? Add(int? a, int? b) => a + b;
|
||||||
{
|
|
||||||
// throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override int Clamp()
|
protected override int? Minus(int? a, int? b) => a - b;
|
||||||
{
|
|
||||||
return MathUtilities.Clamp(Value, Maximum, Minimum);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using Avalonia;
|
using System.Globalization;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Metadata;
|
using Avalonia.Controls.Metadata;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ public abstract class NumericUpDown : TemplatedControl
|
|||||||
protected internal Panel? _dragPanel;
|
protected internal Panel? _dragPanel;
|
||||||
|
|
||||||
private Point? _point;
|
private Point? _point;
|
||||||
private bool _updateFromTextInput;
|
protected internal bool _updateFromTextInput;
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> TextEditableProperty = AvaloniaProperty.Register<NumericUpDown, bool>(
|
public static readonly StyledProperty<bool> TextEditableProperty = AvaloniaProperty.Register<NumericUpDown, bool>(
|
||||||
nameof(TextEditable), defaultValue: true);
|
nameof(TextEditable), defaultValue: true);
|
||||||
@@ -58,8 +61,94 @@ public abstract class NumericUpDown : TemplatedControl
|
|||||||
get => GetValue(WatermarkProperty);
|
get => GetValue(WatermarkProperty);
|
||||||
set => SetValue(WatermarkProperty, value);
|
set => SetValue(WatermarkProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<NumberFormatInfo?> NumberFormatProperty = AvaloniaProperty.Register<NumericUpDown, NumberFormatInfo?>(
|
||||||
|
nameof(NumberFormat), defaultValue: NumberFormatInfo.CurrentInfo);
|
||||||
|
|
||||||
|
public NumberFormatInfo? NumberFormat
|
||||||
|
{
|
||||||
|
get => GetValue(NumberFormatProperty);
|
||||||
|
set => SetValue(NumberFormatProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> FormatStringProperty = AvaloniaProperty.Register<NumericUpDown, string>(
|
||||||
|
nameof(FormatString), string.Empty);
|
||||||
|
|
||||||
|
public string FormatString
|
||||||
|
{
|
||||||
|
get => GetValue(FormatStringProperty);
|
||||||
|
set => SetValue(FormatStringProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<NumberStyles> ParsingNumberStyleProperty = AvaloniaProperty.Register<NumericUpDown, NumberStyles>(
|
||||||
|
nameof(ParsingNumberStyle));
|
||||||
|
|
||||||
|
public NumberStyles ParsingNumberStyle
|
||||||
|
{
|
||||||
|
get => GetValue(ParsingNumberStyleProperty);
|
||||||
|
set => SetValue(ParsingNumberStyleProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IValueConverter?> TextConverterProperty = AvaloniaProperty.Register<NumericUpDown, IValueConverter?>(
|
||||||
|
nameof(TextConverter));
|
||||||
|
|
||||||
|
public IValueConverter? TextConverter
|
||||||
|
{
|
||||||
|
get => GetValue(TextConverterProperty);
|
||||||
|
set => SetValue(TextConverterProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> AllowSpinProperty = AvaloniaProperty.Register<NumericUpDown, bool>(
|
||||||
|
nameof(AllowSpin), true);
|
||||||
|
|
||||||
|
public bool AllowSpin
|
||||||
|
{
|
||||||
|
get => GetValue(AllowSpinProperty);
|
||||||
|
set => SetValue(AllowSpinProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> AllowMouseWheelProperty = AvaloniaProperty.Register<NumericUpDown, bool>(
|
||||||
|
nameof(AllowMouseWheel));
|
||||||
|
|
||||||
|
public bool AllowMouseWheel
|
||||||
|
{
|
||||||
|
get => GetValue(AllowMouseWheelProperty);
|
||||||
|
set => SetValue(AllowMouseWheelProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<SpinEventArgs>? Spinned;
|
||||||
|
|
||||||
|
static NumericUpDown()
|
||||||
|
{
|
||||||
|
NumberFormatProperty.Changed.AddClassHandler<NumericUpDown>((o, e) => o.OnFormatChange(e));
|
||||||
|
FormatStringProperty.Changed.AddClassHandler<NumericUpDown>((o, e) => o.OnFormatChange(e));
|
||||||
|
IsReadOnlyProperty.Changed.AddClassHandler<NumericUpDown>((o,e)=>o.ChangeToSetSpinDirection(e));
|
||||||
|
TextConverterProperty.Changed.AddClassHandler<NumericUpDown>((o, e) => o.OnFormatChange(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ChangeToSetSpinDirection(AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs, bool afterInitialization = false)
|
||||||
|
{
|
||||||
|
if (afterInitialization)
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
{
|
||||||
|
SetValidSpinDirection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetValidSpinDirection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnFormatChange(AvaloniaPropertyChangedEventArgs arg)
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
{
|
||||||
|
SyncTextAndValue(false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
@@ -70,6 +159,7 @@ public abstract class NumericUpDown : TemplatedControl
|
|||||||
if(_textBox is not null)
|
if(_textBox is not null)
|
||||||
{
|
{
|
||||||
_textBox.TextChanged -= OnTextChange;
|
_textBox.TextChanged -= OnTextChange;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dragPanel is not null)
|
if (_dragPanel is not null)
|
||||||
@@ -101,7 +191,7 @@ public abstract class NumericUpDown : TemplatedControl
|
|||||||
|
|
||||||
protected override void OnLostFocus(RoutedEventArgs e)
|
protected override void OnLostFocus(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
CommitInput();
|
CommitInput(true);
|
||||||
base.OnLostFocus(e);
|
base.OnLostFocus(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,46 +224,65 @@ public abstract class NumericUpDown : TemplatedControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
private void OnTextChange(object sender, TextChangedEventArgs e)
|
private void OnTextChange(object sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
_updateFromTextInput = true;
|
_updateFromTextInput = true;
|
||||||
UpdateTextToValue(_textBox?.Text ?? string.Empty);
|
SyncTextAndValue();
|
||||||
_updateFromTextInput = false;
|
_updateFromTextInput = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSpin(object sender, SpinEventArgs e)
|
private void OnSpin(object sender, SpinEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Direction == SpinDirection.Increase)
|
if (AllowSpin && !IsReadOnly)
|
||||||
{
|
{
|
||||||
Increase();
|
var spin = !e.UsingMouseWheel;
|
||||||
}
|
spin |= _textBox is { IsFocused: true };
|
||||||
else
|
if (spin)
|
||||||
{
|
{
|
||||||
Decrease();
|
e.Handled = true;
|
||||||
|
var handler = Spinned;
|
||||||
|
handler?.Invoke(this, e);
|
||||||
|
if (e.Direction == SpinDirection.Increase)
|
||||||
|
{
|
||||||
|
Increase();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Decrease();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void SetValidSpinDirection();
|
||||||
|
|
||||||
protected abstract void Increase();
|
protected abstract void Increase();
|
||||||
protected abstract void Decrease();
|
protected abstract void Decrease();
|
||||||
protected abstract void UpdateTextToValue(string x);
|
|
||||||
protected abstract bool CommitInput();
|
protected virtual bool CommitInput(bool forceTextUpdate = false)
|
||||||
protected abstract void SyncTextAndValue();
|
{
|
||||||
|
return SyncTextAndValue(true, _textBox?.Text, forceTextUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool SyncTextAndValue(bool fromTextToValue = false, string? text = null,
|
||||||
|
bool forceTextUpdate = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class NumericUpDownBase<T>: NumericUpDown where T: struct, IComparable<T>
|
public abstract class NumericUpDownBase<T>: NumericUpDown where T: struct, IComparable<T>
|
||||||
{
|
{
|
||||||
public static readonly StyledProperty<T> ValueProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
public static readonly StyledProperty<T?> ValueProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T?>(
|
||||||
nameof(Value));
|
nameof(Value));
|
||||||
|
|
||||||
public T Value
|
public T? Value
|
||||||
{
|
{
|
||||||
get => GetValue(ValueProperty);
|
get => GetValue(ValueProperty);
|
||||||
set => SetValue(ValueProperty, value);
|
set => SetValue(ValueProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<T> MaximumProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
public static readonly StyledProperty<T> MaximumProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
||||||
nameof(Maximum));
|
nameof(Maximum), coerce: CoerceMaximum);
|
||||||
|
|
||||||
public T Maximum
|
public T Maximum
|
||||||
{
|
{
|
||||||
get => GetValue(MaximumProperty);
|
get => GetValue(MaximumProperty);
|
||||||
@@ -181,13 +290,54 @@ public abstract class NumericUpDownBase<T>: NumericUpDown where T: struct, IComp
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<T> MinimumProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
public static readonly StyledProperty<T> MinimumProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
||||||
nameof(Minimum));
|
nameof(Minimum), coerce: CoerceMinimum);
|
||||||
|
|
||||||
public T Minimum
|
public T Minimum
|
||||||
{
|
{
|
||||||
get => GetValue(MinimumProperty);
|
get => GetValue(MinimumProperty);
|
||||||
set => SetValue(MinimumProperty, value);
|
set => SetValue(MinimumProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Max and Min Coerce
|
||||||
|
private static T CoerceMaximum(AvaloniaObject instance, T value)
|
||||||
|
{
|
||||||
|
if (instance is NumericUpDownBase<T> n)
|
||||||
|
{
|
||||||
|
return n.CoerceMaximum(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T CoerceMaximum(T value)
|
||||||
|
{
|
||||||
|
if (value.CompareTo(Minimum) < 0)
|
||||||
|
{
|
||||||
|
return Minimum;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T CoerceMinimum(AvaloniaObject instance, T value)
|
||||||
|
{
|
||||||
|
if (instance is NumericUpDownBase<T> n)
|
||||||
|
{
|
||||||
|
return n.CoerceMinimum(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T CoerceMinimum(T value)
|
||||||
|
{
|
||||||
|
if (value.CompareTo(Maximum) > 0)
|
||||||
|
{
|
||||||
|
return Maximum;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public static readonly StyledProperty<T> StepProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
public static readonly StyledProperty<T> StepProperty = AvaloniaProperty.Register<NumericUpDownBase<T>, T>(
|
||||||
nameof(Step));
|
nameof(Step));
|
||||||
@@ -198,5 +348,245 @@ public abstract class NumericUpDownBase<T>: NumericUpDown where T: struct, IComp
|
|||||||
set => SetValue(StepProperty, value);
|
set => SetValue(StepProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract T Clamp();
|
public static readonly StyledProperty<T?> EmptyInputValueProperty =
|
||||||
|
AvaloniaProperty.Register<NumericUpDownBase<T>, T?>(
|
||||||
|
nameof(EmptyInputValue), defaultValue: null);
|
||||||
|
|
||||||
|
public T? EmptyInputValue
|
||||||
|
{
|
||||||
|
get => GetValue(EmptyInputValueProperty);
|
||||||
|
set => SetValue(EmptyInputValueProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the <see cref="ValueChanged"/> event.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly RoutedEvent<ValueChangedEventArgs<T>> ValueChangedEvent =
|
||||||
|
RoutedEvent.Register<NumericUpDown, ValueChangedEventArgs<T>>(nameof(ValueChanged), RoutingStrategies.Bubble);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the <see cref="Value"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ValueChangedEventArgs<T>>? ValueChanged
|
||||||
|
{
|
||||||
|
add => AddHandler(ValueChangedEvent, value);
|
||||||
|
remove => RemoveHandler(ValueChangedEvent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NumericUpDownBase()
|
||||||
|
{
|
||||||
|
StepProperty.Changed.AddClassHandler<NumericUpDownBase<T>>((o, e) => o.ChangeToSetSpinDirection(e));
|
||||||
|
MaximumProperty.Changed.AddClassHandler<NumericUpDownBase<T>>((o, e) => o.OnConstraintChanged(e));
|
||||||
|
MinimumProperty.Changed.AddClassHandler<NumericUpDownBase<T>>((o, e) => o.OnConstraintChanged(e));
|
||||||
|
ValueProperty.Changed.AddClassHandler<NumericUpDownBase<T>>((o, e) => o.OnValueChanged(e) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConstraintChanged(AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs)
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
{
|
||||||
|
SetValidSpinDirection();
|
||||||
|
}
|
||||||
|
if (Value.HasValue)
|
||||||
|
{
|
||||||
|
SetCurrentValue(ValueProperty, Clamp(Value, Maximum, Minimum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
{
|
||||||
|
SyncTextAndValue(false, null, true);
|
||||||
|
}
|
||||||
|
SetValidSpinDirection();
|
||||||
|
T? oldValue = args.GetOldValue<T?>();
|
||||||
|
T? newValue = args.GetNewValue<T?>();
|
||||||
|
var e = new ValueChangedEventArgs<T>(ValueChangedEvent, oldValue, newValue);
|
||||||
|
RaiseEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
if (_textBox != null)
|
||||||
|
{
|
||||||
|
_textBox.Text = ConvertValueToText(Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual T? Clamp(T? value, T max, T min)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value.Value.CompareTo(max) > 0)
|
||||||
|
{
|
||||||
|
return Maximum;
|
||||||
|
}
|
||||||
|
if (value.Value.CompareTo(min) < 0)
|
||||||
|
{
|
||||||
|
return Minimum;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValidSpinDirection()
|
||||||
|
{
|
||||||
|
var validDirection = ValidSpinDirections.None;
|
||||||
|
if (!IsReadOnly)
|
||||||
|
{
|
||||||
|
if (Value is null)
|
||||||
|
{
|
||||||
|
validDirection = ValidSpinDirections.Increase | ValidSpinDirections.Decrease;
|
||||||
|
}
|
||||||
|
if (Value.HasValue && Value.Value.CompareTo(Maximum) < 0)
|
||||||
|
{
|
||||||
|
validDirection |= ValidSpinDirections.Increase;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Value.HasValue && Value.Value.CompareTo(Minimum) > 0)
|
||||||
|
{
|
||||||
|
validDirection |= ValidSpinDirections.Decrease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_spinner != null)
|
||||||
|
{
|
||||||
|
_spinner.ValidSpinDirection = validDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isSyncingTextAndValue;
|
||||||
|
|
||||||
|
protected override bool SyncTextAndValue(bool fromTextToValue = false, string? text = null, bool forceTextUpdate = false)
|
||||||
|
{
|
||||||
|
if (_isSyncingTextAndValue) return true;
|
||||||
|
_isSyncingTextAndValue = true;
|
||||||
|
var parsedTextIsValid = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fromTextToValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
var newValue = ConvertTextToValue(text);
|
||||||
|
if (EmptyInputValue is not null && newValue is null)
|
||||||
|
{
|
||||||
|
newValue = EmptyInputValue;
|
||||||
|
}
|
||||||
|
if (!Equals(newValue, Value))
|
||||||
|
{
|
||||||
|
SetCurrentValue(ValueProperty, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
parsedTextIsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_updateFromTextInput)
|
||||||
|
{
|
||||||
|
if (forceTextUpdate)
|
||||||
|
{
|
||||||
|
var newText = ConvertValueToText(Value);
|
||||||
|
if (_textBox!= null && !Equals(_textBox.Text, newText))
|
||||||
|
{
|
||||||
|
_textBox.Text = newText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_updateFromTextInput && !parsedTextIsValid)
|
||||||
|
{
|
||||||
|
if (_spinner is not null)
|
||||||
|
{
|
||||||
|
_spinner.ValidSpinDirection = ValidSpinDirections.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetValidSpinDirection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSyncingTextAndValue = false;
|
||||||
|
}
|
||||||
|
return parsedTextIsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual T? ConvertTextToValue(string? text)
|
||||||
|
{
|
||||||
|
T? result;
|
||||||
|
if (string.IsNullOrWhiteSpace(text)) return null;
|
||||||
|
if (TextConverter != null)
|
||||||
|
{
|
||||||
|
var valueFromText = TextConverter.Convert(text, typeof(T?), null, CultureInfo.CurrentCulture);
|
||||||
|
return (T?)valueFromText;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ParseText(text, out var outputValue))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Input string was not in a correct format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = outputValue;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual string? ConvertValueToText(T? value)
|
||||||
|
{
|
||||||
|
if (TextConverter is not null)
|
||||||
|
{
|
||||||
|
return TextConverter.ConvertBack(Value, typeof(int), null, CultureInfo.CurrentCulture)?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FormatString.Contains("{0"))
|
||||||
|
{
|
||||||
|
return string.Format(NumberFormat, FormatString, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValueToString(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Increase()
|
||||||
|
{
|
||||||
|
T? value;
|
||||||
|
if (Value is not null)
|
||||||
|
{
|
||||||
|
value = Add(Value.Value, Step);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = IsSet(MinimumProperty) ? Minimum : Zero;
|
||||||
|
}
|
||||||
|
SetCurrentValue(ValueProperty, Clamp(value, Maximum, Minimum));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Decrease()
|
||||||
|
{
|
||||||
|
T? value;
|
||||||
|
if (Value is not null)
|
||||||
|
{
|
||||||
|
value = Minus(Value.Value, Step);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = IsSet(MaximumProperty) ? Maximum : Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCurrentValue(ValueProperty, Clamp(value, Maximum, Minimum));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool ParseText(string? text, out T? number);
|
||||||
|
protected abstract string? ValueToString(T? value);
|
||||||
|
protected abstract T Zero { get; }
|
||||||
|
protected abstract T? Add(T? a, T? b);
|
||||||
|
protected abstract T? Minus(T? a, T? b);
|
||||||
|
|
||||||
}
|
}
|
||||||
15
src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs
Normal file
15
src/Ursa/Controls/NumericUpDown/ValueChangedEventArgs.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
public class ValueChangedEventArgs<T>: RoutedEventArgs where T: struct, IComparable<T>
|
||||||
|
{
|
||||||
|
public ValueChangedEventArgs(RoutedEvent routedEvent, T? oldValue, T? newValue): base(routedEvent)
|
||||||
|
{
|
||||||
|
OldValue = oldValue;
|
||||||
|
NewValue = newValue;
|
||||||
|
}
|
||||||
|
public T? OldValue { get; }
|
||||||
|
public T? NewValue { get; }
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user