逐步解說:在使用者控制項上啟用拖放功能
本逐步解說示範如何建立自訂使用者控制項,以參與 Windows Presentation Foundation (WPF) 中的拖放資料傳輸。
在本逐步解說中,您將建立代表圓形圖形的自訂 WPF UserControl。 您將在控制項上實作功能,以啟用透過拖放的資料傳輸。 例如,如果您從某個 Circle 控制項拖曳至另一個 Circle 控制項,則會將填滿色彩資料從來源 Circle 複製至目標。 如果您從 Circle 控制項拖曳至 TextBox,則填滿色彩的字串表示會複製至 TextBox。 您也會建立包含兩個面板控制項和一個 TextBox 的小型應用程式,來測試拖放功能。 您將撰寫讓面板處理所置放 Circle 資料的程式碼,以讓您將 Circle 從某個面版的子系集合移動或複製至另一個面板。
本逐步解說將說明下列工作:
建立自訂使用者控制項。
可讓使用者控制項成為拖曳來源。
讓使用者控制項成為置放目標。
啟用面板,以接收從使用者控制項置放的資料。
必要條件
若要完成這個逐步解說,您必須具有 Visual Studio。
建立應用程式專案
在本節中,您會建立應用程式基礎結構,包括具有兩個面板和一個 TextBox 的主要頁面。
在 Visual Basic 或 Visual C# 中,建立名為
DragDropExample
的新 WPF 應用程式專案。 如需詳細資訊,請參閱逐步解說︰我的第一個 WPF 桌面應用程式。開啟 MainWindow.xaml。
在開頭和結尾 Grid 標籤之間新增下列標記。
此標記會建立測試應用程式的使用者介面。
<Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Background="Beige"> <TextBox Width="Auto" Margin="2" Text="green"/> </StackPanel> <StackPanel Grid.Column="1" Background="Bisque"> </StackPanel>
將新的使用者控制項新增至專案
在本節中,您會將新的使用者控制項新增至專案。
在 [專案] 功能表上,選取 [新增使用者控制項]。
在 [新增項目] 對話方塊中,將名稱變更為
Circle.xaml
,然後按一下 [新增]。Circle.xaml 和其程式碼後置會新增至專案。
開啟 Circle.xaml。
此檔案將包含使用者控制項的使用者介面項目。
將下列標記新增至根 Grid,以建立將藍色圓形作為其 UI 的簡單使用者控制項。
<Ellipse x:Name="circleUI" Height="100" Width="100" Fill="Blue" />
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
在 C# 中,於無參數建構函式後面新增下列程式碼,以建立複製建構函式。 在 Visual Basic 中,新增下列程式碼,以建立無參數建構函式和複製建構函式。
若要允許複製使用者控制項,您可以在程式碼後置檔案中新增複製建構函式方法。 在簡化 Circle 使用者控制項中,您只會複製使用者控制項的填滿和大小。
public Circle(Circle c) { InitializeComponent(); this.circleUI.Height = c.circleUI.Height; this.circleUI.Width = c.circleUI.Height; this.circleUI.Fill = c.circleUI.Fill; }
Public Sub New() ' This call is required by the designer. InitializeComponent() End Sub Public Sub New(ByVal c As Circle) InitializeComponent() Me.circleUI.Height = c.circleUI.Height Me.circleUI.Width = c.circleUI.Height Me.circleUI.Fill = c.circleUI.Fill End Sub
將使用者控制項新增至主要視窗
開啟 MainWindow.xaml。
將下列 XAML 新增至 Window 開始標記,以建立目前應用程式的 XML 命名空間參考。
xmlns:local="clr-namespace:DragDropExample"
在第一個 StackPanel 中,新增下列 XAML,以在第一個面板中建立 Circle 使用者控制項的兩個執行個體。
<local:Circle Margin="2" /> <local:Circle Margin="2" />
面板的完整 XAML 如下。
<StackPanel Grid.Column="0" Background="Beige"> <TextBox Width="Auto" Margin="2" Text="green"/> <local:Circle Margin="2" /> <local:Circle Margin="2" /> </StackPanel> <StackPanel Grid.Column="1" Background="Bisque"> </StackPanel>
在使用者控制項中實作拖曳來源事件
在本節中,您將覆寫 OnMouseMove 方法,並起始拖放作業。
如果開始拖曳 (按下滑鼠按鈕並移動滑鼠),您將封裝要傳送至 DataObject 的資料。 在此情況下,Circle 控制項將會封裝三個資料項目:其填滿色彩的字串表示、高度的 double 表示和其本身的複本。
啟始拖放作業
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
新增下列 OnMouseMove 覆寫,以提供 MouseMove 事件的類別處理。
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (e.LeftButton == MouseButtonState.Pressed) { // Package the data. DataObject data = new DataObject(); data.SetData(DataFormats.StringFormat, circleUI.Fill.ToString()); data.SetData("Double", circleUI.Height); data.SetData("Object", this); // Initiate the drag-and-drop operation. DragDrop.DoDragDrop(this, data, DragDropEffects.Copy | DragDropEffects.Move); } }
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Input.MouseEventArgs) MyBase.OnMouseMove(e) If e.LeftButton = MouseButtonState.Pressed Then ' Package the data. Dim data As New DataObject data.SetData(DataFormats.StringFormat, circleUI.Fill.ToString()) data.SetData("Double", circleUI.Height) data.SetData("Object", Me) ' Inititate the drag-and-drop operation. DragDrop.DoDragDrop(Me, data, DragDropEffects.Copy Or DragDropEffects.Move) End If End Sub
這個 OnMouseMove 覆寫會執行下列工作:
檢查是否在滑鼠移動時按下滑鼠左鍵。
將 Circle 資料封裝成 DataObject。 在此情況下,Circle 控制項會封裝三個資料項目:其填滿色彩的字串表示、高度的 double 表示和其本身的複本。
呼叫靜態 DragDrop.DoDragDrop 方法來起始拖放作業。 您會將下列三個參數傳遞至 DoDragDrop 方法:
dragSource
– 此控制項的參考。data
– 在先前的程式碼中建立 DataObject。
按 F5 以建置並執行應用程式。
按一下其中一個 Circle 控制項,並將它拖曳至面板、另一個 Circle 和 TextBox 上方。 拖曳到 TextBox 時,游標會變更以表示移動。
將 Circle 拖曳到 TextBox上時,請按下 Ctrl 鍵。 請注意,游標變更以指出正在進行複製。
根據預設,游標會在拖放作業期間變更,表示置放資料的效果。 您可以處理 GiveFeedback 事件,並設定不同的游標,以自訂提供給使用者的意見反應。
提供意見反應給使用者
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
新增下列 OnGiveFeedback 覆寫,以提供 GiveFeedback 事件的類別處理。
protected override void OnGiveFeedback(GiveFeedbackEventArgs e) { base.OnGiveFeedback(e); // These Effects values are set in the drop target's // DragOver event handler. if (e.Effects.HasFlag(DragDropEffects.Copy)) { Mouse.SetCursor(Cursors.Cross); } else if (e.Effects.HasFlag(DragDropEffects.Move)) { Mouse.SetCursor(Cursors.Pen); } else { Mouse.SetCursor(Cursors.No); } e.Handled = true; }
Protected Overrides Sub OnGiveFeedback(ByVal e As System.Windows.GiveFeedbackEventArgs) MyBase.OnGiveFeedback(e) ' These Effects values are set in the drop target's ' DragOver event handler. If e.Effects.HasFlag(DragDropEffects.Copy) Then Mouse.SetCursor(Cursors.Cross) ElseIf e.Effects.HasFlag(DragDropEffects.Move) Then Mouse.SetCursor(Cursors.Pen) Else Mouse.SetCursor(Cursors.No) End If e.Handled = True End Sub
這個 OnGiveFeedback 覆寫會執行下列工作:
按 F5 以建置並執行應用程式。
將其中一個 Circle 控制項拖曳至面板、另一個 Circle 和 TextBox 上方。 請注意,游標現在是 OnGiveFeedback 覆寫中所指定的自訂游標。
從 TextBox 選取文字
green
。將
green
文字拖曳至 Circle 控制項。 請注意,會顯示預設游標,表示拖放作業的效果。 意見反應游標一律是由拖曳來源所設定。
在使用者控制項中實作拖放目標事件
在本節中,您將指定使用者控制項是置放目標、覆寫讓使用者控制項成為置放目標的方法,以及處理置放在其上的資料。
讓使用者控制項成為置放目標
開啟 Circle.xaml。
在開頭 UserControl 標籤中,新增 AllowDrop 屬性,並將其設定為
true
。<UserControl x:Class="DragDropWalkthrough.Circle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" AllowDrop="True">
當 AllowDrop 屬性設定為 true
,並將拖曳來源中的資料放在 Circle 使用者控制項時,就會呼叫 OnDrop 方法。 在此方法中,您將處理置放的資料,並將資料套用至 Circle。
處理置放的資料
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
新增下列 OnDrop 覆寫,以提供 Drop 事件的類別處理。
protected override void OnDrop(DragEventArgs e) { base.OnDrop(e); // If the DataObject contains string data, extract it. if (e.Data.GetDataPresent(DataFormats.StringFormat)) { string dataString = (string)e.Data.GetData(DataFormats.StringFormat); // If the string can be converted into a Brush, // convert it and apply it to the ellipse. BrushConverter converter = new BrushConverter(); if (converter.IsValid(dataString)) { Brush newFill = (Brush)converter.ConvertFromString(dataString); circleUI.Fill = newFill; // Set Effects to notify the drag source what effect // the drag-and-drop operation had. // (Copy if CTRL is pressed; otherwise, move.) if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey)) { e.Effects = DragDropEffects.Copy; } else { e.Effects = DragDropEffects.Move; } } } e.Handled = true; }
Protected Overrides Sub OnDrop(ByVal e As System.Windows.DragEventArgs) MyBase.OnDrop(e) ' If the DataObject contains string data, extract it. If e.Data.GetDataPresent(DataFormats.StringFormat) Then Dim dataString As String = e.Data.GetData(DataFormats.StringFormat) ' If the string can be converted into a Brush, ' convert it and apply it to the ellipse. Dim converter As New BrushConverter If converter.IsValid(dataString) Then Dim newFill As Brush = converter.ConvertFromString(dataString) circleUI.Fill = newFill ' Set Effects to notify the drag source what effect ' the drag-and-drop operation had. ' (Copy if CTRL is pressed; otherwise, move.) If e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) Then e.Effects = DragDropEffects.Copy Else e.Effects = DragDropEffects.Move End If End If End If e.Handled = True End Sub
這個 OnDrop 覆寫會執行下列工作:
使用 GetDataPresent 方法來檢查拖曳的資料是否包含字串物件。
如果字串資料存在,請使用 GetData 方法來擷取字串資料。
使用 BrushConverter 試著將字串轉換成 Brush。
將 Drop 事件標示為已處理。 您應該將置放事件標示為已處理,讓收到此事件的其他項目知道 Circle 使用者控制項已處理過它。
按 F5 以建置並執行應用程式。
選取 TextBox 中的文字
green
。將文字拖放至 Circle 控制項。 Circle 會從藍色變成綠色。
在 TextBox 中輸入文字
green
。選取 TextBox 中的文字
gre
。將它拖放至 Circle 控制項。 請注意,游標變更以指出允許置放,但 Circle 的色彩不會變更,因為
gre
不是有效的色彩。從綠色 Circle 控制項拖放至藍色 Circle 控制項。 Circle 會從藍色變成綠色。 請注意,顯示的游標取決於 TextBox 或 Circle 是否為拖曳來源。
將 AllowDrop 屬性設定為 true
並處理所拖放的資料,只需要啟用項目作為拖放目標。 不過,為了提供更好的用戶體驗,您也應該處理 DragEnter、 DragLeave和 DragOver 事件。 在這些事件中,您可以在置放資料之前執行檢查,並將其他意見反應提供給使用者。
將資料拖曳至 Circle 使用者控制項上方時,控制項應該通知拖曳來源是否可以處理所拖曳的資料。 如果控制項不知道如何處理資料,則應該拒絕置放。 若要這樣做,您將處理 DragOver 事件,並設定 Effects 屬性。
確認允許資料置放
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
新增下列 OnDragOver 覆寫,以提供 DragOver 事件的類別處理。
protected override void OnDragOver(DragEventArgs e) { base.OnDragOver(e); e.Effects = DragDropEffects.None; // If the DataObject contains string data, extract it. if (e.Data.GetDataPresent(DataFormats.StringFormat)) { string dataString = (string)e.Data.GetData(DataFormats.StringFormat); // If the string can be converted into a Brush, allow copying or moving. BrushConverter converter = new BrushConverter(); if (converter.IsValid(dataString)) { // Set Effects to notify the drag source what effect // the drag-and-drop operation will have. These values are // used by the drag source's GiveFeedback event handler. // (Copy if CTRL is pressed; otherwise, move.) if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey)) { e.Effects = DragDropEffects.Copy; } else { e.Effects = DragDropEffects.Move; } } } e.Handled = true; }
Protected Overrides Sub OnDragOver(ByVal e As System.Windows.DragEventArgs) MyBase.OnDragOver(e) e.Effects = DragDropEffects.None ' If the DataObject contains string data, extract it. If e.Data.GetDataPresent(DataFormats.StringFormat) Then Dim dataString As String = e.Data.GetData(DataFormats.StringFormat) ' If the string can be converted into a Brush, allow copying or moving. Dim converter As New BrushConverter If converter.IsValid(dataString) Then ' Set Effects to notify the drag source what effect ' the drag-and-drop operation will have. These values are ' used by the drag source's GiveFeedback event handler. ' (Copy if CTRL is pressed; otherwise, move.) If e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) Then e.Effects = DragDropEffects.Copy Else e.Effects = DragDropEffects.Move End If End If End If e.Handled = True End Sub
這個 OnDragOver 覆寫會執行下列工作:
按 F5 以建置並執行應用程式。
選取 TextBox 中的文字
gre
。將文字拖曳至 Circle 控制項。 請注意,游標現在會變更以指出不允許置放,因為
gre
不是有效的色彩。
您可以套用置放作業的預覽,進一步增強使用者體驗。 針對 Circle 使用者控制項,您將覆寫 OnDragEnter 和 OnDragLeave 方法。 將資料拖曳至控制項上方時,會將目前背景 Fill 儲存在預留位置變數中。 字串接著會轉換為筆刷,並套用至提供 Circle UI 的 Ellipse。 如果將資料拖曳出 Circle,而未進行置放,則會將原始 Fill 值重新套用至 Circle。
預覽拖放作業的效果
開啟 Circle.xaml.cs 或 Circle.xaml.vb。
在 Circle 類別中,宣告名為
_previousFill
的私用 Brush 變數,並將它初始化為null
。public partial class Circle : UserControl { private Brush _previousFill = null;
Public Class Circle Private _previousFill As Brush = Nothing
新增下列 OnDragEnter 覆寫,以提供 DragEnter 事件的類別處理。
protected override void OnDragEnter(DragEventArgs e) { base.OnDragEnter(e); // Save the current Fill brush so that you can revert back to this value in DragLeave. _previousFill = circleUI.Fill; // If the DataObject contains string data, extract it. if (e.Data.GetDataPresent(DataFormats.StringFormat)) { string dataString = (string)e.Data.GetData(DataFormats.StringFormat); // If the string can be converted into a Brush, convert it. BrushConverter converter = new BrushConverter(); if (converter.IsValid(dataString)) { Brush newFill = (Brush)converter.ConvertFromString(dataString.ToString()); circleUI.Fill = newFill; } } }
Protected Overrides Sub OnDragEnter(ByVal e As System.Windows.DragEventArgs) MyBase.OnDragEnter(e) _previousFill = circleUI.Fill ' If the DataObject contains string data, extract it. If e.Data.GetDataPresent(DataFormats.StringFormat) Then Dim dataString As String = e.Data.GetData(DataFormats.StringFormat) ' If the string can be converted into a Brush, convert it. Dim converter As New BrushConverter If converter.IsValid(dataString) Then Dim newFill As Brush = converter.ConvertFromString(dataString) circleUI.Fill = newFill End If End If e.Handled = True End Sub
這個 OnDragEnter 覆寫會執行下列工作:
新增下列 OnDragLeave 覆寫,以提供 DragLeave 事件的類別處理。
protected override void OnDragLeave(DragEventArgs e) { base.OnDragLeave(e); // Undo the preview that was applied in OnDragEnter. circleUI.Fill = _previousFill; }
Protected Overrides Sub OnDragLeave(ByVal e As System.Windows.DragEventArgs) MyBase.OnDragLeave(e) ' Undo the preview that was applied in OnDragEnter. circleUI.Fill = _previousFill End Sub
這個 OnDragLeave 覆寫會執行下列工作:
按 F5 以建置並執行應用程式。
選取 TextBox 中的文字
green
。將文字拖曳至 Circle 控制項上方,但不置放文字。 Circle 會從藍色變成綠色。
從 Circle 控制項拖離文字。 Circle 會從綠色變回藍色。
讓面板接收拖放的資料
在本節中,您會啟用一些面板,以裝載 Circle 使用者控制項作為所拖曳 Circle 資料的拖放目標。 您將實作程式碼,以將 Circle 從某個面板移至另一個面板,或在拖放 Circle 時按住 CTRL 鍵來複製 Circle 控制項。
開啟 MainWindow.xaml。
如下列 XAML 所示,在每個 StackPanel 控制項中,新增 DragOver 和 Drop 事件的處理程式。 將 DragOver 事件處理常式命名為
panel_DragOver
,並將 Drop 事件處理常式命名為panel_Drop
。根據預設,面板不會是拖放目標。 若要開啟它們,請將 AllowDrop 屬性新增至這兩個面板,並將值設定為
true
。<StackPanel Grid.Column="0" Background="Beige" AllowDrop="True" DragOver="panel_DragOver" Drop="panel_Drop"> <TextBox Width="Auto" Margin="2" Text="green"/> <local:Circle Margin="2" /> <local:Circle Margin="2" /> </StackPanel> <StackPanel Grid.Column="1" Background="Bisque" AllowDrop="True" DragOver="panel_DragOver" Drop="panel_Drop"> </StackPanel>
開啟 MainWindows.xaml.cs 或 MainWindow.xaml.vb。
針對 DragOver 事件處理常式新增下列程式碼。
private void panel_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent("Object")) { // These Effects values are used in the drag source's // GiveFeedback event handler to determine which cursor to display. if (e.KeyStates == DragDropKeyStates.ControlKey) { e.Effects = DragDropEffects.Copy; } else { e.Effects = DragDropEffects.Move; } } }
Private Sub panel_DragOver(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs) If e.Data.GetDataPresent("Object") Then ' These Effects values are used in the drag source's ' GiveFeedback event handler to determine which cursor to display. If e.KeyStates = DragDropKeyStates.ControlKey Then e.Effects = DragDropEffects.Copy Else e.Effects = DragDropEffects.Move End If End If End Sub
此 DragOver 處理常式會執行下列工作:
檢查所拖曳的資料包含 "Object" 資料,而 Circle 使用者控制項已將此 "Object" 資料封裝在 DataObject,並將其傳入 DoDragDrop 呼叫。
如果有「物件」資料,請檢查是否按下 CTRL 鍵。
如果按下 Ctrl 鍵 ,請將 Effects 屬性設定為 Copy。 否則,請將 Effects 屬性設定為 Move。
針對 Drop 事件處理常式新增下列程式碼。
private void panel_Drop(object sender, DragEventArgs e) { // If an element in the panel has already handled the drop, // the panel should not also handle it. if (e.Handled == false) { Panel _panel = (Panel)sender; UIElement _element = (UIElement)e.Data.GetData("Object"); if (_panel != null && _element != null) { // Get the panel that the element currently belongs to, // then remove it from that panel and add it the Children of // the panel that its been dropped on. Panel _parent = (Panel)VisualTreeHelper.GetParent(_element); if (_parent != null) { if (e.KeyStates == DragDropKeyStates.ControlKey && e.AllowedEffects.HasFlag(DragDropEffects.Copy)) { Circle _circle = new Circle((Circle)_element); _panel.Children.Add(_circle); // set the value to return to the DoDragDrop call e.Effects = DragDropEffects.Copy; } else if (e.AllowedEffects.HasFlag(DragDropEffects.Move)) { _parent.Children.Remove(_element); _panel.Children.Add(_element); // set the value to return to the DoDragDrop call e.Effects = DragDropEffects.Move; } } } } }
Private Sub panel_Drop(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs) ' If an element in the panel has already handled the drop, ' the panel should not also handle it. If e.Handled = False Then Dim _panel As Panel = sender Dim _element As UIElement = e.Data.GetData("Object") If _panel IsNot Nothing And _element IsNot Nothing Then ' Get the panel that the element currently belongs to, ' then remove it from that panel and add it the Children of ' the panel that its been dropped on. Dim _parent As Panel = VisualTreeHelper.GetParent(_element) If _parent IsNot Nothing Then If e.KeyStates = DragDropKeyStates.ControlKey And _ e.AllowedEffects.HasFlag(DragDropEffects.Copy) Then Dim _circle As New Circle(_element) _panel.Children.Add(_circle) ' set the value to return to the DoDragDrop call e.Effects = DragDropEffects.Copy ElseIf e.AllowedEffects.HasFlag(DragDropEffects.Move) Then _parent.Children.Remove(_element) _panel.Children.Add(_element) ' set the value to return to the DoDragDrop call e.Effects = DragDropEffects.Move End If End If End If End If End Sub
此 Drop 處理常式會執行下列工作:
檢查是否已處理 Drop 事件。 例如,如果將 Circle 置放在另一個處理 Drop 事件的 Circle 上,則不會想要包含 Circle 的面板也處理它。
如果未處理 Drop 事件,請檢查是否按下 CTRL 鍵。
如果發生 Drop 時按下 Ctrl 鍵,請建立 Circle 控制項的複本,並將它新增至 StackPanel 的 Children集合。
如果未按下 Ctrl 鍵 ,請將 Circle 從其父面板的 Children 集合移至已卸載面板的 Children 集合。
設定 Effects 屬性,以通知 DoDragDrop 方法是否執行移動或複製作業。
按 F5 以建置並執行應用程式。
從 TextBox 選取文字
green
。將文字拖放至 Circle 控制項。
將 Circle 控制項從從左面板拖放至右面板。 Circle 會從左面板的 Children 集合予以移除,並新增至右面板的 Children 集合。
按下 Ctrl 鍵時,將 Circle 控制項從所在的面板拖放至另一個面板。 即會複製 Circle,並將複本新增至接收面板的 Children 集合。