基于 WPFDevelopers 的拖动案例实现二次拓展
控件名:DrapView
作 者:WPFDevelopersOrg - 关关 | 驚鏵
原文链接 [1] :https://github.com/WPFDevelopersOrg/WPFDevelopers
码云链接 [2] :https://gitee.com/WPFDevelopersOrg/WPFDevelopers
框架支持
.NET4 至 .NET8
;
Visual Studio 2022
;
基于 WPFDevelopers 中的 TransformLayout [3] 实现拖动案例。
源码在
dev
分支 https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev
以下代码请跳转 源码 [4]
1)新增
TransformThumb.cs
代码如下:
IsSeletedProperty
:定义
IsSelected
的依赖属性,表示该控件是否被选中。当属性值改变时,会触发一个回调函数,该函数会根据新值来更新控件的外观,以反映选中状态。
ShapeTypeProperty
:定义
ShapeType
的依赖属性,表示控件的形状类型(未完成)。
ThumbTypeProperty
:定义
ThumbType
的依赖属性,表示控件的装饰器类型。当属性改变时,会调用
onPropertyChanged
方法来更新控件的装饰器。
TransformThumb_IsVisibleChanged
方法:如果控件变为可见时,调用
CreateAdorner
方法创建装饰器。
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespaceWPFDevelopers.Samples.ExampleViews
{
public classTransformThumb : ContentControl
{
public Guid Id { get; privateset; }
publicTransformThumb()
{
Id = Guid.NewGuid();
}
public Type ContentType
{
get { return (Type)GetValue(ContentTypeProperty); }
set { SetValue(ContentTypeProperty, value); }
}
publicstaticreadonly DependencyProperty ContentTypeProperty =
DependencyProperty.Register("ContentType", typeof(Type), typeof(TransformThumb));
// 选中事件
// 点击事件
publicevent EventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
publicstaticreadonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
"Click", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TransformThumb));
public PrintState PrintState
{
get { return (PrintState)GetValue(PrintStateProperty); }
set { SetValue(PrintStateProperty, value); }
}
publicstaticreadonly DependencyProperty PrintStateProperty =
DependencyProperty.Register("PrintState", typeof(PrintState), typeof(TransformThumb), new PropertyMetadata(PrintState.Printing));
publicbool IsSeleted
{
get { return (bool)GetValue(IsSeletedProperty); }
set { SetValue(IsSeletedProperty, value); }
}
publicstaticreadonly DependencyProperty IsSeletedProperty =
DependencyProperty.Register("IsSeleted", typeof(bool), typeof(TransformThumb), new FrameworkPropertyMetadata(false, (s, e) => {
// 显示遮罩
TransformThumb transform = s as TransformThumb;
if (transform != null)
{
// 判定为选中还是非选中
bool selected = (bool)e.NewValue;
string status = selected ? "选中" : "非选中";
// 控制选中效果
Debug.WriteLine($"节点:{transform.Id} 节点状态:{status}");
//transform.SetValue(ThumbTypeProperty, ThumbType. style3);
UIElement element = transform as UIElement;
if (element == null)
{
return;
}
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (adornerLayer != null)
{
var adorners = adornerLayer.GetAdorners(element);
Type oldtype = null;
switch (transform.ThumbType)
{
case ThumbType. style1:
oldtype = typeof(ElementAdorner);
break;
case ThumbType. style2:
oldtype = typeof(ThumbAdorner);
break;
case ThumbType. style3:
oldtype = typeof(ThumbAdorner2);
break;
default:
oldtype = typeof(ThumbAdorner2);
break;
}
foreach (var adorner in adorners)
{
if (adorner.GetType() == oldtype)
{
adorner.Visibility = selected? Visibility.Visible:Visibility.Hidden;
}
}
}
}
}));
public ShapeType ShapeType
{
get { return (ShapeType)GetValue(ShapeTypeProperty); }
set { SetValue(ShapeTypeProperty, value); }
}
publicstaticreadonly DependencyProperty ShapeTypeProperty =
DependencyProperty.Register("ShapeType", typeof(ShapeType), typeof(TransformThumb));
public ThumbType ThumbType
{
get { return (ThumbType)GetValue(ThumbTypeProperty); }
set { SetValue(ThumbTypeProperty, value); }
}
publicstaticreadonly DependencyProperty ThumbTypeProperty =
DependencyProperty.Register("ThumbType", typeof(ThumbType), typeof(TransformThumb),new FrameworkPropertyMetadata(ThumbType. style1, onPropertyChanged));
privatestaticvoidonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateAdorner(d, e);
}
staticTransformThumb()
{
Default styleKeyProperty.OverrideMetadata(typeof(TransformThumb), new FrameworkPropertyMetadata(typeof(TransformThumb)));
}
publicoverridevoidOnApplyTemplate()
{
base.OnApplyTemplate();
IsVisibleChanged += TransformThumb_IsVisibleChanged;
CreateAdorner();
}
voidCreateAdorner()
{
var adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null)
{
Adorner adorner;
switch (ThumbType)
{
case ThumbType. style1:
adorner = new ElementAdorner(this);
break;
case ThumbType. style2:
adorner = new ThumbAdorner(this);
break;
case ThumbType. style3:
adorner = new ThumbAdorner2(this);
break;
default:
adorner = new ThumbAdorner2(this);
break;
}
if (adorner != null)
{
adorner.Visibility = Visibility.Hidden;
adornerLayer.Add(adorner);
}
}
}
staticvoidUpdateAdorner(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement element = d as UIElement;
if (element == null)
{
return;
}
var adornerLayer = AdornerLayer.GetAdornerLayer(element as Visual);
if (adornerLayer != null)
{
ThumbType oldvalue = (ThumbType)e.OldValue;
var adorners = adornerLayer.GetAdorners(element as UIElement);
Type oldtype = null;
switch (oldvalue)
{
case ThumbType. style1:
oldtype = typeof(ElementAdorner);
break;
case ThumbType. style2:
oldtype = typeof(ThumbAdorner);
break;
case ThumbType. style3:
oldtype = typeof(ThumbAdorner2);
break;
default:
oldtype = typeof(ThumbAdorner2);
break;
}
foreach (var adorner in adorners) {
if (adorner.GetType() == oldtype)
{
adornerLayer.Remove(adorner);
}
}
ThumbType newvalue = (ThumbType)e.NewValue;
Adorner Newadorner;
switch (newvalue)
{
case ThumbType. style1:
Newadorner = new ElementAdorner(element);
break;
case ThumbType. style2:
Newadorner = new ThumbAdorner(element);
break;
case ThumbType. style3:
Newadorner = new ThumbAdorner2(element);
break;
default:
Newadorner = new ThumbAdorner2(element);
break;
}
if (Newadorner != null)
{
adornerLayer.Add(Newadorner);
}
}
}
privatevoidTransformThumb_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue isbool isVisible)
{
if (isVisible)
{
CreateAdorner();
}
}
}
publicoverridestringToString()
{
return$"{Id}";
}
}
}
2)新增
TransformThumb.xaml
代码如下:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Samples.ExampleViews">
< styleBasedOn="{StaticResource WD.ControlBasic style}"TargetType="{x:Type controls:TransformThumb}">
<SetterProperty="MinHeight"Value="20" />
<SetterProperty="MinWidth"Value="30" />
<SetterProperty="Height"Value="100" />
<SetterProperty="Width"Value="50" />
<SetterProperty="RenderTransformOrigin"Value="0.5,0.5" />
<SetterProperty="Template">
<Setter.Value>
<ControlTemplateTargetType="{x:Type controls:TransformThumb}">
<ContentPresenter
x:Name="PART_ContentPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</ style>
</ResourceDictionary>
3)新增
DrapViewExample.xaml
示例代码如下:
Stretch="None"
:画刷在应用到目标区域时不进行拉伸,保持原始大小。
TileMode="Tile"
:平铺图案以填充目标区域。
Viewport="0,0,16,16"
:设置
16x16
的区域。
ViewportUnits="Absolute"
:指定以像素为基准。
<DrawingBrush.Drawing>
元素内部定义一个绘图元素,通过
<GeometryDrawing>
和
<GeometryGroup>
来创建两个矩形作为背景图。
<DockPanel>
<DockPanel.Resources>
<Colorx:Key="WD.MainContentForegroundColor">#FFF0F0F0</Color>
<Colorx:Key="WD.MainContentBackgroundColor">#FFF5F5F5</Color>
<DrawingBrush
x:Key="WD.MainContentForegroundDrawingBrush"
RenderOptions.CachingHint="Cache"
Stretch="None"
TileMode="Tile"
Viewport="0,0,16,16"
ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawingBrush="{DynamicResource WD.MainContentForegroundBrush}">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometryRect="0,0,8,8" />
<RectangleGeometryRect="8,8,8,8" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
<SolidColorBrushx:Key="WD.MainContentForegroundBrush"Color="{DynamicResource WD.MainContentForegroundColor}" />
<SolidColorBrushx:Key="WD.MainContentBackgroundBrush"Color="{DynamicResource WD.MainContentBackgroundColor}" />
</DockPanel.Resources>
<BorderMargin="0,10"DockPanel.Dock="Top">
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal"
PreviewMouseLeftButtonDown="StackPanel_PreviewMouseLeftButtonDown">
<RadioButton
Margin="4,0"
Background="Red"
GroupName="color"
IsChecked="True"
style="{StaticResource WD.ColorRadioButton}" />
<RadioButton
Margin="4,0"
Background="DodgerBlue"
GroupName="color"
style="{StaticResource WD.ColorRadioButton}" />
<RadioButton
Margin="4,0"
Background="LimeGreen"
GroupName="color"
style="{StaticResource WD.ColorRadioButton}" />
<RadioButton
Margin="4,0"
Background="Yellow"
GroupName="color"
style="{StaticResource WD.ColorRadioButton}" />
</StackPanel>
</Border>
<BorderMargin="0,10"DockPanel.Dock="Top">
<StackPanelHorizontalAlignment="Center"Orientation="Horizontal">
<RadioButton
VerticalContentAlignment="Center"
Content="点"
GroupName="polygon"
Tag="0" />
<RadioButton
VerticalContentAlignment="Center"
Content="线"
GroupName="polygon"
Tag="1" />
<RadioButton
VerticalContentAlignment="Center"
Content="三角形"
GroupName="polygon"
Tag="2" />
<RadioButton
VerticalContentAlignment="Center"
Content="矩形"
GroupName="polygon"
IsChecked="True"
Tag="3" />
<RadioButton
VerticalContentAlignment="Center"
Content="多边形"
GroupName="polygon"
Tag="4" />
</StackPanel>
</Border>
<BorderMargin="0,10"DockPanel.Dock="Top">
<StackPanelHorizontalAlignment="Center"Orientation="Horizontal">
<RadioButton
VerticalContentAlignment="Center"
Checked="RadioButton_Checked"
Content=" style1"
GroupName=" style"
IsChecked="True"
Tag="YYYY" />
<RadioButton
VerticalContentAlignment="Center"
Checked="RadioButton_Checked"
Content=" style2"
GroupName=" style"
Tag="1" />
<RadioButton
VerticalContentAlignment="Center"
Checked="RadioButton_Checked"
Content=" style3"
GroupName=" style"
Tag="2" />
<StackPanelHorizontalAlignment="Center"Orientation="Horizontal">
<Button
Name="btn_import"
Margin="10,0"
Padding="10,5"
Content="导入" />
<Button
Name="btn_export"
Margin="10,0"
Padding="10,5"
Content="导出" />
<Button
Name="btn_remove"
Margin="10,0"
Padding="10,5"
Click="btn_remove_Click"
Content="删除选中" />
<Button
Name="btn_removeAll"
Margin="10,0"
Padding="10,5"
Click="btn_removeAll_Click"
Content="清空画板" />
</StackPanel>
</StackPanel>
</Border>
<BorderWidth="200"DockPanel.Dock="Left">
<ListBoxx:Name="lst_layers" />
</Border>
<Canvas
Name="MainCanvas"
Background="{StaticResource WD.MainContentForegroundDrawingBrush}"
ClipToBounds="True"
MouseLeftButtonDown="MainCanvas_MouseLeftButtonDown"
MouseLeftButtonUp="MainCanvas_MouseLeftButtonUp"
MouseMove="MainCanvas_MouseMove" />
</DockPanel>
4)新增
DrapViewExample.xaml.cs
示例代码如下:
RadioButton_Checked
: 单选按钮选择一个不同类型,会更新
selectThumb
,以便在绘制时使用正确的元素类型。
btn_remove_Click
: 删除选定图形事件。它遍历
MainCanvas
中的所有子元素,找到被选中的
TransformThumb
元素,并将它从
MainCanvas
中移除,并从
lst_layers
中移除对应的项。
NodeExist
: 这个方法用于检查给定的
TransformThumb
元素是否存在于
MainCanvas
中。它遍历
MainCanvas
的子元素,如果找到与给定节点相同的节点,则返回
true
,否则返回
false
。
btn_removeAll_Click
: 这个方法处理了移除所有图形元素的按钮点击事件。它会清空
MainCanvas
中的所有子元素,并清空
lst_layers
中的所有项。
StackPanel_PreviewMouseLeftButtonDown
: 这个方法处理了鼠标左键按下事件,用于捕获当前选定的颜色。当用户点击颜色选择器时,会将当前颜色保存到
currentBrush
变量中,以便在绘制图形时使用。
publicpartial classDrapViewExample : UserControl
{
Point start;
TransformThumb element;
Rectangle shape;
ThumbType selectThumb;
Brush currentBrush = new SolidColorBrush(Colors.Red);
publicDrapViewExample()
{
InitializeComponent();
}
privatevoidMainCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//鼠标左键按下
Debug.WriteLine("鼠标左键按下");
// 更新选中项
UpdateDraw();
// 判定是否包含快捷键
// 判定左键按下
if (element != null && element.PrintState == PrintState.Edit)
{
// 设置选项选中
element.IsSeleted = true;
element = null;
e.Handled = true;
return;
}
start = e.GetPosition(MainCanvas);
// 创建几何实例
shape = new Rectangle();
shape.Stroke = currentBrush;
shape.Fill = currentBrush;
shape.StrokeThickness = 1;
shape.Width = 0;
shape.Height = 0;
shape.MouseLeftButtonDown += (s, e1) => {
e.Handled = false;
};
element = new TransformThumb();
element.MouseLeftButtonDown += (s, e2) => {
TransformThumb transform = s as TransformThumb;
if (transform != null)
{
Debug.WriteLine($"节点:{transform.Id} 节点按下");
transform.PrintState = PrintState.Edit;
element = transform;
e.Handled = false;
}
};
element.MouseLeftButtonUp += (s, e3) => {
e.Handled = false;
};
element.Content = shape;
element.Width = shape.Width;
element.Height = shape.Height;
element.ThumbType = selectThumb;
element.ContentType = shape.GetType();
element.PrintState = PrintState.Printing;
}
privatevoidMainCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// 鼠标左键键起
Debug.WriteLine("鼠标左键键起");
// 更新选中项
UpdateDraw();
// 键下和键起在同一个位置表示页面无选择项
Point end = Mouse.GetPosition(MainCanvas);
if (end == start)
{
// 移除节点
MainCanvas.Children.Remove(element);
// 重置绘制节点
element = null;
return;
}
if (element != null && element.PrintState != PrintState.Edit)
{
element.IsSeleted = true;
element.PrintState = PrintState.Edit;
}
// 重置绘制节点
element = null;
}
privatevoidUpdateDraw()
{
// 设置其他项默认隐藏
foreach (var item in MainCanvas.Children)
{
var child = item as TransformThumb;
if (child != null && child.IsSeleted && element != null)
{
// 重置非选中项
if (child != element)
{
child.IsSeleted = false;
child.PrintState = PrintState.Normal;
}
}
}
}
privatevoidMainCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (element == null)
{
return;
}
if (!NodeExist(element))
{
// 设置几何位置
Canvas.SetLeft(element, start.X);
Canvas.SetTop(element, start.Y);
// 更新其他选中项
UpdateDraw();
// 添加到面板中
MainCanvas.Children.Add(element);
lst_layers.Items.Add(element.ToString());
}
// 鼠标移动(鼠标未按下移动事件不生效)
if (e.LeftButton == MouseButtonState.Pressed && element.PrintState == PrintState.Printing)
{
Debug.WriteLine("鼠标移动绘制几何");
Point current = e.GetPosition(MainCanvas);
// 判定几何实例
// 判定鼠标移动点是否小于起始点
if (current.X - start.X < 0)
{
// 设置起始点为移动点
Canvas.SetLeft(element, current.X);
}
if (current.Y - start.Y < 0)
{
// 设置起始点为移动点
Canvas.SetTop(element, current.Y);
}
shape.SetValue(WidthProperty, Math.Abs(current.X - start.X));
shape.SetValue(HeightProperty, Math.Abs(current.Y - start.Y));
element.Width = Math.Abs(current.X - start.X);
element.Height = Math.Abs(current.Y - start.Y);
}
}
privatevoidRadioButton_Checked(object sender, RoutedEventArgs e)
{
RadioButton radioButton = (RadioButton)sender;
if (radioButton != null)
{
ThumbType result;
if (Enum.TryParse(radioButton.Content.ToString(), out result))
{
selectThumb = result;
}
}
}
privatevoidbtn_remove_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < MainCanvas.Children.Count; i++)
{
UIElement item = MainCanvas.Children[i];
if (item is TransformThumb)
{
var tranform = item as TransformThumb;
if (tranform != null && tranform.IsSeleted)
{
MainCanvas.Children.Remove(tranform);
lst_layers.Items.Remove(tranform.ToString());
}
}
}
}
privateboolNodeExist(TransformThumb node)
{
bool result = false;
int count = MainCanvas.Children.Count;
for (int i = 0; i < count; i++)
{
UIElement item = MainCanvas.Children[i];
if (item is TransformThumb)
{
var tranform = item as TransformThumb;
if (tranform == node)
{
result = true;
break;
}
}
}
return result;
}
privatevoidbtn_removeAll_Click(object sender, RoutedEventArgs e)
{
MainCanvas.Children.Clear();
lst_layers.Items.Clear();
}
privatevoidStackPanel_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is RadioButton)
{
var radioButton = (RadioButton)e.Source;
currentBrush = radioButton.Background;
}
}
}
参考资料
[1]
原文链接:
https://github.com/WPFDevelopersOrg/WPFDevelopers
码云链接:
https://gitee.com/WPFDevelopersOrg/WPFDevelopers
TransformLayout:
https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Samples.Shared/Controls/TransformLayout
源码:
https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Samples.Shared/ExampleViews/DrapView