Let's enjoy some hacky things:
Here is a Style
of Slider
as a NumericUpDown
, simple and easy to use, without any hidden code or third party library.
<Style TargetType="{x:Type Slider}">
<Style.Resources>
<Style x:Key="RepeatButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="Focusable" Value="false" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Padding" Value="0" />
<Setter Property="Width" Value="20" />
</Style>
</Style.Resources>
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false" />
<Setter Property="SmallChange" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.RowSpan="2" Height="Auto"
Margin="0" Padding="0"
VerticalAlignment="Stretch" VerticalContentAlignment="Center"
Text="{Binding Value, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}">
<TextBox.InputBindings>
<KeyBinding Gesture="Up" Command="{x:Static Slider.IncreaseSmall}" />
<KeyBinding Gesture="Down" Command="{x:Static Slider.DecreaseSmall}" />
<KeyBinding Gesture="PageUp" Command="{x:Static Slider.IncreaseLarge}" />
<KeyBinding Gesture="PageDown" Command="{x:Static Slider.DecreaseLarge}" />
</TextBox.InputBindings>
</TextBox>
<RepeatButton Grid.Row="0" Grid.Column="1"
Command="{x:Static Slider.IncreaseSmall}"
Style="{StaticResource RepeatButtonStyle}">
<Path Data="M4,0 L0,4 8,4 Z" Fill="Black" />
</RepeatButton>
<RepeatButton Grid.Row="1" Grid.Column="1"
Command="{x:Static Slider.DecreaseSmall}"
Style="{StaticResource RepeatButtonStyle}">
<Path Data="M0,0 L4,4 8,0 Z" Fill="Black" />
</RepeatButton>
<Border x:Name="TrackBackground" Visibility="Collapsed">
<Rectangle x:Name="PART_SelectionRange" Visibility="Collapsed" />
</Border>
<Thumb x:Name="Thumb" Visibility="Collapsed" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you want apply input restrictions on TextBox
indeed, try this:
public static class InputLimit
{
public static string GetDecimalValueProxy(TextBox obj) => (string)obj.GetValue(DecimalValueProxyProperty);
public static void SetDecimalValueProxy(TextBox obj, string value) => obj.SetValue(DecimalValueProxyProperty, value);
// Using a DependencyProperty as the backing store for DecimalValueProxy. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DecimalValueProxyProperty =
DependencyProperty.RegisterAttached("DecimalValueProxy", typeof(string), typeof(InputLimit),
new FrameworkPropertyMetadata("0", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceDecimalValueProxy));
private static object CoerceDecimalValueProxy(DependencyObject d, object baseValue)
{
if (decimal.TryParse(baseValue as string, out _)) return baseValue;
return DependencyProperty.UnsetValue;
}
}
And modify xaml TextBox
part to:
<TextBox Grid.RowSpan="2" Height="Auto"
Margin="0" Padding="0"
VerticalAlignment="Stretch" VerticalContentAlignment="Center"
local:InputLimit.DecimalValueProxy="{Binding Value, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
Text="{Binding RelativeSource={RelativeSource Self}, Path=(local:InputLimit.DecimalValueProxy), Mode=TwoWay}">
EDIT:
But the above code has a nasty bug...
Look at the code:
Text="{Binding Value, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
The TextBox
will update value after it lost focus, but if you add UpdateSourceTrigger=PropertyChanged
in Binding
, you will cannot type dot(.) into TextBox
.
You can use next converter code to fix it:
public class SafeDotConverter : MarkupExtension, IValueConverter
{
private bool hasDot;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dbl = (double)value;
if (hasDot && Math.Truncate(dbl) == dbl)
{
return $"{dbl}.";
}
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string str)
{
hasDot = str.EndsWith(".");
if (double.TryParse(str, out var val))
return val;
}
return DependencyProperty.UnsetValue;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Use in Xaml:
Text="{Binding Value, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={local:SafeDotConverter}}"