From 9712ee5db645d5fcc2726a393f95ee488d884676 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Wed, 24 Aug 2022 12:57:32 -0700 Subject: [PATCH] Add Control.DragEnd event This is useful when you want to know when the drag operation finishes after DoDragDrop() is called, since it is only blocking on Windows. --- src/Eto.Gtk/Forms/Controls/GridViewHandler.cs | 4 +- .../Forms/Controls/TreeGridViewHandler.cs | 4 +- src/Eto.Gtk/Forms/GtkControl.cs | 29 +++++++++++- src/Eto.Mac/Forms/Controls/GridHandler.cs | 5 +- src/Eto.Mac/Forms/Controls/GridViewHandler.cs | 6 +++ .../Forms/Controls/TreeGridViewHandler.cs | 6 +++ src/Eto.Mac/Forms/EtoDragSource.cs | 16 ++++++- src/Eto.Mac/Forms/MacView.cs | 5 +- src/Eto.Mac/MacConversions.cs | 7 ++- src/Eto.WinForms/Forms/WindowsControl.cs | 16 +++++-- src/Eto.Wpf/Forms/WpfFrameworkElement.cs | 9 +++- src/Eto/Forms/Controls/Control.cs | 47 +++++++++++++++++++ .../Sections/Behaviors/DragDropSection.cs | 12 +++++ 13 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs b/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs index f3cb82b9ae..cdc7125db4 100755 --- a/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/GridViewHandler.cs @@ -249,7 +249,7 @@ protected class GridViewConnector : GridConnector public new GridViewHandler Handler { get { return (GridViewHandler)base.Handler; } } - protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location, uint time = 0, object controlObject = null) + protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location, uint time = 0, object controlObject = null, DataObject data = null) { var t = Handler?.Control; GridViewDragInfo dragInfo = _dragInfo; @@ -264,7 +264,7 @@ protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, Point } } - return base.GetDragEventArgs(context, location, time, dragInfo); + return base.GetDragEventArgs(context, location, time, dragInfo, data); } public override void HandleDragMotion(object o, Gtk.DragMotionArgs args) diff --git a/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs index f5c19ccf8e..b1bde785b7 100755 --- a/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/TreeGridViewHandler.cs @@ -271,7 +271,7 @@ public void HandleRowActivated(object o, Gtk.RowActivatedArgs args) Handler?.Callback.OnActivated(Handler.Widget, new TreeGridViewItemEventArgs(Handler.model.GetItemAtPath(args.Path))); } - protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location, uint time = 0, object controlObject = null) + protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location, uint time = 0, object controlObject = null, DataObject data = null) { var h = Handler; var t = h?.Control; @@ -298,7 +298,7 @@ protected override DragEventArgs GetDragEventArgs(Gdk.DragContext context, Point } } - return base.GetDragEventArgs(context, location, time, dragInfo); + return base.GetDragEventArgs(context, location, time, dragInfo, data); } public override void HandleDragMotion(object o, Gtk.DragMotionArgs args) diff --git a/src/Eto.Gtk/Forms/GtkControl.cs b/src/Eto.Gtk/Forms/GtkControl.cs index 89a2a6b736..5ed060e042 100644 --- a/src/Eto.Gtk/Forms/GtkControl.cs +++ b/src/Eto.Gtk/Forms/GtkControl.cs @@ -63,6 +63,7 @@ static class GtkControl public static readonly object ScrollAmount_Key = new object(); public static readonly object DragInfo_Key = new object(); public static readonly object DropSource_Key = new object(); + public static readonly object DropSourceData_Key = new object(); public static readonly object Font_Key = new object(); public static readonly object TabIndex_Key = new object(); public static readonly object Cursor_Key = new object(); @@ -455,6 +456,9 @@ public override void AttachEvent(string id) HandleEvent(Eto.Forms.Control.DragOverEvent); DragControl.DragLeave += Connector.HandleDragLeave; break; + case Eto.Forms.Control.DragEndEvent: + DragControl.DragEnd += Connector.HandleDragEnd; + break; case Eto.Forms.Control.EnabledChangedEvent: #if GTK3 ContainerControl.StateFlagsChanged += Connector.HandleStateFlagsChangedForEnabled; @@ -720,7 +724,7 @@ public virtual void MappedEvent(object sender, EventArgs e) Handler?.Callback.OnShown(Handler.Widget, EventArgs.Empty); } - protected virtual DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location, uint time = 0, object controlObject = null) + protected virtual DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF? location = null, uint time = 0, object controlObject = null, DataObject data = null) { var widget = Gtk.Drag.GetSourceWidget(context); var source = widget?.Data[GtkControl.DropSource_Key] as Eto.Forms.Control; @@ -731,7 +735,7 @@ protected virtual DragEventArgs GetDragEventArgs(Gdk.DragContext context, PointF var action = context.SelectedAction; #endif - var data = new DataObject(new DataObjectHandler(Handler.DragControl, context, time)); + data = data ?? new DataObject(new DataObjectHandler(Handler.DragControl, context, time)); if (location == null) location = Handler.PointFromScreen(Mouse.Position); @@ -809,6 +813,23 @@ public virtual void HandleDragLeave(object o, Gtk.DragLeaveArgs args) Eto.Forms.Application.Instance.AsyncInvoke(() => DragArgs = null); } + public virtual void HandleDragEnd(object o, Gtk.DragEndArgs args) + { + var handler = Handler; + if (handler == null) + return; + var data = handler.DragControl.Data[GtkControl.DropSourceData_Key] as DataObject; + + var e = GetDragEventArgs(args.Context, data: data); +#if GTK2 + e.Effects = args.Context.Action.ToEto(); +#else + e.Effects = args.Context.SelectedAction.ToEto(); +#endif + handler.Callback.OnDragEnd(handler.Widget, e); + handler.DragControl.Data[GtkControl.DropSourceData_Key] = null; + } + #if GTK3 public virtual void HandleStateFlagsChangedForEnabled(object o, Gtk.StateFlagsChangedArgs args) { @@ -971,6 +992,10 @@ public void DoDragDrop(DataObject data, DragEffects allowedEffects, Image image, DragInfo = new DragInfoObject { Data = data, AllowedEffects = allowedEffects }; DragControl.Data[GtkControl.DropSource_Key] = Widget; + + // set data and ensure it gets cleared out. + DragControl.Data[GtkControl.DropSourceData_Key] = data; + HandleEvent(Eto.Forms.Control.DragEndEvent); #if GTKCORE var context = Gtk.Drag.BeginWithCoordinates(DragControl, targets, allowedEffects.ToGdk(), 1, Gtk.Application.CurrentEvent, -1, -1); diff --git a/src/Eto.Mac/Forms/Controls/GridHandler.cs b/src/Eto.Mac/Forms/Controls/GridHandler.cs index 71ad92b28f..a2c81c9736 100644 --- a/src/Eto.Mac/Forms/Controls/GridHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridHandler.cs @@ -124,6 +124,8 @@ class GridDragInfo public NSDragOperation AllowedOperation { get; set; } public NSImage DragImage { get; set; } public PointF ImageOffset { get; set; } + + public DataObject Data { get; set; } public CGPoint GetDragImageOffset() { @@ -722,7 +724,8 @@ public override void DoDragDrop(DataObject data, DragEffects allowedAction, Imag { AllowedOperation = allowedAction.ToNS(), DragImage = image.ToNS(), - ImageOffset = origin + ImageOffset = origin, + Data = data }; } else diff --git a/src/Eto.Mac/Forms/Controls/GridViewHandler.cs b/src/Eto.Mac/Forms/Controls/GridViewHandler.cs index b3b91562cf..1c68b24b38 100644 --- a/src/Eto.Mac/Forms/Controls/GridViewHandler.cs +++ b/src/Eto.Mac/Forms/Controls/GridViewHandler.cs @@ -271,6 +271,12 @@ public override void DraggingSessionEnded(NSTableView tableView, NSDraggingSessi h.CustomSelectedRows = null; h.Callback.OnSelectionChanged(h.Widget, EventArgs.Empty); } + + var allowedOperation = h.DragInfo?.AllowedOperation ?? NSDragOperation.None; + var data = h.DragInfo?.Data; + var args = new DragEventArgs(h.Widget, data, allowedOperation.ToEto(), endedAtScreenPoint.ToEto(h.ContainerControl), Keyboard.Modifiers, Mouse.Buttons); + args.Effects = operation.ToEto(); + h.Callback.OnDragEnd(h.Widget, args); } public override bool WriteRows(NSTableView tableView, NSIndexSet rowIndexes, NSPasteboard pboard) diff --git a/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs b/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs index 70507a0058..08ba17b7ce 100644 --- a/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs +++ b/src/Eto.Mac/Forms/Controls/TreeGridViewHandler.cs @@ -465,6 +465,12 @@ public override void DraggingSessionEnded(NSOutlineView outlineView, NSDraggingS h.CustomSelectedItems = null; h.Callback.OnSelectionChanged(h.Widget, EventArgs.Empty); } + + var allowedOperation = h.DragInfo?.AllowedOperation ?? NSDragOperation.None; + var data = h.DragInfo?.Data; + var args = new DragEventArgs(h.Widget, data, allowedOperation.ToEto(), screenPoint.ToEto(h.ContainerControl), Keyboard.Modifiers, Mouse.Buttons); + args.Effects = operation.ToEto(); + h.Callback.OnDragEnd(h.Widget, args); } public override bool OutlineViewwriteItemstoPasteboard(NSOutlineView outlineView, NSArray items, NSPasteboard pboard) diff --git a/src/Eto.Mac/Forms/EtoDragSource.cs b/src/Eto.Mac/Forms/EtoDragSource.cs index d38b8ae6fe..b79e725f8b 100644 --- a/src/Eto.Mac/Forms/EtoDragSource.cs +++ b/src/Eto.Mac/Forms/EtoDragSource.cs @@ -1,10 +1,13 @@ using System; - +using Eto.Forms; namespace Eto.Mac.Forms { class EtoDragSource : NSDraggingSource { + public DataObject Data { get; set; } + public IMacViewHandler Handler { get; set; } + [Export("sourceView")] public NSView SourceView { get; set; } @@ -16,5 +19,16 @@ public NSDragOperation DraggingSessionSourceOperationMask(NSDraggingSession sess { return AllowedOperation; } + + [Export("draggingSession:endedAtPoint:operation:")] + public void DraggingSessionEnded(NSDraggingSession session, CGPoint point, NSDragOperation operation) + { + var h = Handler; + if (h == null) + return; + var args = new DragEventArgs(h.Widget, Data, AllowedOperation.ToEto(), point.ToEto(), Keyboard.Modifiers, Mouse.Buttons, this); + args.Effects = operation.ToEto(); + h.Callback.OnDragEnd(h.Widget, args); + } } } diff --git a/src/Eto.Mac/Forms/MacView.cs b/src/Eto.Mac/Forms/MacView.cs index 0de8cc27a1..bc03142e3b 100644 --- a/src/Eto.Mac/Forms/MacView.cs +++ b/src/Eto.Mac/Forms/MacView.cs @@ -817,6 +817,9 @@ public override void AttachEvent(string id) case Eto.Forms.Control.DragLeaveEvent: AddMethod(MacView.selDraggingExited, MacView.TriggerDraggingExited_Delegate, "v@:@", DragControl); break; + case Eto.Forms.Control.DragEndEvent: + // handled in EtoDragSource, TreeGridViewHandler.EtoDragSource, and GridViewHandler.EtoDragSource + break; default: base.AttachEvent(id); break; @@ -1268,7 +1271,7 @@ public virtual void DoDragDrop(DataObject data, DragEffects allowedAction, Image { var handler = data.Handler as IDataObjectHandler; - var source = new EtoDragSource { AllowedOperation = allowedAction.ToNS(), SourceView = ContainerControl }; + var source = new EtoDragSource { AllowedOperation = allowedAction.ToNS(), SourceView = ContainerControl, Handler = this, Data = data }; NSDraggingItem[] draggingItems = null; if (image != null) diff --git a/src/Eto.Mac/MacConversions.cs b/src/Eto.Mac/MacConversions.cs index 3ed8772a1f..8151c8212b 100644 --- a/src/Eto.Mac/MacConversions.cs +++ b/src/Eto.Mac/MacConversions.cs @@ -531,7 +531,12 @@ public static NSBorderType ToNS(this BorderType border) } } - public static DataObject ToEto(this NSPasteboard pasteboard) => new DataObject(new DataObjectHandler(pasteboard)); + public static DataObject ToEto(this NSPasteboard pasteboard) + { + if (pasteboard == null) + return null; + return new DataObject(new DataObjectHandler(pasteboard)); + } public static NSPasteboard ToNS(this DataObject data) => DataObjectHandler.GetControl(data); diff --git a/src/Eto.WinForms/Forms/WindowsControl.cs b/src/Eto.WinForms/Forms/WindowsControl.cs index 1a1216936a..ef79067207 100644 --- a/src/Eto.WinForms/Forms/WindowsControl.cs +++ b/src/Eto.WinForms/Forms/WindowsControl.cs @@ -474,6 +474,9 @@ public override void AttachEvent(string id) Callback.OnDragLeave(Widget, new DragEventArgs(null, new DataObject(), DragEffects.None, PointF.Empty, Keys.None, MouseButtons.None)); }; break; + case Eto.Forms.Control.DragEndEvent: + // Handled in DoDragDrop as it is blocking on Windows. + break; case Eto.Forms.Control.EnabledChangedEvent: Control.EnabledChanged += Control_EnabledChanged; break; @@ -496,7 +499,9 @@ DragEventArgs GetDragEventArgs(swf.DragEventArgs data) var modifiers = data.GetEtoModifiers(); var buttons = data.GetEtoButtons(); var location = PointFromScreen(new PointF(data.X, data.Y)); - return new SwfDragEventArgs(source, dragData, data.AllowedEffect.ToEto(), location, modifiers, buttons); + var args = new SwfDragEventArgs(source, dragData, data.AllowedEffect.ToEto(), location, modifiers, buttons); + args.Effects = data.Effect.ToEto(); + return args; } void HandleMouseWheel(object sender, swf.MouseEventArgs e) @@ -1000,6 +1005,7 @@ public void DoDragDrop(DataObject data, DragEffects allowedEffects, Image image, { var dataObject = data.ToSwf(); WindowsControl.DragSourceControl = Widget; + swf.DragDropEffects effects; if (UseShellDropManager) { swf.DragSourceHelper.AllowDropDescription(true); @@ -1010,7 +1016,7 @@ public void DoDragDrop(DataObject data, DragEffects allowedEffects, Image image, swf.SwfDataObjectExtensions.SetDragImage(dataObject, image.ToSD(), cursorOffset.ToSDPoint()); swf.DragSourceHelper.RegisterDefaultDragSource(Control, dataObject); - Control.DoDragDrop(dataObject, allowedEffects.ToSwf()); + effects = Control.DoDragDrop(dataObject, allowedEffects.ToSwf()); swf.DragSourceHelper.UnregisterDefaultDragSource(Control); } else @@ -1018,9 +1024,13 @@ public void DoDragDrop(DataObject data, DragEffects allowedEffects, Image image, if (image != null) Debug.WriteLine("DoDragDrop cannot show drag image when UseShellDropManager is false"); - Control.DoDragDrop(dataObject, allowedEffects.ToSwf()); + effects = Control.DoDragDrop(dataObject, allowedEffects.ToSwf()); } WindowsControl.DragSourceControl = null; + + var args = new DragEventArgs(Widget, data, allowedEffects, PointFromScreen(Mouse.Position), Keyboard.Modifiers, Mouse.Buttons); + args.Effects = effects.ToEto(); + Callback.OnDragEnd(Widget, args); } public Window GetNativeParentWindow() => ContainerControl.FindForm().ToEtoWindow(); diff --git a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs index 49fa178bc8..087018e4e3 100755 --- a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs +++ b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs @@ -516,6 +516,9 @@ public override void AttachEvent(string id) Control.DragLeave += Control_DragLeave; HandleEvent(Eto.Forms.Control.DragEnterEvent); // need DragEnter so it doesn't get called when going over children break; + case Eto.Forms.Control.DragEndEvent: + // handled in DoDragDrop, as it is blocking on Windows + break; case Eto.Forms.Control.EnabledChangedEvent: Control.IsEnabledChanged += Control_IsEnabledChanged; break; @@ -1028,10 +1031,14 @@ public void DoDragDrop(DataObject data, DragEffects allowedAction, Image image, sw.WpfDataObjectExtensions.SetDragImage(dataObject, image.ToWpf(), PointF.Empty.ToWpf()); } - sw.DragDrop.DoDragDrop(Control, dataObject, allowedAction.ToWpf()); + var effects = sw.DragDrop.DoDragDrop(Control, dataObject, allowedAction.ToWpf()); WpfFrameworkElement.DragSourceControl = null; sw.DragSourceHelper.UnregisterDefaultDragSource(Control); + + var args = new DragEventArgs(Widget, data, allowedAction, PointFromScreen(Mouse.Position), Keyboard.Modifiers, Mouse.Buttons); + args.Effects = effects.ToEto(); + Callback.OnDragEnd(Widget, args); } diff --git a/src/Eto/Forms/Controls/Control.cs b/src/Eto/Forms/Controls/Control.cs index 35a1df3b07..413d16b812 100644 --- a/src/Eto/Forms/Controls/Control.cs +++ b/src/Eto/Forms/Controls/Control.cs @@ -682,6 +682,31 @@ public event EventHandler DragLeave /// Event arguments protected virtual void OnDragLeave(DragEventArgs e) => Properties.TriggerEvent(DragLeaveEvent, this, e); + + /// + /// Event identifier for handlers when attaching the event + /// + public const string DragEndEvent = "Control.DragEnd"; + + /// + /// Occurs for a source control after a call to when the drag operation has ended. + /// The is the final used at the drop destination. + /// + /// + /// For controls that you are dragging from this event is useful to know what to do with the dragged content after it is dropped in a different control or application. + /// + public event EventHandler DragEnd + { + add { Properties.AddHandlerEvent(DragEndEvent, value); } + remove { Properties.RemoveEvent(DragEndEvent, value); } + } + + /// + /// Raises the event. + /// + /// Event arguments + protected virtual void OnDragEnd(DragEventArgs e) => Properties.TriggerEvent(DragEndEvent, this, e); + /// /// Event identifier for handlers when attaching the event /// @@ -724,6 +749,7 @@ static Control() EventLookup.Register(c => c.OnDragOver(null), Control.DragOverEvent); EventLookup.Register(c => c.OnDragEnter(null), Control.DragEnterEvent); EventLookup.Register(c => c.OnDragLeave(null), Control.DragLeaveEvent); + EventLookup.Register(c => c.OnDragEnd(null), Control.DragEndEvent); EventLookup.Register(c => c.OnEnabledChanged(null), Control.EnabledChangedEvent); } @@ -1313,6 +1339,10 @@ public virtual bool AllowDrop /// /// Starts drag operation using this control as drag source. /// + /// + /// This method can be blocking on some platforms (Wpf, WinForms), and non-blocking on others (Mac, Gtk). + /// Use the event to determine when the drag operation is completed and get its resulting DragEffects. + /// /// Drag data. /// Allowed action. public virtual void DoDragDrop(DataObject data, DragEffects allowedEffects) @@ -1323,6 +1353,10 @@ public virtual void DoDragDrop(DataObject data, DragEffects allowedEffects) /// /// Starts drag operation using this control as drag source. /// + /// + /// This method can be blocking on some platforms (Wpf, WinForms), and non-blocking on others (Mac, Gtk). + /// Use the event to determine when the drag operation is completed and get its resulting DragEffects. + /// /// Drag data. /// Allowed effects. /// Custom drag image @@ -1510,6 +1544,10 @@ public static implicit operator Control(Image image) /// void OnDragLeave(Control widget, DragEventArgs e); /// + /// Raises the DragEnd event. + /// + void OnDragEnd(Control widget, DragEventArgs e); + /// /// Raises the EnabledChanged event. /// void OnEnabledChanged(Control widget, EventArgs e); @@ -1669,6 +1707,15 @@ public void OnDragLeave(Control widget, DragEventArgs e) widget.OnDragLeave(e); } + /// + /// Raises the DragEnd event. + /// + public void OnDragEnd(Control widget, DragEventArgs e) + { + using (widget.Platform.Context) + widget.OnDragEnd(e); + } + /// /// Raises the EnabledChanged event. /// diff --git a/test/Eto.Test/Sections/Behaviors/DragDropSection.cs b/test/Eto.Test/Sections/Behaviors/DragDropSection.cs index 5b044e1d1b..10caedd8c5 100644 --- a/test/Eto.Test/Sections/Behaviors/DragDropSection.cs +++ b/test/Eto.Test/Sections/Behaviors/DragDropSection.cs @@ -76,6 +76,7 @@ DataObject CreateDataObject() // sources var buttonSource = new Button { Text = "Source" }; + LogSourceEvents(buttonSource); buttonSource.MouseDown += (sender, e) => { if (e.Buttons != MouseButtons.None) @@ -86,6 +87,7 @@ DataObject CreateDataObject() }; var panelSource = new Panel { BackgroundColor = Colors.Red, Size = new Size(50, 50) }; + LogSourceEvents(panelSource); panelSource.MouseMove += (sender, e) => { if (e.Buttons != MouseButtons.None) @@ -96,6 +98,7 @@ DataObject CreateDataObject() }; var treeSource = new TreeGridView { Size = new Size(200, 200) }; + LogSourceEvents(treeSource); treeSource.SelectedItemsChanged += (sender, e) => Log.Write(treeSource, $"TreeGridView.SelectedItemsChanged (source) Rows: {string.Join(", ", treeSource.SelectedRows.Select(r => r.ToString()))}"); treeSource.DataStore = CreateTreeData(); SetupTreeColumns(treeSource); @@ -116,6 +119,7 @@ DataObject CreateDataObject() }; var gridSource = new GridView { }; + LogSourceEvents(gridSource); gridSource.SelectedRowsChanged += (sender, e) => Log.Write(gridSource, $"GridView.SelectedItemsChanged (source): {string.Join(", ", gridSource.SelectedRows.Select(r => r.ToString()))}"); SetupGridColumns(gridSource); gridSource.DataStore = CreateGridData(); @@ -381,6 +385,14 @@ string WriteDragInfo(object sender, DragEventArgs e) sb.Append($"\n\tUris: {string.Join(", ", uris.Select(r => r.IsFile ? r.LocalPath : r.AbsoluteUri))})"); return sb.ToString(); } + + void LogSourceEvents(Control control) + { + control.DragEnd += (sender, e) => + { + Log.Write(sender, $"DragEnd: Effects: {e.Effects}, {WriteDragInfo(sender, e)}"); + }; + } void LogEvents(Control control) {