我正在尝试设计一个 ControlTemplate,使 Slider 看起来像 NumericUpDown 控件。
它有一个三列的网格,其中有一个递减按钮、一个显示当前值的 TextBlock 和一个递增按钮。
是的,我知道 WPF 中有一些 NumericUpDown 控件的模仿,它们在某些库中使用代码隐藏或自定义控件,但我正在寻找一种通过向按钮的 Click 事件添加 EventTriggers 来增加和减少值的偷偷摸摸的方法在模板上。
我可以通过调用这些 EventTriggers 中的 ValueChanged 或其他滑块事件来实现我的目标吗?
注意:带有 DoubleAnimation 的 Storyboard 几乎可以工作,但是,唉,由于 Freezable 问题,我无法将动画的“To”属性绑定到 Slider 本身的递增或递减 Value 属性。
在这种情况下,我建议您创建一个派生自 RangeBase 的自定义控件。
实现示例:
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace WpfCustomControlsCore
{
public class NumericUpDown : RangeBase
{
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
SmallChangeProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(1.0));
MaximumProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(100.0));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new(UpValueCommand, OnUpValueExecute, OnUpDownValueCanExecute));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new(DownValueCommand, OnDownValueExecute, OnUpDownValueCanExecute));
}
private static void OnUpValueExecute(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown numericUpDown = (NumericUpDown)sender;
numericUpDown.Value += numericUpDown.SmallChange;
}
private static void OnUpDownValueCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
NumericUpDown numericUpDown = (NumericUpDown)sender;
e.CanExecute = numericUpDown.SmallChange > 0.0;
}
private static void OnDownValueExecute(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown numericUpDown = (NumericUpDown)sender;
numericUpDown.Value -= numericUpDown.SmallChange;
}
public static readonly RoutedUICommand UpValueCommand = new("Up Value", nameof(UpValueCommand), typeof(NumericUpDown));
public static readonly RoutedUICommand DownValueCommand = new("Down Value", nameof(DownValueCommand), typeof(NumericUpDown));
/// <summary>Duplicate property, for passing the value back from Value to the source.</summary>
public double Value2
{
get => (double)GetValue(Value2Property);
set => SetValue(Value2Property, value);
}
// Using a DependencyProperty as the backing store for Value2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty Value2Property =
DependencyProperty.Register(
nameof(Value2),
typeof(double),
typeof(NumericUpDown),
new FrameworkPropertyMetadata(0.0)
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = async (d, e) =>
{
NumericUpDown nud = (NumericUpDown)d;
if (!e.NewValue.Equals(nud.Value))
{
await nud.Dispatcher.BeginInvoke(() => nud.Value2 = nud.Value, System.Windows.Threading.DispatcherPriority.Send);
}
}
});
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
SetValue(Value2Property, newValue);
}
}
}
在主题\Generic.xaml中
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="25" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Value,
RelativeSource={RelativeSource TemplatedParent},
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" FontSize="10"
Command="{x:Static local:NumericUpDown.UpValueCommand}">
<TextBlock Text="▲"
VerticalAlignment="Top"/>
</Button>
<Button Grid.Row="1"
FontSize="10"
Command="{x:Static local:NumericUpDown.DownValueCommand}">
<TextBlock Text="▼"
VerticalAlignment="Top"/>
</Button>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
使用示例:
public class DoubleItem
{
public double Number { get; set; }
}
<ContentControl VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" >
<Border Background="LightGreen" BorderBrush="Green" BorderThickness="2"
Padding="20" Margin="10">
<StackPanel MinWidth="100">
<FrameworkElement.DataContext>
<local:DoubleItem Number="-3"/>
</FrameworkElement.DataContext>
<TextBlock Text="{Binding Number}" Margin="5"/>
<custctrl:NumericUpDown Margin="5"
Value="{Binding Number}"
Maximum="10" Minimum="5"
/>
<TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
</StackPanel>
</Border>
<Border Background="LightGreen" BorderBrush="Green" BorderThickness="2"
Padding="20" Margin="10">
<StackPanel MinWidth="100">
<FrameworkElement.DataContext>
<local:DoubleItem Number="-3"/>
</FrameworkElement.DataContext>
<TextBlock Text="{Binding Number}" Margin="5"/>
<custctrl:NumericUpDown Margin="5"
Value="{Binding Number}"
Maximum="10" Minimum="5"
Value2="{Binding Number}"/>
<TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
</StackPanel>
</Border>
</StackPanel>
</ContentControl>