关于Thumb控件
在MSDN中文档中有这样的描述: "Represents a control that can be dragged by the user."
Thumb控件提供了一些事件用于管理拖拽操作:
DragStarted
当用户按下鼠标左键,Thumb控件获得焦点并捕获鼠标,触发此事件。DragDelta
当Thumb控件获得焦点并捕获鼠标时,此事件会触发多次。DragCompleted
当控件失去鼠标捕获时,触发此事件
实现拖拽功能
从简单的布局开始
<Canvas>
<Thumb
Canvas.Left="100"
Canvas.Top="50" Width="100"
Height="100" />
</Canvas>
运行结果:
现在这个控件并不能拖动。
处理DragDelta
事件
<Thumb
Canvas.Left="100"
Canvas.Top="50"
Width="100"
Height="100"
DragDelta="Thumb_DragDelta"
/>
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Control c = sender as Control;
if (c != null)
{
double x = Canvas.GetLeft(c);
double y = Canvas.GetTop(c);
x = double.IsNaN(x) ? 0 : x;
y = double.IsNaN(y) ? 0 : y;
Canvas.SetLeft(c, x + e.HorizontalChange);
Canvas.SetTop(c, y + e.VerticalChange);
}
}
编译运行,现在这个控件可以拖动了。
拖动任意类型的控件
经过前面的操作,可以拖动Thumb
控件,并且实现方式也很简单, 但是在实际工作中并没有任何作用。
<Canvas>
<ContentControl
x:Name="cc"
Canvas.Left="100"
Canvas.Top="50"
Width="100"
Height="100">
<Grid>
<Thumb DragDelta="Thumb_DragDelta"
DataContext="{Binding ElementName=cc}"/>
<Ellipse Fill="Blue"/>
</Grid>
</ContentControl>
</Canvas>
- 我们用
ContentControl
控件包裹 Thumb 和 Ellipse 控件 - Ellipse 表示我们要拖动的控件, Thumb 用于实现拖动操作
之前的Thumb_DragDelta
函数直接从sender中获取要拖动的控件,但是现在我们要拖动的是ContentControl控件,因此处理代码要稍作改动。
private void Thumb_DragDelta(object sender,
System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Control thb = sender as Control;
if (thb == null)
{
return;
}
Control c = thb.DataContext as Control;
if (c != null)
{
double x = Canvas.GetLeft(c);
double y = Canvas.GetTop(c);
x = double.IsNaN(x) ? 0 : x;
y = double.IsNaN(y) ? 0 : y;
Canvas.SetLeft(c, x + e.HorizontalChange);
Canvas.SetTop(c, y + e.VerticalChange);
}
}
从sender的DataContext中获取要拖动的控件,然后设置其位置..., DataContext我们在XAML代码中做了绑定所以可以正确获取到内容DataContext="{Binding ElementName=cc}"
编译运行:
IsHitTestVisible
运行前面的程序,发现当鼠标在蓝色椭圆上时,并不能拖动控件。
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
因为Ellipse阻断了鼠标事件,导致鼠标事件没能传Thumb处理.IsHitTestVisible="False"
可以让控件不处理鼠标事件。
隐藏Thumb控件
显示Thumb控件,显然不是我们想要的, 第一想法是将Thumb的Background设置为透明
<Thumb DragDelta="Thumb_DragDelta"
Background="Transparent"
DataContext="{Binding ElementName=cc}"/>
但好像并没有什么用...经过测试Opacity="0"
可以工作
<Thumb DragDelta="Thumb_DragDelta"
Opacity="0"
DataContext="{Binding ElementName=cc}"/>
使用模板隐藏Thumb控件
<Thumb DragDelta="Thumb_DragDelta"
DataContext="{Binding ElementName=cc}">
<Thumb.Template>
<ControlTemplate>
<Rectangle Fill="Transparent"></Rectangle>
</ControlTemplate>
</Thumb.Template>
</Thumb>
设置Opacity可以隐藏Thumb控件,但是更推荐这种用法。
实现Resize功能
Resize比较复杂,但处理方式和拖拽类似。
<Grid DataContext="{Binding ElementName=cc}">
<Thumb DragDelta="Thumb_DragDelta" Cursor="SizeAll">
<Thumb.Template>
<ControlTemplate>
<Rectangle Fill="Transparent"></Rectangle>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
</Grid>
用8个Thumb围住Ellipse,我们把DataContext="{Binding ElementName=cc}"
移到了Grid控件上,因为DataContext
的继承性,不用每个Thumb都写一遍。
private void Thumb_DragDelta_Resize(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Control thumb = sender as Control;
Control designerItem = thumb.DataContext as Control;
if (designerItem != null)
{
double deltaVertical, deltaHorizontal;
switch (thumb.VerticalAlignment)
{
case VerticalAlignment.Bottom:
deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
designerItem.Height -= deltaVertical;
break;
case VerticalAlignment.Top:
deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);
designerItem.Height -= deltaVertical;
break;
default:
break;
}
switch (thumb.HorizontalAlignment)
{
case HorizontalAlignment.Left:
deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);
designerItem.Width -= deltaHorizontal;
break;
case HorizontalAlignment.Right:
deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
designerItem.Width -= deltaHorizontal;
break;
default:
break;
}
}
e.Handled = true;
}
Resize的事件处理函数中,根据thumb.HorizontalAlignment和thumb.VerticalAlignment设置控件的位置和大小,四个角上的Thumb两个属性都有设置,两个分支都会进入。
编译运行,结果如下:
模板化
功能虽然实现了,但是如果每个控件都写那么多代码也态麻烦了. WPF的模板可以简化代码, xaml 完整代码如下:
<Window x:Class="MoveAndResizeAble.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MoveAndResizeAble"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="MoveAndResizeTemplate" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Thumb DragDelta="Thumb_DragDelta"
Cursor="SizeAll">
<Thumb.Template>
<ControlTemplate>
<Rectangle Fill="Transparent"></Rectangle>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Canvas>
<ContentControl
Canvas.Left="200"
Canvas.Top="50"
Width="100"
Height="100"
Template="{StaticResource MoveAndResizeTemplate }"
>
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
</ContentControl>
<ContentControl
Canvas.Left="350"
Canvas.Top="150"
Width="100"
Height="100"
Template="{StaticResource MoveAndResizeTemplate }">
<Button Margin="2" Content="TestButton" IsHitTestVisible="False"/>
</ContentControl>
</Canvas>
</Window>
封装成控件
MoveResizeItem.xaml
<ContentControl x:Class="MoveResizeItem.MoveResizeItem"
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:MoveResizeItem"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Thumb DragDelta="Thumb_DragDelta"
Cursor="SizeAll">
<Thumb.Template>
<ControlTemplate>
<Rectangle Fill="Transparent"></Rectangle>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
DragDelta="Thumb_DragDelta_Resize"
VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
MoveResizeItem.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace MoveResizeItem
{
/// <summary>
/// UserControl1.xaml 的交互逻辑
/// </summary>
public partial class MoveResizeItem : ContentControl
{
public MoveResizeItem()
{
InitializeComponent();
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Control thb = sender as Control;
if (thb == null)
{
return;
}
Control c = thb.DataContext as Control;
if (c != null)
{
double x = Canvas.GetLeft(c);
double y = Canvas.GetTop(c);
x = double.IsNaN(x) ? 0 : x;
y = double.IsNaN(y) ? 0 : y;
Canvas.SetLeft(c, x + e.HorizontalChange);
Canvas.SetTop(c, y + e.VerticalChange);
}
}
private void Thumb_DragDelta_Resize(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Control thumb = sender as Control;
Control designerItem = thumb.DataContext as Control;
if (designerItem != null)
{
double deltaVertical, deltaHorizontal;
switch (thumb.VerticalAlignment)
{
case VerticalAlignment.Bottom:
deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
designerItem.Height -= deltaVertical;
break;
case VerticalAlignment.Top:
deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);
designerItem.Height -= deltaVertical;
break;
default:
break;
}
switch (thumb.HorizontalAlignment)
{
case HorizontalAlignment.Left:
deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);
designerItem.Width -= deltaHorizontal;
break;
case HorizontalAlignment.Right:
deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
designerItem.Width -= deltaHorizontal;
break;
default:
break;
}
}
e.Handled = true;
}
}
}
步骤:
- 新建WPF用户控件
- 把基改成ContentControl
- 之后所有的代码都是都写过复制粘贴即可
代码下载
码云: https://gitee.com/luan-it/move-resize-able.git