[WPF] Darg and Drop (3)

  • 260
  • 0

前一篇做了一個可以支援 Drag and Drop 的 Canvas,但方便性還是不夠,稍微來改進一下,製作一個泛用性更廣的類別吧。

前面我們製作 DragAndDropCanvas 的時候用到了附加屬性,註冊成附加屬性和相依屬性其中有一個很重要的差別是相依屬性只能在 DependencyObject class 中註冊,而附加屬性卻沒有這項限制。一個想法是「如果能建立一個拖曳功能的通用類別,透過附加屬性控制,就可以用在大部分的畫面元素中」,那我們就開始吧。

和前一篇一樣,先完成以下三個附加屬性:(1) 需要複製的資料型態 (2) 拖曳來源 (3) 拖曳目標。

在這個類別裡會需要控制容器的 AllowDrop 屬性,所以會新增一個附加屬性。

  public static readonly DependencyProperty DragAndDropEnabledProperty
      = DependencyProperty.RegisterAttached("DragAndDropEnabled", typeof(bool), typeof(DragAndDropHelper), new FrameworkPropertyMetadata(OnDragAndDropEnableChanged));

  public static void SetDragAndDropEnabled(UIElement element, bool value)
  {
      element.SetValue(DragAndDropEnabledProperty, value);
  }

  public static bool GetDragAndDropEnabled(UIElement element)
  {
      return (bool)element.GetValue(DragAndDropEnabledProperty);
  }

  private static void OnDragAndDropEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
      var element = (UIElement)d;
      var newValue = (bool)e.NewValue;
      var oldValue = (bool)e.OldValue;
      if (newValue != oldValue)
      {
          if (newValue)
          {
              Enable(element);
          }
          else
          {
              Disable(element);
          }
      }
  }

  private static void Enable(UIElement element)
  {
      element.AllowDrop = true;
      element.PreviewMouseDown += Element_PreviewMouseDown;
      element.Drop += Element_Drop;
  }

  private static void Disable(UIElement element)
  {
      element.AllowDrop = false;
      element.PreviewMouseDown -= Element_PreviewMouseDown;
      element.Drop -= Element_Drop;
  }

  private static void Element_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
  {
      if (e.Source is UIElement element)
      {
          var property = DragAndDropHelper.GetDragSource(element);
          if (property == null) return;
          var data = element.GetValue(property);
          DragDrop.DoDragDrop(element, data, DragDropEffects.Copy);
      }
  }

  private static void Element_Drop(object sender, DragEventArgs e)
  {
      if (e.Source is UIElement element)
      {
          var property = DragAndDropHelper.GetDropTarget(element);
          if (property == null) return;
          var format = GetDropDataFormat(element);
          if (format == null) return;
          if (e.Data.GetDataPresent(format))
          {
              element.SetValue(property, e.Data.GetData(format));
          }
      };
  }

這個 DragAndDropEnabledProperty 附加屬性在註冊的時候加入了一個 PropertyCahngedCallback – 指向 OnDragAndDropEnableChanged 方法。這個方法的作用在於當設定為 true 時將容器的 AllowDrop 設定為 true,並且為容器的 PreviewMouseDown 事件與 DragDrop.Drop 附加事件掛放必要的委派方法。

照例寫個簡單的 xaml 試用一下。元素只要掛上 DragAndDropHelper.DragAndDropEnabled="True" 就會變成能夠拖曳其內部元素的容器,內部元素的使用方式則是掛上 DropSource,DropTarget 和 DropDataFormat 即可。

<Window x:Class="DragDropWPFSample004.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:DragDropWPFSample004"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Canvas local:DragAndDropHelper.DragAndDropEnabled="True">
        <Button Content="CopyThisText" Canvas.Top="10" Canvas.Left="10" 
                local:DragAndDropHelper.DropDataFormat="{x:Static DataFormats.Text}"
                local:DragAndDropHelper.DragSource="Button.Content" />

        <Button Content="here" Canvas.Top="300" Canvas.Left="300" 
                local:DragAndDropHelper.DropDataFormat="{x:Static DataFormats.Text}"
                local:DragAndDropHelper.DropTarget="Button.Content"/>
    </Canvas>
</Window>

這樣就完成一個用途廣泛的 Drag and Drop helper,完整範例在此