65.9K
CodeProject 正在变化。 阅读更多。
Home

拖动它

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (6投票s)

2014年1月24日

CPOL

2分钟阅读

viewsIcon

27921

downloadIcon

645

像 Expression Design & Blend 中一样上下拖动数值

引言

许多设计程序,例如 Microsoft Expression Blend&Design,都具有TextBox控件,您可以通过拖动数值来增加或减少数字。



这些控件在调整设计元素大小以及大小随鼠标移动时尤其直观。
本文档说明了如何使用附加属性在任何 TextBox 中实现此功能。

也可作为 NuGet 包 提供。

示例应用程序

 

包含的测试项目演示了拖动功能。

示例应用程序基本上是一个货币计算器。
输入或拖动单个输入字段的货币时,将根据该货币计算出相应的货币。
每个 TextBox 都有不同的 Precision,以演示设置精度的能力。
添加了一个 CheckBox 以启用或禁用示例应用程序的整体拖动功能。

向上/向下
请注意,TextBox(顶部和左侧)的 Y 轴是反转的。
Microsoft Expression 程序会在鼠标向下拖动时增加值,这与一般的 UI 布局很好地相关,在 UI 布局中,Y 轴指向下方。
此实现会在向下拖动时减少值(默认),但可以反转 Y 轴。

标记

 

示例 MainWindow.xaml 以及如何设置附加属性:

<Window x:Class="DragItTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="clr-namespace:NMT.Wpf.Controls;assembly=DragIt"
        Title="DragIt Test" Height="299" Width="277" ResizeMode="NoResize" Icon="DragIt.ico">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox Grid.Row="1" Grid.Column="0" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Dkk, StringFormat=DKK {0:f0} Kr.}"  
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}"
                 controls:DragIt.Precision="5"
                 controls:DragIt.InvertYAxis="True"/>
        <TextBox  Grid.Row="1" Grid.Column="1" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Eur, StringFormat=EUR {0:f1} €}" 
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}" 
                 controls:DragIt.Precision=".5"/>
        <TextBox Grid.Row="3" Grid.Column="0" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Usd, StringFormat=USD {0:f0} $}" 
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}" 
                 controls:DragIt.Precision="2" />
        <TextBox   Grid.Row="3" Grid.Column="1" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Gbp, StringFormat=GBP {0:f1} £}"
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}" 
                 controls:DragIt.Precision=".2" />
        <TextBox Grid.Row="5" Grid.Column="0" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Yen, StringFormat=YEN {0:f0} ¥}" 
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}" 
                 controls:DragIt.Precision="1" />
        <TextBox Grid.Row="5" Grid.Column="1" Height="23" Margin="3,0,3,3" Width="124"
                 Text="{Binding Cad, StringFormat=CAD {0:f2} $}" 
                 controls:DragIt.DragEnabled="{Binding ElementName=EnableDragging, Path=IsChecked}" 
                 controls:DragIt.Precision=".01" />
        <!-- Labels -->
        <Label Grid.Row="0" Grid.Column="0" Content="Precision 5" Margin="3,3,0,0" />
        <Label Grid.Row="0" Grid.Column="1"  Content="Precision 0.5" Margin="3,3,0,0" />
        <Label Grid.Row="2" Grid.Column="0"  Content="Precision 2" Margin="3,3,0,0" />
        <Label Grid.Row="2" Grid.Column="1"  Content="Precision 0.2" Margin="3,3,0,0" />
        <Label Grid.Row="4" Grid.Column="0" Content="Precision 1" Margin="3,3,0,0" />
        <Label Grid.Row="4" Grid.Column="1" Content="Precision 0.1" Margin="3,3,0,0" />
        <CheckBox Grid.Row="6" Grid.Column="0"  x:Name="EnableDragging" IsChecked="True" Margin="3" 
                  Content="Enable dragging" />
        <Rectangle Grid.Row="6" Grid.Column="1" Grid.RowSpan="2"  Fill="{StaticResource MoveBrush}" Margin="3"
                   RenderTransformOrigin="0.5,0.5">
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform Angle="-25.843"/>
                    <TranslateTransform/>
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Window> 

Text 属性绑定到视图模型,并使用 StringFormat 选项进行格式化。
使用字符串格式会使事情变得复杂,因为该值必须作为字符串解析,而不是作为值(double)。
使用正则表达式解析字符串,并在附加属性中处理。

代码

包含公共附加属性 DragEnabledPrecisionInvertYAxix 的类 DragIt

 

大多数受保护的属性用于记住状态和值。
 

