基於 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