达到限制时禁用向上或向下按钮的 UpDown 控件

UpDown control that disables up or down button when limit is reached

我的问题是基于对这个 post 的回答之一: Where is the WPF Numeric UpDown control? Squirrel.Downy先生回答。 我想要完成的是一个数字向上控件,当按下按钮的时间更长时,increases/decreases 的数量更大,否则 increase/decrease 是正常数量。此外,当达到 max/min 时,按钮应禁用。 我有一个基于 Slider 的样式,它包含 2 个 HoldButton 类型的按钮(up/down,从 RepeatButton 派生)和一个用于值的只读 TextBlock。 在 HoldButton 中,我有一个 ICommand 的 2 个依赖属性。这些是 ClickAndHoldCommand 和 ClickCommand,它们根据鼠标按钮按下的长度从 OnPreviewMouseLeftButtonDown() 或 OnPreviewMouseLeftButtonUp() 执行。在 xaml 中,它们分别绑定到 Slider.IncreaseLarge 和 Slider.IncreaseSmall。 如何在达到最大值时禁用向上按钮并在达到最小值时禁用向下按钮?困难在于,例如,当我禁用滑块时,向上鼠标事件不再起作用...

<Style TargetType="{x:Type Slider}" x:Key="NumericUpDown">
    <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>
                    <Rectangle RadiusX="10" RadiusY="10" Stroke="{StaticResource SolidBrushLightGrey}" Fill="Black" StrokeThickness="1" />
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <TextBlock Grid.Row="0" x:Name="ControlName" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" />
                        <TextBlock Grid.Row="1" x:Name="ControlUnits" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" />
                        <usercontrols:HoldButton Grid.Row="2" Delay="250" Interval="375" 
                                                 EnableClickHold="True" 
                                                 ClickAndHoldCommand="{x:Static Slider.IncreaseLarge}" 
                                                 ClickCommand="{x:Static Slider.IncreaseSmall}"
                                                 MaxWidth="60" Height="60" Width="60" Style="{StaticResource ButtonStyleGeneral}" Content="+">
                        </usercontrols:HoldButton>
                        
                        <TextBlock Grid.Row="3" x:Name="Temperature" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" FontSize="30" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Value, StringFormat=N1}" />

                        <usercontrols:HoldButton Grid.Row="4" Delay="250" Interval="375" 
                                                 EnableClickHold="True" 
                                                 ClickAndHoldCommand="{x:Static Slider.DecreaseLarge}" 
                                                 ClickCommand="{x:Static Slider.DecreaseSmall}" 
                                                 MaxWidth="60" Height="60" Width="60" Style="{StaticResource ButtonStyleGeneral}" Content="-">
                        </usercontrols:HoldButton>
                        
                        <Border x:Name="TrackBackground" Visibility="Collapsed">
                            <Rectangle x:Name="PART_SelectionRange" Visibility="Collapsed" />
                        </Border>
                        <Thumb x:Name="Thumb" Visibility="Collapsed" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
public partial class HoldButton : RepeatButton
{
    private bool buttonIsHeldPressed;

    public HoldButton()
    {
        InitializeComponent();
        buttonIsHeldPressed = false;

        this.PreviewMouseLeftButtonUp += OnPreviewMouseLeftButtonUp;
        
        // RepeatButton fires click event repeatedly while button is being pressed!
        this.Click += HoldButton_Click;
    }

    private void HoldButton_Click(object sender, RoutedEventArgs e)
    {
        Trace.WriteLine("HoldButton_Click()");

        if (EnableClickHold)
        {
            if (numberButtonRepeats > 2)
            {
                ClickAndHoldCommand.Execute(this.CommandParameter);
                e.Handled = true;
                buttonIsHeldPressed = true;
            }

            numberButtonRepeats++;
        }
    }

    public bool EnableClickHold
    {
        get { return (bool)GetValue(EnableClickHoldProperty); }
        set { SetValue(EnableClickHoldProperty, value); }
    }

    public ICommand ClickAndHoldCommand
    {
        get { return (ICommand)GetValue(ClickAndHoldCommandProperty); }
        set { SetValue(ClickAndHoldCommandProperty, value); }
    }

    public ICommand ClickCommand
    {
        get { return (ICommand)GetValue(ClickCommandProperty); }
        set { SetValue(ClickCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ClickAndHoldCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ClickAndHoldCommandProperty =
        DependencyProperty.Register("ClickAndHoldCommand", typeof(ICommand), typeof(HoldButton), new UIPropertyMetadata(null));

    // Using a DependencyProperty as the backing store for ClickCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ClickCommandProperty =
        DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(HoldButton), new UIPropertyMetadata(null));

    // Using a DependencyProperty as the backing store for EnableClickHold.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EnableClickHoldProperty =
        DependencyProperty.Register("EnableClickHold", typeof(bool), typeof(HoldButton), new PropertyMetadata(false));

    // Using a DependencyProperty as the backing store for MillisecondsToWait.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MillisecondsToWaitProperty =
        DependencyProperty.Register("MillisecondsToWait", typeof(int), typeof(HoldButton), new PropertyMetadata(0));

    public int MillisecondsToWait
    {
        get { return (int)GetValue(MillisecondsToWaitProperty); }
        set { SetValue(MillisecondsToWaitProperty, value); }
    }

    private int numberButtonRepeats;

    private void OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (EnableClickHold)
        {
            numberButtonRepeats = 0;

            if(!buttonIsHeldPressed)
            {
                ClickCommand?.Execute(this.CommandParameter);
            }

            buttonIsHeldPressed = false;
        }
    }

    private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Trace.WriteLine("OnPreviewMouseLeftButtonDown()");
        if (EnableClickHold)
        {
            // When numberButtonRepeats comes above 1 then the button is considered to be pressed long
            if (numberButtonRepeats > 1)
            {
                ClickAndHoldCommand?.Execute(this.CommandParameter);
            }

            numberButtonRepeats++;
        }
    }
}
<UserControl x:Class="Views.TemperatureControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Views" 
             xmlns:cal="http://www.caliburnproject.org"
             xmlns:controls="clr-namespace:UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="250" d:DesignWidth="150">
    <Slider Minimum="{Binding MinimumTemperature}" 
            Maximum="{Binding MaximumTemperature}" 
            SmallChange="{Binding TemperatureTinySteps}" 
            LargeChange="{Binding TemperatureSmallSteps}" 
            Value="{Binding ControlValue}" 
            Style="{StaticResource NumericUpDown}" />
</UserControl>

您应该扩展 Slider 控件并在那里实现逻辑。
最后命名 RepeatButton 元素并将 Style 移动到 Generic.xaml 文件。

public class CustomSlider : Slider
{
  static CustomSlider()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomSlider), new FrameworkPropertyMetadata(typeof(CustomSlider)));
  }

  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();
    this.PART_IncreaseButton = GetTemplateChild(nameof(this.PART_IncreaseButton)) as UIElement;
    this.PART_DecreaseButton = GetTemplateChild(nameof(this.PART_DecreaseButton)) as UIElement;
  }

  protected override void OnValueChanged(double oldValue, double newValue)
  {
    base.OnValueChanged(oldValue, newValue);

    if (this.PART_IncreaseButton == null 
      || this.PART_DecreaseButton == null)
    {
      return;
    }

    this.PART_IncreaseButton.IsEnabled = newValue < this.Maximum;
    this.PART_DecreaseButton.IsEnabled = newValue > this.Minimum;
  }

  private UIElement PART_IncreaseButton { get; set; }
  private UIElement PART_DecreaseButton { get; set; }
}

Generic.xaml
将 HoldButton 元素命名为 "PART_IncreaseButton""PART_IncreaseButton",以便您可以在模板中轻松找到它们。

<Style TargetType="{x:Type CustomSlider}">
  <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>
          <Rectangle RadiusX="10" RadiusY="10" Stroke="{StaticResource SolidBrushLightGrey}" Fill="Black" StrokeThickness="1" />
          <Grid>
            <Grid.RowDefinitions>
              <RowDefinition Height="Auto" />
              <RowDefinition Height="Auto" />
              <RowDefinition Height="Auto" />
              <RowDefinition Height="Auto" />
              <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
              
            <TextBlock Grid.Row="0" x:Name="ControlName" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" />
            <TextBlock Grid.Row="1" x:Name="ControlUnits" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" />
              
            <usercontrols:HoldButton x:Name="PART_IncreaseButton" 
                                     Grid.Row="2" 
                                     Delay="250" 
                                     Interval="375" 
                                     EnableClickHold="True" 
                                     ClickAndHoldCommand="{x:Static Slider.IncreaseLarge}" 
                                     ClickCommand="{x:Static Slider.IncreaseSmall}"
                                     MaxWidth="60" 
                                     Height="60" Width="60" 
                                     Style="{StaticResource ButtonStyleGeneral}" 
                                     Content="+" />


            <TextBlock Grid.Row="3" x:Name="Temperature" Style="{StaticResource LabelStyle}" Margin="0,5,0,0" FontSize="30" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Value, StringFormat=N1}" />

            <usercontrols:HoldButton x:Name="PART_DecreaseButton" 
                                     Grid.Row="4" 
                                     Delay="250" 
                                     Interval="375" 
                                     EnableClickHold="True" 
                                     ClickAndHoldCommand="{x:Static Slider.DecreaseLarge}" 
                                     ClickCommand="{x:Static Slider.DecreaseSmall}" 
                                     MaxWidth="60" 
                                     Height="60" Width="60" 
                                     Style="{StaticResource ButtonStyleGeneral}" 
                                     Content="-" />


              <Border x:Name="TrackBackground" Visibility="Collapsed">
              <Rectangle x:Name="PART_SelectionRange" Visibility="Collapsed" />
            </Border>
            <Thumb x:Name="Thumb" Visibility="Collapsed" />
          </Grid>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>