DragIt 类:

 

 

  /// <summary>
  /// DragIt class
  /// Used to extend the functionality of TextBoxes so that a value can be altered by
  /// dragging up/down/left/right.
  /// </summary>
  public class DragIt
  {
    #region -- Members --
    /// <summary>
    /// The number style
    /// </summary>
    private const NumberStyles numberStyle =
      NumberStyles.AllowThousands | NumberStyles.Float | NumberStyles.AllowCurrencySymbol;
    /// <summary>
    /// The number pattern
    /// </summary>
    private const string numberPattern = @"[-+]?[0-9]*[.,]?[0-9]+";
    /// <summary>
    /// Sets the cursor position.
    /// </summary>
    /// <param name="x">The x.</param>
    /// <param name="y">The y.</param>
    /// <returns><c>true</c> if success, <c>false</c> otherwise.</returns>
    [DllImport("User32.dll")]
    private static extern bool SetCursorPos(int x, int y);
 
    #endregion
 
    #region -- Properties --
 
    #region InvertYAxis
    /// <summary>
    /// The invert y axis property
    /// </summary>
    public static readonly DependencyProperty InvertYAxisProperty = DependencyProperty.RegisterAttached(
      "InvertYAxis", typeof (bool), typeof (DragIt), new PropertyMetadata(default(bool)));
 
    /// <summary>
    /// Sets the invert y axis.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">if set to <c>true</c> [value].</param>
    public static void SetInvertYAxis(DependencyObject element, bool value)
    {
      element.SetValue(InvertYAxisProperty, value);
    }
 
    /// <summary>
    /// Gets the invert y axis.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    public static bool GetInvertYAxis(DependencyObject element)
    {
      return (bool) element.GetValue(InvertYAxisProperty);
    }
    #endregion
 
    #region Pressed
    /// <summary>
    /// The pressed property
    /// </summary>
    protected static readonly DependencyProperty PressedProperty =
      DependencyProperty.RegisterAttached("Pressed", typeof(Boolean), typeof(DragIt), 
      new PropertyMetadata(default(bool)));
 
    /// <summary>
    /// Sets the pressed.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">if set to <c>true</c> [value].</param>
    protected static void SetPressed(DependencyObject element, bool value)
    {
      element.SetValue(PressedProperty, value);
    }
 
    /// <summary>
    /// Gets if pressed.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns><c>true</c> if pressed, <c>false</c> otherwise.</returns>
    protected static bool GetPressed(DependencyObject element)
    {
      return (bool)element.GetValue(PressedProperty);
    }
    #endregion
 
    #region DragEnabled
 
    /// <summary>
    /// The drag enabled property
    /// </summary>
    public static readonly DependencyProperty DragEnabledProperty =
      DependencyProperty.RegisterAttached("DragEnabled", typeof(Boolean), typeof(DragIt), 
      new FrameworkPropertyMetadata(OnDragEnabledChanged));
 
    /// <summary>
    /// Sets the drag enabled.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetDragEnabled(DependencyObject element, Boolean value)
    {
      element.SetValue(DragEnabledProperty, value);
    }
 
    /// <summary>
    /// Gets the drag enabled.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Boolean.</returns>
    public static Boolean GetDragEnabled(DependencyObject element)
    {
      return (Boolean)element.GetValue(DragEnabledProperty);
    }
 

    #endregion
 
    #region Precision
    /// <summary>
    /// The precision property
    /// </summary>
    public static readonly DependencyProperty PrecisionProperty =
      DependencyProperty.RegisterAttached("Precision", typeof(double), 
      typeof(DragIt), new PropertyMetadata(default(double)));
 
    /// <summary>
    /// Sets the precision.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetPrecision(DependencyObject element, double value)
    {
      element.SetValue(PrecisionProperty, value);
    }
 
    /// <summary>
    /// Gets the precision.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>System.Double.</returns>
    public static double GetPrecision(DependencyObject element)
    {
      return (double)element.GetValue(PrecisionProperty);
    }
 
    #endregion
 
    #region StartPoint
    /// <summary>
    /// The start point property
    /// </summary>
    protected static readonly DependencyProperty StartPointProperty =
      DependencyProperty.RegisterAttached("StartPoint", typeof(Point), typeof(DragIt), 
      new PropertyMetadata(default(Point)));
 
    /// <summary>
    /// Sets the start point.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    protected static void SetStartPoint(UIElement element, Point value)
    {
      element.SetValue(StartPointProperty, value);
    }
 
    /// <summary>
    /// Gets the start point.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Point.</returns>
    protected static Point GetStartPoint(UIElement element)
    {
      return (Point)element.GetValue(StartPointProperty);
    }
    #endregion
 
    #region EndPoint
    /// <summary>
    /// The end point property
    /// </summary>
    protected static readonly DependencyProperty EndPointProperty = 
      DependencyProperty.RegisterAttached("EndPoint", typeof(Point), typeof(DragIt), 
      new PropertyMetadata(default(Point)));
 
    /// <summary>
    /// Sets the end point.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    protected static void SetEndPoint(DependencyObject element, Point value)
    {
      element.SetValue(EndPointProperty, value);
    }
 
    /// <summary>
    /// Gets the end point.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Point.</returns>
    protected static Point GetEndPoint(DependencyObject element)
    {
      return (Point)element.GetValue(EndPointProperty);
    }
    #endregion
 
    #endregion
 
    #region -- CallBacks --
 
    /// <summary>
    /// Callback for the OnDragEnabled state change event.
    /// </summary>
    /// <param name="obj">The object.</param>
    /// <param name="e">The instance containing the event data.</param>
    private static void OnDragEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
      var control = (TextBox)obj;
      if (!((bool)e.NewValue))
      {
        control.PreviewMouseLeftButtonDown -= ControlOnPreviewMouseLeftButtonDown;
        control.PreviewMouseMove -= ControlOnPreviewMouseMove;
        control.PreviewMouseLeftButtonUp -= ControlPreviewMouseLeftButtonUp;
        control.Cursor = null;
        return;
      }
      control.PreviewMouseLeftButtonDown += ControlOnPreviewMouseLeftButtonDown;
      control.PreviewMouseMove += ControlOnPreviewMouseMove;
      control.PreviewMouseLeftButtonUp += ControlPreviewMouseLeftButtonUp;
      using (var stream = new MemoryStream(Resources.Expression_move))
        control.Cursor = new Cursor(stream);
    }
 
    /// <summary>
    /// Callback for the preview mouse left button up.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The instance containing the event data.</param>
    private static void ControlPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
      var source = sender as TextBox;
      if (source == null) return;
      SetPressed(source, false);
    }
 
    /// <summary>
    /// Callback for the preview mouse left button down.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="mouseButtonEventArgs">The instance containing the event data.</param>
    private static void ControlOnPreviewMouseLeftButtonDown(object sender,
      MouseButtonEventArgs mouseButtonEventArgs)
    {
      var source = sender as TextBox;
      if (source == null) return;
      var position = source.PointToScreen(Mouse.GetPosition(source));
      SetStartPoint(source, position);
      SetEndPoint(source, new Point(0,0));
      SetPressed(source, true);
    }
 
    /// <summary>
    /// Callback for the preview mouse move.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="mouseEventArgs">The instance containing the event data.</param>
    private static void ControlOnPreviewMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
      var source = sender as TextBox;
      if (source == null || !GetPressed(source)) return;
      // Get value
      var value = ParseValue(source.Text);
      // Get mouse position
      var point = source.PointToScreen(Mouse.GetPosition(source));
      var deltaX = GetEndPoint(source).X + point.X - GetStartPoint(source).X;
      var deltaY = GetEndPoint(source).Y + point.Y - GetStartPoint(source).Y;
      var vector = new Vector(deltaX, deltaY);
      // Test against minimum dragging distance.
      if (vector.Length < SystemParameters.MinimumHorizontalDragDistance)
      {
        SetEndPoint(source, new Point(deltaX, deltaY));
        return;
      }
      // Set value
      var invert = GetInvertYAxis(source) ? -1 : 1;
      source.Text = (value + ((int)((deltaX - deltaY * invert) / SystemParameters.MinimumHorizontalDragDistance)) * 
        GetPrecision(source)).ToString(CultureInfo.InvariantCulture);
      // Reset mouse position
      SetPosition(GetStartPoint(source));
      SetEndPoint(source, new Point(0,0));
    }
 
    /// <summary>
    /// Sets the position.
    /// </summary>
    /// <param name="point">The point.</param>
    private static void SetPosition(Point point)
    {
      SetCursorPos((int)point.X, (int)point.Y);
    }
 
    /// <summary>
    /// Parses the value.
    /// </summary>
    /// <param name="number">The number.</param>
    /// <returns>System.Double.</returns>
    private static double ParseValue(String number)
    {
      string match = Regex.Match(number, numberPattern).Value;
      double retVal;
      var success = Double.TryParse(match, numberStyle, CultureInfo.InvariantCulture, out retVal);
      if (!success)
        retVal = 0;
      return retVal;
    }
 
    #endregion
 
  } 

 

 

关注点

正则表达式
使用正则表达式解析 StringFormat 的结果需要一些注意。
我最终使用 http://regexpal.com/ 测试输入和结果。

系统参数
我发现 SystemParameters 非常有用。
它包含大量系统设置,您经常会发现自己难以找到它们。

 

DragIt
DragIt 附加属性当前适用于 TextBoxes,但也可以扩展到涵盖其他控件。

历史

版本 1.0.1 - 拖动时隐藏光标。
初始版本 1.0.0

 

© . All rights reserved.