當前位置: 妍妍網 > 碼農

基於 WPFDevelopers 的拖動案例實作二次拓展

2024-05-08碼農

基於 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 { getprivateset; }
    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

    [2]

    碼雲連結: https://gitee.com/WPFDevelopersOrg/WPFDevelopers

    [3]

    TransformLayout: https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Samples.Shared/Controls/TransformLayout

    [4]

    源碼: https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Samples.Shared/ExampleViews/DrapView