18 changed files with 1184 additions and 1682 deletions
@ -1,96 +0,0 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<StackPanel Orientation="Vertical" Gap="4"> |
|||
<TextBlock Margin="2" Classes="h1">Numeric up-down controls</TextBlock> |
|||
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down controls provide a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock> |
|||
<TextBlock Margin="2" Classes="h2">The following controls are available to support various native numeric types:</TextBlock> |
|||
<TextBlock Margin="2" Classes="h2">ByteUpDown, ShortUpDown, IntegerUpDown, LongUpDown, SingleUpDown, DoubleUpDown, DecimalUpDown.</TextBlock> |
|||
|
|||
<TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock> |
|||
<Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto"> |
|||
<Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="35,35,35,35,35"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock> |
|||
<CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #doubleUpDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">IsReadOnly:</TextBlock> |
|||
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #doubleUpDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock> |
|||
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #doubleUpDown.AllowSpin}" IsEnabled="{Binding #doubleUpDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock> |
|||
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #doubleUpDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">DisplayDefaultValueOnEmptyText:</TextBlock> |
|||
<CheckBox Grid.Row="4" Grid.Column="1" IsChecked="{Binding #doubleUpDown.DisplayDefaultValueOnEmptyText}" VerticalAlignment="Center" Margin="2"/> |
|||
</Grid> |
|||
<Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="35,35,35,35,35"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock> |
|||
<DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}" |
|||
VerticalAlignment="Center" Margin="2"> |
|||
<DropDown.ItemTemplate> |
|||
<DataTemplate> |
|||
<StackPanel Orientation="Horizontal" Gap="2"> |
|||
<TextBlock Text="{Binding Name}"/> |
|||
<TextBlock Text="-"/> |
|||
<TextBlock Text="{Binding Value}"/> |
|||
</StackPanel> |
|||
</DataTemplate> |
|||
</DropDown.ItemTemplate> |
|||
</DropDown> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock> |
|||
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #doubleUpDown.ButtonSpinnerLocation}" |
|||
VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock> |
|||
<DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #doubleUpDown.CultureInfo}" |
|||
VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock> |
|||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #doubleUpDown.Watermark}" VerticalAlignment="Center" Margin="2" /> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock> |
|||
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #doubleUpDown.Text}" VerticalAlignment="Center" Margin="2" /> |
|||
</Grid> |
|||
<Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="35,35,35,35,35" ColumnDefinitions="Auto, 120"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock> |
|||
<DoubleUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #doubleUpDown.Minimum}" AllowInputSpecialValues="NegativeInfinity" |
|||
CultureInfo="{Binding #doubleUpDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock> |
|||
<DoubleUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #doubleUpDown.Maximum}" AllowInputSpecialValues="PositiveInfinity" |
|||
CultureInfo="{Binding #doubleUpDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock> |
|||
<DoubleUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #doubleUpDown.Increment}" VerticalAlignment="Center" |
|||
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock> |
|||
<DoubleUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #doubleUpDown.Value}" VerticalAlignment="Center" |
|||
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">DefaultValue:</TextBlock> |
|||
<DoubleUpDown Grid.Row="4" Grid.Column="1" Value="{Binding #doubleUpDown.DefaultValue}" AllowInputSpecialValues="Any" |
|||
VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
</Grid> |
|||
</Grid> |
|||
|
|||
<TextBlock Margin="2" Classes="h2">DoubleUpDown and SingleUpDown support the AllowInputSpecialValues property</TextBlock> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowInputSpecialValues:</TextBlock> |
|||
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding AllowedSpecialValues}" SelectedItem="{Binding #doubleUpDown.AllowInputSpecialValues}" |
|||
VerticalAlignment="Center" Margin="2"/> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Gap="10"> |
|||
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of DoubleUpDown:</TextBlock> |
|||
<DoubleUpDown Name="doubleUpDown" Minimum="0" Maximum="10" Increment="0.5" |
|||
AllowInputSpecialValues="Any" CultureInfo="en-US" |
|||
VerticalAlignment="Center" Height="25" Width="100" |
|||
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/> |
|||
</StackPanel> |
|||
|
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,86 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<StackPanel Orientation="Vertical" Gap="4"> |
|||
<TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock> |
|||
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock> |
|||
|
|||
<TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock> |
|||
<Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto"> |
|||
<Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="35,35,35,35,35"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock> |
|||
<CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #upDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">IsReadOnly:</TextBlock> |
|||
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #upDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock> |
|||
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" IsEnabled="{Binding #upDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock> |
|||
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">DisplayDefaultValueOnEmptyText:</TextBlock> |
|||
<CheckBox Grid.Row="4" Grid.Column="1" IsChecked="{Binding #upDown.DisplayDefaultValueOnEmptyText}" VerticalAlignment="Center" Margin="2"/> |
|||
</Grid> |
|||
<Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="35,35,35,35,35"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock> |
|||
<DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}" |
|||
VerticalAlignment="Center" Margin="2"> |
|||
<DropDown.ItemTemplate> |
|||
<DataTemplate> |
|||
<StackPanel Orientation="Horizontal" Gap="2"> |
|||
<TextBlock Text="{Binding Name}"/> |
|||
<TextBlock Text="-"/> |
|||
<TextBlock Text="{Binding Value}"/> |
|||
</StackPanel> |
|||
</DataTemplate> |
|||
</DropDown.ItemTemplate> |
|||
</DropDown> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock> |
|||
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}" |
|||
VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock> |
|||
<DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}" |
|||
VerticalAlignment="Center" Margin="2"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock> |
|||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.Watermark}" VerticalAlignment="Center" Margin="2" /> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock> |
|||
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" /> |
|||
</Grid> |
|||
<Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="35,35,35,35,35" ColumnDefinitions="Auto, 120"> |
|||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock> |
|||
<NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}" |
|||
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock> |
|||
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}" |
|||
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock> |
|||
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center" |
|||
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock> |
|||
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center" |
|||
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">DefaultValue:</TextBlock> |
|||
<NumericUpDown Grid.Row="4" Grid.Column="1" Value="{Binding #upDown.DefaultValue}" |
|||
VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/> |
|||
|
|||
</Grid> |
|||
</Grid> |
|||
|
|||
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Gap="10"> |
|||
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock> |
|||
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5" |
|||
CultureInfo="en-US" VerticalAlignment="Center" Height="25" Width="100" |
|||
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/> |
|||
</StackPanel> |
|||
|
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -1,15 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
[Flags] |
|||
public enum AllowedSpecialValues |
|||
{ |
|||
None = 0, |
|||
NaN = 1, |
|||
PositiveInfinity = 2, |
|||
NegativeInfinity = 4, |
|||
AnyInfinity = PositiveInfinity | NegativeInfinity, |
|||
Any = NaN | AnyInfinity |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class ByteUpDown : CommonNumericUpDown<byte> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ByteUpDown"/> class.
|
|||
/// </summary>
|
|||
static ByteUpDown() => UpdateMetadata(typeof(ByteUpDown), 1, byte.MinValue, byte.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="ByteUpDown"/> class.
|
|||
/// </summary>
|
|||
public ByteUpDown() : base(byte.TryParse, decimal.ToByte, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override byte IncrementValue(byte value, byte increment) => (byte)(value + increment); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override byte DecrementValue(byte value, byte increment) => (byte)(value - increment); |
|||
} |
|||
} |
|||
@ -1,346 +0,0 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for controls that represents a TextBox with button spinners that allow incrementing and decrementing nullable numeric values.
|
|||
/// </summary>
|
|||
public abstract class CommonNumericUpDown<T> : NumericUpDown<T?> where T : struct, IFormattable, IComparable<T> |
|||
{ |
|||
protected delegate bool FromText(string s, NumberStyles style, IFormatProvider provider, out T result); |
|||
protected delegate T FromDecimal(decimal d); |
|||
|
|||
private readonly FromText _fromText; |
|||
private readonly FromDecimal _fromDecimal; |
|||
private readonly Func<T, T, bool> _fromLowerThan; |
|||
private readonly Func<T, T, bool> _fromGreaterThan; |
|||
|
|||
private NumberStyles _parsingNumberStyle = NumberStyles.Any; |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ParsingNumberStyle"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<CommonNumericUpDown<T>, NumberStyles> ParsingNumberStyleProperty = |
|||
AvaloniaProperty.RegisterDirect<CommonNumericUpDown<T>, NumberStyles>(nameof(ParsingNumberStyle), |
|||
updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="CommonNumericUpDown{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="fromText">Delegate to parse value from text.</param>
|
|||
/// <param name="fromDecimal">Delegate to parse value from decimal.</param>
|
|||
/// <param name="fromLowerThan">Delegate to compare if one value is lower than another.</param>
|
|||
/// <param name="fromGreaterThan">Delegate to compare if one value is greater than another.</param>
|
|||
protected CommonNumericUpDown(FromText fromText, FromDecimal fromDecimal, Func<T, T, bool> fromLowerThan, Func<T, T, bool> fromGreaterThan) |
|||
{ |
|||
_fromText = fromText ?? throw new ArgumentNullException(nameof(fromText)); |
|||
_fromDecimal = fromDecimal ?? throw new ArgumentNullException(nameof(fromDecimal)); |
|||
_fromLowerThan = fromLowerThan ?? throw new ArgumentNullException(nameof(fromLowerThan)); |
|||
_fromGreaterThan = fromGreaterThan ?? throw new ArgumentNullException(nameof(fromGreaterThan)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
|
|||
/// </summary>
|
|||
public NumberStyles ParsingNumberStyle |
|||
{ |
|||
get { return _parsingNumberStyle; } |
|||
set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); } |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnIncrement() |
|||
{ |
|||
if (!HandleNullSpin()) |
|||
{ |
|||
var result = IncrementValue(Value.Value, Increment.Value); |
|||
Value = CoerceValueMinMax(result); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnDecrement() |
|||
{ |
|||
if (!HandleNullSpin()) |
|||
{ |
|||
var result = DecrementValue(Value.Value, Increment.Value); |
|||
Value = CoerceValueMinMax(result); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnMinimumChanged(T? oldValue, T? newValue) |
|||
{ |
|||
base.OnMinimumChanged(oldValue, newValue); |
|||
|
|||
if (Value.HasValue && ClipValueToMinMax) |
|||
{ |
|||
Value = CoerceValueMinMax(Value.Value); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnMaximumChanged(T? oldValue, T? newValue) |
|||
{ |
|||
base.OnMaximumChanged(oldValue, newValue); |
|||
|
|||
if (Value.HasValue && ClipValueToMinMax) |
|||
{ |
|||
Value = CoerceValueMinMax(Value.Value); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override T? ConvertTextToValue(string text) |
|||
{ |
|||
T? result = null; |
|||
|
|||
if (string.IsNullOrEmpty(text)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
// Since the conversion from Value to text using a FormartString may not be parsable,
|
|||
// we verify that the already existing text is not the exact same value.
|
|||
var currentValueText = ConvertValueToText(); |
|||
if (Equals(currentValueText, text)) |
|||
{ |
|||
return Value; |
|||
} |
|||
|
|||
result = ConvertTextToValueCore(currentValueText, text); |
|||
|
|||
if (ClipValueToMinMax) |
|||
{ |
|||
return GetClippedMinMaxValue(result); |
|||
} |
|||
|
|||
ValidateDefaultMinMax(result); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override string ConvertValueToText() |
|||
{ |
|||
if (Value == null) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
|
|||
if (FormatString.Contains("{0")) |
|||
{ |
|||
return string.Format(CultureInfo, FormatString, Value.Value); |
|||
} |
|||
|
|||
return Value.Value.ToString(FormatString, CultureInfo); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetValidSpinDirection() |
|||
{ |
|||
var validDirections = ValidSpinDirections.None; |
|||
|
|||
// Null increment always prevents spin.
|
|||
if (Increment != null && !IsReadOnly) |
|||
{ |
|||
if (IsLowerThan(Value, Maximum) || !Value.HasValue || !Maximum.HasValue) |
|||
{ |
|||
validDirections = validDirections | ValidSpinDirections.Increase; |
|||
} |
|||
|
|||
if (IsGreaterThan(Value, Minimum) || !Value.HasValue || !Minimum.HasValue) |
|||
{ |
|||
validDirections = validDirections | ValidSpinDirections.Decrease; |
|||
} |
|||
} |
|||
|
|||
if (Spinner != null) |
|||
{ |
|||
Spinner.ValidSpinDirection = validDirections; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if provided value is within allowed values.
|
|||
/// </summary>
|
|||
/// <param name="allowedValues">The alowed values.</param>
|
|||
/// <param name="valueToCompare">The value to check.</param>
|
|||
protected void TestInputSpecialValue(AllowedSpecialValues allowedValues, AllowedSpecialValues valueToCompare) |
|||
{ |
|||
if ((allowedValues & valueToCompare) != valueToCompare) |
|||
{ |
|||
switch (valueToCompare) |
|||
{ |
|||
case AllowedSpecialValues.NaN: |
|||
throw new InvalidDataException("Value to parse shouldn't be NaN."); |
|||
case AllowedSpecialValues.PositiveInfinity: |
|||
throw new InvalidDataException("Value to parse shouldn't be Positive Infinity."); |
|||
case AllowedSpecialValues.NegativeInfinity: |
|||
throw new InvalidDataException("Value to parse shouldn't be Negative Infinity."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected static void UpdateMetadata(Type type, T? increment, T? minimun, T? maximum) |
|||
{ |
|||
IncrementProperty.OverrideDefaultValue(type, increment); |
|||
MinimumProperty.OverrideDefaultValue(type, minimun); |
|||
MaximumProperty.OverrideDefaultValue(type, maximum); |
|||
} |
|||
|
|||
protected abstract T IncrementValue(T value, T increment); |
|||
|
|||
protected abstract T DecrementValue(T value, T increment); |
|||
|
|||
private bool IsLowerThan(T? value1, T? value2) |
|||
{ |
|||
if (value1 == null || value2 == null) |
|||
{ |
|||
return false; |
|||
} |
|||
return _fromLowerThan(value1.Value, value2.Value); |
|||
} |
|||
|
|||
private bool IsGreaterThan(T? value1, T? value2) |
|||
{ |
|||
if (value1 == null || value2 == null) |
|||
{ |
|||
return false; |
|||
} |
|||
return _fromGreaterThan(value1.Value, value2.Value); |
|||
} |
|||
|
|||
private bool HandleNullSpin() |
|||
{ |
|||
if (!Value.HasValue) |
|||
{ |
|||
var forcedValue = DefaultValue ?? default(T); |
|||
Value = CoerceValueMinMax(forcedValue); |
|||
return true; |
|||
} |
|||
else if (!Increment.HasValue) |
|||
{ |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
internal bool IsValid(T? value) |
|||
{ |
|||
return !IsLowerThan(value, Minimum) && !IsGreaterThan(value, Maximum); |
|||
} |
|||
|
|||
private T? CoerceValueMinMax(T value) |
|||
{ |
|||
if (IsLowerThan(value, Minimum)) |
|||
{ |
|||
return Minimum; |
|||
} |
|||
else if (IsGreaterThan(value, Maximum)) |
|||
{ |
|||
return Maximum; |
|||
} |
|||
else |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
private bool IsPercent(string stringToTest) |
|||
{ |
|||
var PIndex = stringToTest.IndexOf("P", StringComparison.Ordinal); |
|||
if (PIndex >= 0) |
|||
{ |
|||
//stringToTest contains a "P" between 2 "'", it's considered as text, not percent
|
|||
var isText = stringToTest.Substring(0, PIndex).Contains("'") |
|||
&& stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'"); |
|||
|
|||
return !isText; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private T? ConvertTextToValueCore(string currentValueText, string text) |
|||
{ |
|||
T? result; |
|||
|
|||
if (IsPercent(FormatString)) |
|||
{ |
|||
result = _fromDecimal(ParsePercent(text, CultureInfo)); |
|||
} |
|||
else |
|||
{ |
|||
// Problem while converting new text
|
|||
if (!_fromText(text, ParsingNumberStyle, CultureInfo, out T outputValue)) |
|||
{ |
|||
var shouldThrow = true; |
|||
|
|||
// Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
|
|||
if (!_fromText(currentValueText, ParsingNumberStyle, CultureInfo, out T _)) |
|||
{ |
|||
// extract non-digit characters
|
|||
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c)); |
|||
var textSpecialCharacters = text.Where(c => !char.IsDigit(c)); |
|||
// same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
|
|||
if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0) |
|||
{ |
|||
foreach (var character in textSpecialCharacters) |
|||
{ |
|||
text = text.Replace(character.ToString(), string.Empty); |
|||
} |
|||
// if without the special characters, parsing is good, do not throw
|
|||
if (_fromText(text, ParsingNumberStyle, CultureInfo, out outputValue)) |
|||
{ |
|||
shouldThrow = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (shouldThrow) |
|||
{ |
|||
throw new InvalidDataException("Input string was not in a correct format."); |
|||
} |
|||
} |
|||
result = outputValue; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private T? GetClippedMinMaxValue(T? result) |
|||
{ |
|||
if (IsGreaterThan(result, Maximum)) |
|||
{ |
|||
return Maximum; |
|||
} |
|||
else if (IsLowerThan(result, Minimum)) |
|||
{ |
|||
return Minimum; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private void ValidateDefaultMinMax(T? value) |
|||
{ |
|||
// DefaultValue is always accepted.
|
|||
if (Equals(value, DefaultValue)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (IsLowerThan(value, Minimum)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum)); |
|||
} |
|||
else if (IsGreaterThan(value, Maximum)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class DecimalUpDown : CommonNumericUpDown<decimal> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="DecimalUpDown"/> class.
|
|||
/// </summary>
|
|||
static DecimalUpDown() => UpdateMetadata(typeof(DecimalUpDown), 1m, decimal.MinValue, decimal.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="DecimalUpDown"/> class.
|
|||
/// </summary>
|
|||
public DecimalUpDown() : base(decimal.TryParse, d => d, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override decimal IncrementValue(decimal value, decimal increment) => value + increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override decimal DecrementValue(decimal value, decimal increment) => value - increment; |
|||
} |
|||
} |
|||
@ -1,109 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class DoubleUpDown : CommonNumericUpDown<double> |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="AllowInputSpecialValues"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DoubleUpDown, AllowedSpecialValues> AllowInputSpecialValuesProperty = |
|||
AvaloniaProperty.RegisterDirect<DoubleUpDown, AllowedSpecialValues>(nameof(AllowInputSpecialValues), |
|||
updown => updown.AllowInputSpecialValues, (updown, v) => updown.AllowInputSpecialValues = v); |
|||
|
|||
private AllowedSpecialValues _allowInputSpecialValues; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="DoubleUpDown"/> class.
|
|||
/// </summary>
|
|||
static DoubleUpDown() => UpdateMetadata(typeof(DoubleUpDown), 1d, double.NegativeInfinity, double.PositiveInfinity); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="DoubleUpDown"/> class.
|
|||
/// </summary>
|
|||
public DoubleUpDown() : base(double.TryParse, decimal.ToDouble, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value representing the special values the user is allowed to input, such as "Infinity", "-Infinity" and "NaN" values.
|
|||
/// </summary>
|
|||
public AllowedSpecialValues AllowInputSpecialValues |
|||
{ |
|||
get { return _allowInputSpecialValues; } |
|||
set { SetAndRaise(AllowInputSpecialValuesProperty, ref _allowInputSpecialValues, value); } |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double IncrementValue(double value, double increment) => value + increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double DecrementValue(double value, double increment) => value - increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double? OnCoerceIncrement(double? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && double.IsNaN(baseValue.Value)) |
|||
{ |
|||
throw new ArgumentException("NaN is invalid for Increment."); |
|||
} |
|||
return base.OnCoerceIncrement(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double? OnCoerceMaximum(double? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && double.IsNaN(baseValue.Value)) |
|||
{ |
|||
throw new ArgumentException("NaN is invalid for Maximum."); |
|||
} |
|||
return base.OnCoerceMaximum(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double? OnCoerceMinimum(double? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && double.IsNaN(baseValue.Value)) |
|||
{ |
|||
throw new ArgumentException("NaN is invalid for Minimum."); |
|||
} |
|||
return base.OnCoerceMinimum(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetValidSpinDirection() |
|||
{ |
|||
if (Value.HasValue && double.IsInfinity(Value.Value) && (Spinner != null)) |
|||
{ |
|||
Spinner.ValidSpinDirection = ValidSpinDirections.None; |
|||
} |
|||
else |
|||
{ |
|||
base.SetValidSpinDirection(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override double? ConvertTextToValue(string text) |
|||
{ |
|||
var result = base.ConvertTextToValue(text); |
|||
if (result != null) |
|||
{ |
|||
if (double.IsNaN(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NaN); |
|||
} |
|||
else if (double.IsPositiveInfinity(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.PositiveInfinity); |
|||
} |
|||
else if (double.IsNegativeInfinity(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NegativeInfinity); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class IntegerUpDown : CommonNumericUpDown<int> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="IntegerUpDown"/> class.
|
|||
/// </summary>
|
|||
static IntegerUpDown() => UpdateMetadata(typeof(IntegerUpDown), 1, int.MinValue, int.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="IntegerUpDown"/> class.
|
|||
/// </summary>
|
|||
public IntegerUpDown() : base(int.TryParse, decimal.ToInt32, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override int IncrementValue(int value, int increment) => value + increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override int DecrementValue(int value, int increment) => value - increment; |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class LongUpDown : CommonNumericUpDown<long> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="LongUpDown"/> class.
|
|||
/// </summary>
|
|||
static LongUpDown() => UpdateMetadata(typeof(LongUpDown), 1L, long.MinValue, long.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="LongUpDown"/> class.
|
|||
/// </summary>
|
|||
public LongUpDown() : base(long.TryParse, decimal.ToInt64, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override long IncrementValue(long value, long increment) => value + increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override long DecrementValue(long value, long increment) => value - increment; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,16 @@ |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NumericUpDownValueChangedEventArgs : RoutedEventArgs |
|||
{ |
|||
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double? oldValue, double? newValue) : base(routedEvent) |
|||
{ |
|||
OldValue = oldValue; |
|||
NewValue = newValue; |
|||
} |
|||
|
|||
public double? OldValue { get; } |
|||
public double? NewValue { get; } |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class ShortUpDown : CommonNumericUpDown<short> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ShortUpDown"/> class.
|
|||
/// </summary>
|
|||
static ShortUpDown() => UpdateMetadata(typeof(ShortUpDown), 1, short.MinValue, short.MaxValue); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="ShortUpDown"/> class.
|
|||
/// </summary>
|
|||
public ShortUpDown() : base(short.TryParse, decimal.ToInt16, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override short IncrementValue(short value, short increment) => (short)(value + increment); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override short DecrementValue(short value, short increment) => (short)(value - increment); |
|||
} |
|||
} |
|||
@ -1,106 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <inheritdoc />
|
|||
public class SingleUpDown : CommonNumericUpDown<float> |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="AllowInputSpecialValues"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<SingleUpDown, AllowedSpecialValues> AllowInputSpecialValuesProperty = |
|||
AvaloniaProperty.RegisterDirect<SingleUpDown, AllowedSpecialValues>(nameof(AllowInputSpecialValues), |
|||
updown => updown.AllowInputSpecialValues, (updown, v) => updown.AllowInputSpecialValues = v); |
|||
|
|||
private AllowedSpecialValues _allowInputSpecialValues; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="SingleUpDown"/> class.
|
|||
/// </summary>
|
|||
static SingleUpDown() => UpdateMetadata(typeof(SingleUpDown), 1f, float.NegativeInfinity, float.PositiveInfinity); |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of the <see cref="SingleUpDown"/> class.
|
|||
/// </summary>
|
|||
public SingleUpDown() : base(float.TryParse, decimal.ToSingle, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value representing the special values the user is allowed to input, such as "Infinity", "-Infinity" and "NaN" values.
|
|||
/// </summary>
|
|||
public AllowedSpecialValues AllowInputSpecialValues |
|||
{ |
|||
get { return _allowInputSpecialValues; } |
|||
set { SetAndRaise(AllowInputSpecialValuesProperty, ref _allowInputSpecialValues, value); } |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float? OnCoerceIncrement(float? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && float.IsNaN(baseValue.Value)) |
|||
throw new ArgumentException("NaN is invalid for Increment."); |
|||
|
|||
return base.OnCoerceIncrement(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float? OnCoerceMaximum(float? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && float.IsNaN(baseValue.Value)) |
|||
throw new ArgumentException("NaN is invalid for Maximum."); |
|||
|
|||
return base.OnCoerceMaximum(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float? OnCoerceMinimum(float? baseValue) |
|||
{ |
|||
if (baseValue.HasValue && float.IsNaN(baseValue.Value)) |
|||
throw new ArgumentException("NaN is invalid for Minimum."); |
|||
|
|||
return base.OnCoerceMinimum(baseValue); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float IncrementValue(float value, float increment) => value + increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float DecrementValue(float value, float increment) => value - increment; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void SetValidSpinDirection() |
|||
{ |
|||
if (Value.HasValue && float.IsInfinity(Value.Value) && (Spinner != null)) |
|||
{ |
|||
Spinner.ValidSpinDirection = ValidSpinDirections.None; |
|||
} |
|||
else |
|||
{ |
|||
base.SetValidSpinDirection(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float? ConvertTextToValue(string text) |
|||
{ |
|||
var result = base.ConvertTextToValue(text); |
|||
if (result != null) |
|||
{ |
|||
if (float.IsNaN(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NaN); |
|||
} |
|||
else if (float.IsPositiveInfinity(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.PositiveInfinity); |
|||
} |
|||
else if (float.IsNegativeInfinity(result.Value)) |
|||
{ |
|||
TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NegativeInfinity); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -1,824 +0,0 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public abstract class UpDownBase : TemplatedControl |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Base class for controls that represents a TextBox with button spinners that allow incrementing and decrementing values.
|
|||
/// </summary>
|
|||
public abstract class UpDownBase<T> : UpDownBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="AllowSpin"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> AllowSpinProperty = |
|||
ButtonSpinner.AllowSpinProperty.AddOwner<UpDownBase<T>>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ButtonSpinnerLocation"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty = |
|||
ButtonSpinner.ButtonSpinnerLocationProperty.AddOwner<UpDownBase<T>>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ShowButtonSpinner"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> ShowButtonSpinnerProperty = |
|||
ButtonSpinner.ShowButtonSpinnerProperty.AddOwner<UpDownBase<T>>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ClipValueToMinMax"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<UpDownBase<T>, bool> ClipValueToMinMaxProperty = |
|||
AvaloniaProperty.RegisterDirect<UpDownBase<T>, bool>(nameof(ClipValueToMinMax), |
|||
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CultureInfo"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<UpDownBase<T>, CultureInfo> CultureInfoProperty = |
|||
AvaloniaProperty.RegisterDirect<UpDownBase<T>, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo, |
|||
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DefaultValue"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<T> DefaultValueProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, T>(nameof(DefaultValue)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DisplayDefaultValueOnEmptyText"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> DisplayDefaultValueOnEmptyTextProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, bool>(nameof(DisplayDefaultValueOnEmptyText)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsReadOnly"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> IsReadOnlyProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, bool>(nameof(IsReadOnly)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Maximum"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<T> MaximumProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, T>(nameof(Maximum), validate: OnCoerceMaximum); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Minimum"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<T> MinimumProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, T>(nameof(Minimum), validate: OnCoerceMinimum); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Text"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<UpDownBase<T>, string> TextProperty = |
|||
AvaloniaProperty.RegisterDirect<UpDownBase<T>, string>(nameof(Text), o => o.Text, (o, v) => o.Text = v, |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Value"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<UpDownBase<T>, T> ValueProperty = |
|||
AvaloniaProperty.RegisterDirect<UpDownBase<T>, T>(nameof(Value), updown => updown.Value, |
|||
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Watermark"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<string> WatermarkProperty = |
|||
AvaloniaProperty.Register<UpDownBase<T>, string>(nameof(Watermark)); |
|||
|
|||
private IDisposable _textBoxTextChangedSubscription; |
|||
private T _value; |
|||
private string _text; |
|||
private bool _internalValueSet; |
|||
private bool _clipValueToMinMax; |
|||
private bool _isSyncingTextAndValueProperties; |
|||
private bool _isTextChangedFromUI; |
|||
private CultureInfo _cultureInfo; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Spinner template part.
|
|||
/// </summary>
|
|||
protected Spinner Spinner { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the TextBox template part.
|
|||
/// </summary>
|
|||
protected TextBox TextBox { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ability to perform increment/decrement operations via the keyboard, button spinners, or mouse wheel.
|
|||
/// </summary>
|
|||
public bool AllowSpin |
|||
{ |
|||
get { return GetValue(AllowSpinProperty); } |
|||
set { SetValue(AllowSpinProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets current location of the <see cref="ButtonSpinner"/>.
|
|||
/// </summary>
|
|||
public Location ButtonSpinnerLocation |
|||
{ |
|||
get { return GetValue(ButtonSpinnerLocationProperty); } |
|||
set { SetValue(ButtonSpinnerLocationProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the spin buttons should be shown.
|
|||
/// </summary>
|
|||
public bool ShowButtonSpinner |
|||
{ |
|||
get { return GetValue(ShowButtonSpinnerProperty); } |
|||
set { SetValue(ShowButtonSpinnerProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets if the value should be clipped when minimum/maximum is reached.
|
|||
/// </summary>
|
|||
public bool ClipValueToMinMax |
|||
{ |
|||
get { return _clipValueToMinMax; } |
|||
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current CultureInfo.
|
|||
/// </summary>
|
|||
public CultureInfo CultureInfo |
|||
{ |
|||
get { return _cultureInfo; } |
|||
set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value to use when the <see cref="Value"/> is null and an increment/decrement operation is performed.
|
|||
/// </summary>
|
|||
public T DefaultValue |
|||
{ |
|||
get { return GetValue(DefaultValueProperty); } |
|||
set { SetValue(DefaultValueProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets if the defaultValue should be displayed when the Text is empty.
|
|||
/// </summary>
|
|||
public bool DisplayDefaultValueOnEmptyText |
|||
{ |
|||
get { return GetValue(DisplayDefaultValueOnEmptyTextProperty); } |
|||
set { SetValue(DisplayDefaultValueOnEmptyTextProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets if the control is read only.
|
|||
/// </summary>
|
|||
public bool IsReadOnly |
|||
{ |
|||
get { return GetValue(IsReadOnlyProperty); } |
|||
set { SetValue(IsReadOnlyProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum allowed value.
|
|||
/// </summary>
|
|||
public T Maximum |
|||
{ |
|||
get { return GetValue(MaximumProperty); } |
|||
set { SetValue(MaximumProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum allowed value.
|
|||
/// </summary>
|
|||
public T Minimum |
|||
{ |
|||
get { return GetValue(MinimumProperty); } |
|||
set { SetValue(MinimumProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the formatted string representation of the value.
|
|||
/// </summary>
|
|||
public string Text |
|||
{ |
|||
get { return _text; } |
|||
set { SetAndRaise(TextProperty, ref _text, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value.
|
|||
/// </summary>
|
|||
public T Value |
|||
{ |
|||
get { return _value; } |
|||
set |
|||
{ |
|||
value = OnCoerceValue(value); |
|||
SetAndRaise(ValueProperty, ref _value, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the object to use as a watermark if the <see cref="Value"/> is null.
|
|||
/// </summary>
|
|||
public string Watermark |
|||
{ |
|||
get { return GetValue(WatermarkProperty); } |
|||
set { SetValue(WatermarkProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes new instance of <see cref="UpDownBase{T}"/> class.
|
|||
/// </summary>
|
|||
protected UpDownBase() |
|||
{ |
|||
Initialized += (sender, e) => |
|||
{ |
|||
if (!_internalValueSet && IsInitialized) |
|||
{ |
|||
SyncTextAndValueProperties(false, null, true); |
|||
} |
|||
|
|||
SetValidSpinDirection(); |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="UpDownBase{T}"/> class.
|
|||
/// </summary>
|
|||
static UpDownBase() |
|||
{ |
|||
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged); |
|||
DefaultValueProperty.Changed.Subscribe(OnDefaultValueChanged); |
|||
DisplayDefaultValueOnEmptyTextProperty.Changed.Subscribe(OnDisplayDefaultValueOnEmptyTextChanged); |
|||
IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged); |
|||
MaximumProperty.Changed.Subscribe(OnMaximumChanged); |
|||
MinimumProperty.Changed.Subscribe(OnMinimumChanged); |
|||
TextProperty.Changed.Subscribe(OnTextChanged); |
|||
ValueProperty.Changed.Subscribe(OnValueChanged); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|||
{ |
|||
if (TextBox != null) |
|||
{ |
|||
TextBox.PointerPressed -= TextBoxOnPointerPressed; |
|||
_textBoxTextChangedSubscription?.Dispose(); |
|||
} |
|||
TextBox = e.NameScope.Find<TextBox>("PART_TextBox"); |
|||
if (TextBox != null) |
|||
{ |
|||
TextBox.Text = Text; |
|||
TextBox.PointerPressed += TextBoxOnPointerPressed; |
|||
_textBoxTextChangedSubscription = TextBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBoxOnTextChanged()); |
|||
} |
|||
|
|||
if (Spinner != null) |
|||
{ |
|||
Spinner.Spin -= OnSpinnerSpin; |
|||
} |
|||
|
|||
Spinner = e.NameScope.Find<Spinner>("PART_Spinner"); |
|||
|
|||
if (Spinner != null) |
|||
{ |
|||
Spinner.Spin += OnSpinnerSpin; |
|||
} |
|||
|
|||
SetValidSpinDirection(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Enter: |
|||
var commitSuccess = CommitInput(); |
|||
e.Handled = !commitSuccess; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="CultureInfo"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnCultureInfoChanged(CultureInfo oldValue, CultureInfo newValue) |
|||
{ |
|||
if (IsInitialized) |
|||
{ |
|||
SyncTextAndValueProperties(false, null); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="DefaultValue"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnDefaultValueChanged(T oldValue, T newValue) |
|||
{ |
|||
if (IsInitialized && string.IsNullOrEmpty(Text)) |
|||
{ |
|||
SyncTextAndValueProperties(true, Text); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="DisplayDefaultValueOnEmptyText"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnDisplayDefaultValueOnEmptyTextChanged(bool oldValue, bool newValue) |
|||
{ |
|||
if (IsInitialized && string.IsNullOrEmpty(Text)) |
|||
{ |
|||
SyncTextAndValueProperties(false, Text); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="IsReadOnly"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue) |
|||
{ |
|||
SetValidSpinDirection(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Maximum"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnMaximumChanged(T oldValue, T newValue) |
|||
{ |
|||
if (IsInitialized) |
|||
{ |
|||
SetValidSpinDirection(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Minimum"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnMinimumChanged(T oldValue, T newValue) |
|||
{ |
|||
if (IsInitialized) |
|||
{ |
|||
SetValidSpinDirection(); |
|||
} |
|||
} |
|||
|
|||
/// <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 OnTextChanged(string oldValue, string newValue) |
|||
{ |
|||
if (IsInitialized) |
|||
{ |
|||
SyncTextAndValueProperties(true, Text); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Value"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void OnValueChanged(T oldValue, T newValue) |
|||
{ |
|||
if (!_internalValueSet && IsInitialized) |
|||
{ |
|||
SyncTextAndValueProperties(false, null, true); |
|||
} |
|||
|
|||
SetValidSpinDirection(); |
|||
|
|||
RaiseValueChangedEvent(oldValue, newValue); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Maximum"/> property has to be coerced.
|
|||
/// </summary>
|
|||
/// <param name="baseValue">The value.</param>
|
|||
protected virtual T OnCoerceMaximum(T baseValue) |
|||
{ |
|||
return baseValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Minimum"/> property has to be coerced.
|
|||
/// </summary>
|
|||
/// <param name="baseValue">The value.</param>
|
|||
protected virtual T OnCoerceMinimum(T baseValue) |
|||
{ |
|||
return baseValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Value"/> property has to be coerced.
|
|||
/// </summary>
|
|||
/// <param name="baseValue">The value.</param>
|
|||
protected virtual T OnCoerceValue(T baseValue) |
|||
{ |
|||
return baseValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raises the OnSpin event when spinning is initiated by the end-user.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
protected virtual void OnSpin(SpinEventArgs e) |
|||
{ |
|||
if (e == null) |
|||
{ |
|||
throw new ArgumentNullException("e"); |
|||
} |
|||
|
|||
var handler = Spinned; |
|||
handler?.Invoke(this, e); |
|||
|
|||
if (e.Direction == SpinDirection.Increase) |
|||
{ |
|||
DoIncrement(); |
|||
} |
|||
else |
|||
{ |
|||
DoDecrement(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raises the <see cref="ValueChanged"/> event.
|
|||
/// </summary>
|
|||
/// <param name="oldValue">The old value.</param>
|
|||
/// <param name="newValue">The new value.</param>
|
|||
protected virtual void RaiseValueChangedEvent(T oldValue, T newValue) |
|||
{ |
|||
var e = new UpDownValueChangedEventArgs<T>(ValueChangedEvent, oldValue, newValue); |
|||
RaiseEvent(e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the formatted text to a value.
|
|||
/// </summary>
|
|||
protected abstract T ConvertTextToValue(string text); |
|||
|
|||
/// <summary>
|
|||
/// Converts the value to formatted text.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract string ConvertValueToText(); |
|||
|
|||
/// <summary>
|
|||
/// Called by OnSpin when the spin direction is SpinDirection.Increase.
|
|||
/// </summary>
|
|||
protected abstract void OnIncrement(); |
|||
|
|||
/// <summary>
|
|||
/// Called by OnSpin when the spin direction is SpinDirection.Descrease.
|
|||
/// </summary>
|
|||
protected abstract void OnDecrement(); |
|||
|
|||
/// <summary>
|
|||
/// Sets the valid spin directions.
|
|||
/// </summary>
|
|||
protected abstract void SetValidSpinDirection(); |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="CultureInfo"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (CultureInfo)e.OldValue; |
|||
var newValue = (CultureInfo)e.NewValue; |
|||
upDown.OnCultureInfoChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="DefaultValue"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnDefaultValueChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (T)e.OldValue; |
|||
var newValue = (T)e.NewValue; |
|||
upDown.OnDefaultValueChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="DisplayDefaultValueOnEmptyText"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnDisplayDefaultValueOnEmptyTextChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (bool) e.OldValue; |
|||
var newValue = (bool) e.NewValue; |
|||
upDown.OnDisplayDefaultValueOnEmptyTextChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="IsReadOnly"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (bool)e.OldValue; |
|||
var newValue = (bool)e.NewValue; |
|||
upDown.OnIsReadOnlyChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Maximum"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnMaximumChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (T)e.OldValue; |
|||
var newValue = (T)e.NewValue; |
|||
upDown.OnMaximumChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Minimum"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (T)e.OldValue; |
|||
var newValue = (T)e.NewValue; |
|||
upDown.OnMinimumChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Text"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnTextChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (string)e.OldValue; |
|||
var newValue = (string)e.NewValue; |
|||
upDown.OnTextChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Value"/> property value changed.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
private static void OnValueChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Sender is UpDownBase<T> upDown) |
|||
{ |
|||
var oldValue = (T)e.OldValue; |
|||
var newValue = (T)e.NewValue; |
|||
upDown.OnValueChanged(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
private void SetValueInternal(T value) |
|||
{ |
|||
_internalValueSet = true; |
|||
try |
|||
{ |
|||
Value = value; |
|||
} |
|||
finally |
|||
{ |
|||
_internalValueSet = false; |
|||
} |
|||
} |
|||
|
|||
private static T OnCoerceMaximum(UpDownBase<T> upDown, T value) |
|||
{ |
|||
return upDown.OnCoerceMaximum(value); |
|||
} |
|||
|
|||
private static T OnCoerceMinimum(UpDownBase<T> upDown, T value) |
|||
{ |
|||
return upDown.OnCoerceMinimum(value); |
|||
} |
|||
|
|||
private void TextBoxOnTextChanged() |
|||
{ |
|||
try |
|||
{ |
|||
_isTextChangedFromUI = true; |
|||
if (TextBox != null) |
|||
{ |
|||
Text = TextBox.Text; |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
_isTextChangedFromUI = false; |
|||
} |
|||
} |
|||
|
|||
private void OnSpinnerSpin(object sender, SpinEventArgs e) |
|||
{ |
|||
if (AllowSpin && !IsReadOnly) |
|||
{ |
|||
var spin = !e.UsingMouseWheel; |
|||
spin |= ((TextBox != null) && TextBox.IsFocused); |
|||
|
|||
if (spin) |
|||
{ |
|||
e.Handled = true; |
|||
OnSpin(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void DoDecrement() |
|||
{ |
|||
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease) |
|||
{ |
|||
OnDecrement(); |
|||
} |
|||
} |
|||
|
|||
internal void DoIncrement() |
|||
{ |
|||
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase) |
|||
{ |
|||
OnIncrement(); |
|||
} |
|||
} |
|||
|
|||
public event EventHandler<SpinEventArgs> Spinned; |
|||
|
|||
private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (e.Device.Captured != Spinner) |
|||
{ |
|||
Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ValueChanged"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<UpDownValueChangedEventArgs<T>> ValueChangedEvent = |
|||
RoutedEvent.Register<UpDownBase<T>, UpDownValueChangedEventArgs<T>>(nameof(ValueChanged), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Raised when the <see cref="Value"/> changes.
|
|||
/// </summary>
|
|||
public event EventHandler<SpinEventArgs> ValueChanged |
|||
{ |
|||
add { AddHandler(ValueChangedEvent, value); } |
|||
remove { RemoveHandler(ValueChangedEvent, value); } |
|||
} |
|||
|
|||
private bool CommitInput() |
|||
{ |
|||
return SyncTextAndValueProperties(true, Text); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
|
|||
/// </summary>
|
|||
/// <param name="updateValueFromText">If value should be updated from text.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
protected bool SyncTextAndValueProperties(bool updateValueFromText, string text) |
|||
{ |
|||
return SyncTextAndValueProperties(updateValueFromText, text, false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
|
|||
/// </summary>
|
|||
/// <param name="updateValueFromText">If value should be updated from text.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="forceTextUpdate">Force text update.</param>
|
|||
private bool SyncTextAndValueProperties(bool updateValueFromText, string text, bool forceTextUpdate) |
|||
{ |
|||
if (_isSyncingTextAndValueProperties) |
|||
return true; |
|||
|
|||
_isSyncingTextAndValueProperties = true; |
|||
var parsedTextIsValid = true; |
|||
try |
|||
{ |
|||
if (updateValueFromText) |
|||
{ |
|||
if (string.IsNullOrEmpty(text)) |
|||
{ |
|||
// An empty input sets the value to the default value.
|
|||
SetValueInternal(DefaultValue); |
|||
} |
|||
else |
|||
{ |
|||
try |
|||
{ |
|||
var newValue = ConvertTextToValue(text); |
|||
if (!Equals(newValue, Value)) |
|||
{ |
|||
SetValueInternal(newValue); |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
parsedTextIsValid = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Do not touch the ongoing text input from user.
|
|||
if (!_isTextChangedFromUI) |
|||
{ |
|||
// Don't replace the empty Text with the non-empty representation of DefaultValue.
|
|||
var shouldKeepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text) && Equals(Value, DefaultValue) && !DisplayDefaultValueOnEmptyText; |
|||
if (!shouldKeepEmpty) |
|||
{ |
|||
var newText = ConvertValueToText(); |
|||
if (!Equals(Text, newText)) |
|||
{ |
|||
Text = newText; |
|||
} |
|||
} |
|||
|
|||
// Sync Text and textBox
|
|||
if (TextBox != null) |
|||
{ |
|||
TextBox.Text = Text; |
|||
} |
|||
} |
|||
|
|||
if (_isTextChangedFromUI && !parsedTextIsValid) |
|||
{ |
|||
// Text input was made from the user and the text
|
|||
// repesents an invalid value. Disable the spinner in this case.
|
|||
if (Spinner != null) |
|||
{ |
|||
Spinner.ValidSpinDirection = ValidSpinDirections.None; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
SetValidSpinDirection(); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
_isSyncingTextAndValueProperties = false; |
|||
} |
|||
return parsedTextIsValid; |
|||
} |
|||
} |
|||
|
|||
public class UpDownValueChangedEventArgs<T> : RoutedEventArgs |
|||
{ |
|||
public UpDownValueChangedEventArgs(RoutedEvent routedEvent, T oldValue, T newValue) : base(routedEvent) |
|||
{ |
|||
OldValue = oldValue; |
|||
NewValue = newValue; |
|||
} |
|||
|
|||
public T OldValue { get; } |
|||
public T NewValue { get; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue