【问题标题】:UpDown control that disables up or down button when limit is reached达到限制时禁用向上或向下按钮的 UpDown 控件
【发布时间】:2022-01-06 03:25:42
【问题描述】:

我的问题是基于对这篇文章的回答之一: Where is the WPF Numeric UpDown control? Squirrel.Downy 先生回答。 我想要完成的是一个数字 updown 控件,当按钮被按下更长的时间时,它会以更大的量增加/减少,否则增加/减少是正常量。此外,当达到最大值/最小值时,按钮应禁用。 我有一个基于 Slider 的样式,其中包含 2 个 HoldButton 类型的按钮(向上/向下,从 RepeatButton 派生)和一个只读的 TextBlock 值。 在 HoldButton 中,我有 2 个 ICommand 依赖项属性。这些是 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控件并在新类中实现逻辑。
  • 我不确定触发器是否会起作用,因为值、最小值和最大值都是浮动的
  • 您必须扩展 Slider。在 OnApplyTemplate 覆盖中,您抓住向上和向下的重复按钮。然后为 Value 属性注册一个属性更改回调。在此回调中,您可以根据当前值与最小值和最大值相比禁用向上或向下按钮。
  • 你能帮我一些关于 OnApplyTemplate() 的事情吗?我必须把这个放在哪里?我将 ValueChanged 事件处理程序添加到部分类,但我需要到达向上和向下按钮。我喜欢这样做的 mvvm 方式...
  • 请看我的回答。它 100% 符合 MVVM。

标签: c# wpf mvvm numericupdown


【解决方案1】:

您应该扩展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>

【讨论】:

  • 感谢您的回答,但我在 OnValueChanged() 中遇到 NullReferenceException 问题。似乎 OnValueChanged() 处理程序在 OnApplyTemplate() 之前执行,因此按钮对象为空。
  • 我没有测试过代码。它也是在文本编辑器中编写的。但我可以保证它有效。我添加了一个空检查。
  • 为按钮添加了空引用检查,然后它可以完美运行。感谢您的时间和帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
  • 2011-07-21
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多