拖动它






4.29/5 (6投票s)
像 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
)。
使用正则表达式解析字符串,并在附加属性中处理。
代码
包含公共附加属性 DragEnabled
、Precision
、InvertYAxix
的类 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