[WPF] Darg and Drop (1)

  • 947
  • 0
  • 2021-09-04

很久沒寫文章了,這幾篇就來談談 WPF 中 Drag and Drop 的實作吧。

在 WPF 實作 Drag and Drop 算是滿容易的一件事,一般我們常用的方式是透過 DragDrop class 。這種處理方式主要是透過剪貼簿的方式來完成,也就是把拖曳來源內容先存在剪貼簿內,在目標物上則是取回剪貼簿的內容。

這一篇我們先單純從事件的方式來實作,簡述流程如下:
(1) 當來源元素 (TextBlock) 引發 MouseDown 事件的時候,在其事件委派函式呼叫 DragDrop.DoDragDrop 方法,並且以這個 TextBlock.Text 作為複製用的資料。
(2) 當拖曳到目標元素 (TextBlock) 時,放開滑鼠按鈕將會引發目標控制項的 Drop 附加事件,在此事件的委派函式內取得 (1) 所存入的資料並指派給 Text 屬性。

有一點需要注意的是目標元素必須要設定 AllowDrop 屬性為 True,才有作用。

依據上述撰寫的程式碼如下:

<Window x:Class="DragDropWPFSample001.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:DragDropWPFSample001"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid >
        <Grid.RowDefinitions >
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Canvas Background="LightBlue">
            <TextBlock  Text="簡單拖曳" FontSize="15" x:Name="source" 
                        MouseDown="source_MouseDown"/>
        </Canvas>
        <TextBlock Grid.Row="1" x:Name="target" Margin="12" AllowDrop="True"
                   VerticalAlignment="Center" HorizontalAlignment="Left" 
                   Width="100" Background="YellowGreen"
                   Drop="target_Drop"/>
    </Grid>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void source_MouseDown(object sender, MouseButtonEventArgs e)
    {
        DragDrop.DoDragDrop(source, source.Text, DragDropEffects.Copy);
    }

    private void target_Drop(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            target.Text = (string)e.Data.GetData(DataFormats.StringFormat);
        }
    }
}

這樣就完成一個簡單的拖曳複製了。下一步來做點搞怪的,就是在拖曳的時候複製一個元素的複製品,可以從來源一直拖曳產生新的複製品,放置在 Canvas 的任何地方。先改動一下 XAML 的部分,AllowDrop 設定移動到 Canvas :

<Window x:Class="DragDropWPFSample002.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:DragDropWPFSample002"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" >
    <Canvas Background="LightBlue" x:Name="canvas"
            DragOver="canvas_DragOver"
            Drop="canvas_Drop"
            AllowDrop="True">
        <TextBlock  Text="簡單拖曳" FontSize="15" x:Name="source" 
                        MouseDown="source_MouseDown"/>
    </Canvas>
</Window>

這邊會多加上一個 DragOver 事件的委派函式,在這個函式裡會加入相關於移動的程式碼。這裡要強調一個觀念,對於移動的處理,請避免使用設定 Canvas 座標的方式,而是應該靠 TranslateTransform 來處理會比較好。

public partial class MainWindow : Window
{
    private TextBlock _shadow;
    public MainWindow()
    {
        InitializeComponent();
    }       

    private void source_MouseDown(object sender, MouseButtonEventArgs e)
    {
        CreateShadowTextBlock();
        DragDrop.DoDragDrop(source, source.Text, DragDropEffects.Copy);
    }

    private void CreateShadowTextBlock()
    {
        _shadow = new TextBlock { Text = source.Text };
        _shadow.RenderTransform = new TranslateTransform();
        _shadow.FontSize = source.FontSize;
        _shadow.Foreground = new SolidColorBrush(Colors.Gray);
        canvas.Children.Add(_shadow);
        Canvas.SetTop(_shadow, Canvas.GetTop(source));
        Canvas.SetLeft(_shadow, Canvas.GetLeft(source));
    }

    private void canvas_DragOver(object sender, DragEventArgs e)
    {            
        var position = e.GetPosition(canvas);
        var transform = (TranslateTransform)_shadow.RenderTransform;
        transform.X = position.X;
        transform.Y = position.Y;
    }

    private void canvas_Drop(object sender, DragEventArgs e)
    {
        _shadow.Foreground = source.Foreground;
    }
}

可以看到在 source_MouseDown 的程式碼內呼叫了 CreateShadowTextBlock 方法,這個方法是為了複製新的 TextBlock, 也刻意將其 Foreground 設定為灰色筆刷。接著在 canvas_DragOver 利用 DragEventArgs 的 GetPosition 方法取得滑鼠指標相對於 Canvas 的座標,以此座標作為複製品的 TranslateTransform 的新座標值。

最後,在 canvas_Drop 方法裡將 Foreground 設定成與 source 相同,完成整個拖曳流程。範例程式碼在此