概述 本文描述WPF的拖放功能(Drag and Drop)。 拖放功能涉及到兩個功能,一個就是拖,一個是放。拖放可以發生在兩個控制項之間,也可以在一個控制項自己內部拖放。假設界面上有兩個控制項,一個TreeView,一個ListView,那麼可能發生的拖動有以下幾種情況: 1、TreeView -> L ...
概述
本文描述WPF的拖放功能(Drag and Drop)。
拖放功能涉及到兩個功能,一個就是拖,一個是放。拖放可以發生在兩個控制項之間,也可以在一個控制項自己內部拖放。假設界面上有兩個控制項,一個TreeView,一個ListView,那麼可能發生的拖動有以下幾種情況:
1、TreeView -> ListView
2、ListView -> TreeView
3、TreeView -> TreeView
4、ListView -> ListView
對於拖的控制項需要在滑鼠移動事件中檢測左鍵按下並啟動拖動操作;對於放的控制項需要處理Drop等事件來接收數據。如果是在控制項內部拖動,則以上兩個動作都要處理。
為簡便起見,本文就以ListView拖動到TreeView為例進行講解。
拖
在拖與放的控制項之間一定會有數據傳遞,我們可以設計一個類型來進行數據傳輸,由於ListView本身就是綁定到一個對象列表的,我就把選中的對象位元組拿來傳遞了,沒有額外定義類型。
public class ListViewAdvNodeItem
{
public string Title {get;set;}
}
listView.ItemsSource的數據類型為:BindableCollection<ListViewAdvNodeItem> ListViewAdvNodeItems,通過this.listView.SelectedItem可以得到的數據類型即為:ListViewAdvNodeItem
設計代碼如下:
<ListView x:Name="listView"
Mouse.MouseMove="listView_MouseMove" >
</ListView>
在listView_MouseMove事件中,我們將啟動拖動功能。
private void listView_MouseMove(object sender, MouseEventArgs e) {
if (sender is ListView listview
&& e.LeftButton == MouseButtonState.Pressed
&& listview.SelectedItem != null) { DragDrop.DoDragDrop(listview, listview.SelectedItem, DragDropEffects.Move); } }
通過DragDrop.DoDragDrop方法啟動拖動,該方法有三個參數:
1、發起拖動的控制項
2、傳輸的數據(這裡是一個ListViewAdvNodeItem類型的對象)
3、拖動的類型,一般為Move或Copy
放
下麵就要在TreeView控制項中處理放的事件了
設計代碼:
<TreeView x:Name="treeView"
AllowDrop="True"
DragDrop.Drop="treeView_Drop"
DragDrop.DragOver="treeView_DragOver"
DragDrop.DragEnter="treeView_DragEnter"
DragDrop.DragLeave="treeView_DragLeave" >
</TreeView>
首先要設置AllowDrop="True",然後重點處理DragDrop.Drop事件:
private void treeView_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
{
if (e.OriginalSource is TextBlock txtTitle)
{ if (txtTitle.Tag is Excerpt toExcerpt)
{
//處理業務
}
}
}
}
在處理Drop事件時,我們需要知道兩件事情,1:拖來的是什麼數據?2、放哪裡了?
首先,通過e.Data.GetData(typeof(ListViewAdvNodeItem))就可以獲得數據來源,這裡GetData得到的對象就是上面的 listview.SelectedItem;
其次,通過e.OriginalSource 我們將獲得數據放在哪裡的問題。這段代碼很難理解,要回頭看一下TreeView的ItemTemplate定義
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeViewAdvNodeItem}" ItemsSource="{Binding Children}">
<Border x:Name="itemBorder" Margin="2" BorderBrush="White" BorderThickness="1" >
<StackPanel Orientation="Horizontal">
<Image x:Name="nodeImage" Source="../Images/FolderClose.png" Width="20" Height="20"/>
<TextBlock Text="{Binding Title}" Tag="{Binding Excerpt}" VerticalAlignment="Center" FontSize="13" Margin="2"/>
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
從這個模板定義可以看出,TreeView中用來顯示Title的控制項是一個TextBlock,然後這個TextBlock的Tag屬性上還綁定了一個業務對象。
再回頭看上面一段代碼,就可以看出具體的邏輯:當滑鼠放開時,其所指的對象是一個TextBlock,然後取到這個TextBlock的Tag對象,裡面包含了我想要的業務數據。
到此拖放功能就完成了。
為了更好的展現效果,我們可以對拖放的目標進行判斷,對於一些不能放的位置顯示禁止拖放的圖標,這時就需要處理DragOver事件了
private void treeView_DragOver(object sender, DragEventArgs e)
{ //判斷是否允許拖動
e.Effects = DragDropEffects.None;
if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
{
if (e.OriginalSource is TextBlock txtTitle)
{
if (txtTitle.Tag is Excerpt toExcerpt)
{
if (CanDrop(fromListNode.Excerpt, toExcerpt)) //業務判斷
{
e.Effects = DragDropEffects.Move;
}
}
}
}
e.Handled = true;
}
裝飾器
如果拖動時,有下麵這樣的一個標簽跟隨滑鼠移動,其顯示內容是拖動對象的Title,效果就更好了。
這個就需要通過裝飾器來實現。
關於裝飾器的介紹:裝飾器概述 - WPF .NET Framework | Microsoft Docs
首先我們建一個裝飾器對象DragTitleAdorner
public class DragTitleAdorner : Adorner
{
private readonly ContentPresenter _contentPresenter;
private Control Control
{
get
{
return (Control)this.AdornedElement;
}
}
public DragTitleAdorner(UIElement adornedElement, Point pos, string? Title = "") : base(adornedElement)
{
IsHitTestVisible = false;
int width = 22;
if (Title != null)
{
width += (int)MeasureTextWidth(Title, 14, "宋體");
}
this._contentPresenter = new ContentPresenter
{
Content = new Border
{
Background = Brushes.SteelBlue,
Width = width,
Height = 28,
BorderBrush = Brushes.Gray,
BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
CornerRadius= new CornerRadius(5),
Child = new TextBlock
{
Text = Title,
FontSize = 14,
FontFamily= new FontFamily("宋體"),
Foreground = Brushes.White,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(10, 0, 0, 0),
},
},
};
double left = pos.X;
double top = pos.Y;
this.Margin = new Thickness(left + 5, top + 10, 0, 0);
}
#region Override
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;
{
return this._contentPresenter;
}
protected override Size MeasureOverride(Size constraint)
{
this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure
return this.Control.RenderSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
this._contentPresenter.Arrange(new Rect(finalSize));
return finalSize;
}
#endregion Override
private double MeasureTextWidth(string text, double fontSize, string fontFamily)
{
FormattedText formattedText = new FormattedText(
text,
System.Globalization.CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
new Typeface(fontFamily.ToString()),
fontSize,
Brushes.Black
);
return formattedText.WidthIncludingTrailingWhitespace;
}
}
View Code
在構造這個對象時,我們將傳入兩個重要的參數:Point pos 和 string Title ,這兩個參數決定了它在何處顯示什麼內容。
程式用代碼構建了一個Border,其內有一個TextBlock,並通過pos參數來控制了它的位置。
下麵,在treeView_DragOver事件中顯示這個裝飾器即可。
private void treeView_DragOver(object sender, DragEventArgs e) { if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode) { //顯示裝飾器 AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.treeView); if (adornerLayer != null) { Adorner[] adorners = adornerLayer.GetAdorners(this.treeView); if (adorners != null) { foreach (var adorner in adorners) { adornerLayer.Remove(adorner); } } DragTitleAdorner _adorner = new DragTitleAdorner(this.treeView, pos, fromListNode.Excerpt?.Title); adornerLayer.Add(_adorner); } } e.Handled = true; }
更多信息請參考文末源碼。
資源
系列目錄:WPF開發快速入門【0】前言與目錄
代碼下載:Learn WPF: WPF學習筆記 (gitee.com)
簽名區:
如果您覺得這篇博客對您有幫助或啟發,請點擊右側【推薦】支持,謝謝!