using System;
using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.Shell32;
using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using IDropTarget = Vanara.PInvoke.Ole32.IDropTarget;
namespace Vanara.Windows.Shell
{
///
/// Provides data for the , , or event.
///
///
/// A DragEventArgs object specifies any data associated with drag/drop events; the current state of the SHIFT, CTRL, and ALT keys; the
/// location of the mouse pointer; and the drag-and-drop effects allowed by the source and target of the drag event.
///
public class DragEventArgs : EventArgs
{
/// Initializes a new instance of the class.
/// The data associated with this event.
/// The current state of the SHIFT, CTRL, and ALT keys.
/// The x-coordinate of the mouse cursor in pixels.
/// The y-coordinate of the mouse cursor in pixels.
/// One of the DROPEFFECT values.
/// One of the DROPEFFECT values.
public DragEventArgs(IDataObject data, MouseButtonState keyState, int x, int y, DROPEFFECT allowedEffect, DROPEFFECT lastEffect)
{
Data = data;
KeyState = keyState;
X = x;
Y = y;
AllowedEffect = allowedEffect;
Effect = lastEffect;
}
/// Gets which drag-and-drop operations are allowed by the originator (or source) of the drag event.
public DROPEFFECT AllowedEffect { get; }
/// Gets the IDataObject that contains the data associated with this event.
public IDataObject Data { get; }
/// Gets or sets the target drop effect in a drag-and-drop operation.
public DROPEFFECT Effect { get; set; }
/// Gets the current state of the SHIFT, CTRL, and ALT keys, as well as the state of the mouse buttons.
public MouseButtonState KeyState { get; }
/// Gets the x-coordinate of the mouse pointer, in screen coordinates.
public int X { get; }
/// Gets the y-coordinate of the mouse pointer, in screen coordinates.
public int Y { get; }
}
///
/// COM object that implements IDropTarget. Solves race problem on drop and simplifies interface calls. All IDropTarget methods call
/// their equivalent On[MethodName] equivalents. To specialize their handling, simply override the On[MethodName] method or hook an event
/// to the corresponding event.
///
///
/// This class provides an easy means of creating a COM based out-of-process DropTarget.
///
/// - Create a .NET application project.
/// - Delete the Program.cs file that includes the Main method.
/// - Create a new class and derive it from with the attributes in the example below.
/// - Ensure the project is built as "X86".
///
/// [Guid("<Your GUID here>"), ClassInterface(ClassInterfaceType.None)]
///public class MyDropTarget : ShellDropTarget
///{
/// // In your constructor, be sure to override the DragEnter and DragDrop events and set the effect you desire.
/// public MyDropTarget() : base()
/// {
/// // Setting the effect to Copy here ensure we continue to get drag/drop messages
/// DragEnter += (s, e) => e.Effect = DragDropEffects.Copy;
///
/// DragDrop += MyDragDrop;
/// }
///
/// // This event handler for DragDrop should never hold up the shell. To accommodate a non-blocking response,
/// // use the 'QueueNonBlockingCallback' method which uses PostThreadMessage to run a delegate as shown below.
/// private void MyDragDrop(object sender, DragEventArgs e)
/// {
/// // Setting the effect to None here ends drag/drop messages
/// e.Effect = DragDropEffects.None;
/// // Notice we're passing in the DragEventArgs.Data property that contains the shell item array from the Shell.
/// QueueNonBlockingCallback(DisplayData, e.Data);
/// }
///
/// // This delegate will get run outside of the Shell's calling path and can display a UI or perform any other
/// // function on the list of shell items passed to it.
/// private void DisplayData(object ido)
/// {
/// // *** Replace with your functionality ***
/// using (var shia = ShellItemArray.FromDataObject(ido as DataObject))
/// {
/// var szMsg = $"Found {shia.Count} item(s): " + string.Join(", ", shia.Select(i => i.Name));
/// MessageBox.Show(szMsg, "MyDropTarget");
/// }
/// // Don't fail to call QuitMessageLoop once you're done processing the Shell call.
/// QuitMessageLoop();
/// }
///
/// // This is the main thread of the application. When called from the Shell, it will append the -embedding argument to indicate that
/// // a message loop needs to start running and the COM server registered. All that is accomplished by calling the 'Run' method
/// // on your new class.
/// [STAThread]
/// private static void Main(string[] args)
/// {
/// // *** Replace with your ProgID, name and display name ***
/// const string progID = "txtfile";
/// const string verbDisplayName = "DropTarget Verb Sample";
/// const string verbName = "DropTargetVerb";
///
/// var arg = args.Length > 0 ? args[0].TrimStart('-', '/').ToLowerInvariant() : null;
/// switch (arg)
/// {
/// // Handle a call from the Shell launching this out-of-proc server
/// case "embedding":
/// new MyDropTarget().Run(TimeSpan.FromSeconds(30));
/// break;
///
/// // Unregister this out-of-proc server from handling the DropTarget. This can be omitted if done by an installer.
/// case "unregister":
/// using (var progid = new ProgId(progID, false))
/// progid.Verbs.Remove(verbName);
/// ShellRegistrar.UnregisterLocalServer<MyDropTarget>(false);
/// break;
///
/// // Register this out-of-proc server to handle the DropTarget. This can be omitted if done by an installer.
/// default:
/// ShellRegistrar.RegisterLocalServer<MyDropTarget>("MyTestDropTarget", systemWide: false);
/// using (var progid = new ProgId(progID, false))
/// using (var verb = progid.Verbs.Add(verbName, verbDisplayName))
/// {
/// verb.DropTarget = Marshal.GenerateGuidForType(typeof(MyDropTarget));
/// verb.NeverDefault = true;
/// }
/// break;
/// }
/// }
///}
///
///
public abstract class ShellDropTarget : ShellCommand, IDropTarget, IInitializeCommand
{
private IDataObject lastDataObject;
private DROPEFFECT lastEffect = DROPEFFECT.DROPEFFECT_NONE;
/// Initializes a new instance of the class.
protected ShellDropTarget() : base() { }
/// Initializes a new instance of the class.
/// The context within which the COM object is to be run.
/// Indicates how connections are made to the class object.
protected ShellDropTarget(CLSCTX classContext, REGCLS classUse) : base(classContext, classUse) { }
/// Occurs when a drag-and-drop operation is started. All calls from this event must be non-blocking.
public event EventHandler DragDrop;
/// Occurs to request whether a drop can be accepted, and, if so, the effect of the drop.
public event EventHandler DragEnter;
/// Occurs when the object is told to remove target feedback and releases the data object.
public event EventHandler DragLeave;
///
/// Occurs so target can provide feedback to the user and communicate the drop's effect to the DoDragDrop function so it can
/// communicate the effect of the drop back to the source.
///
public event EventHandler DragOver;
///
HRESULT IDropTarget.DragEnter(IDataObject pDataObj, MouseButtonState grfKeyState, POINT pt, ref DROPEFFECT pdwEffect)
{
System.Diagnostics.Debug.WriteLine($"IDropTarget.DragEnter: effect={pdwEffect}");
var drgevent = CreateDragEventArgs(pDataObj, grfKeyState, pt, pdwEffect);
DragEnter?.Invoke(this, drgevent);
lastEffect = pdwEffect = drgevent.Effect;
return HRESULT.S_OK;
}
///
HRESULT IDropTarget.DragLeave()
{
System.Diagnostics.Debug.WriteLine("IDropTarget.DragLeave");
DragLeave?.Invoke(this, EventArgs.Empty);
lastDataObject = null;
return HRESULT.S_OK;
}
///
HRESULT IDropTarget.DragOver(MouseButtonState grfKeyState, POINT pt, ref DROPEFFECT pdwEffect)
{
System.Diagnostics.Debug.WriteLine($"IDropTarget.DragOver: effect={pdwEffect}");
var drgevent = CreateDragEventArgs(null, grfKeyState, pt, pdwEffect);
DragOver?.Invoke(this, drgevent);
lastEffect = pdwEffect = drgevent.Effect;
return HRESULT.S_OK;
}
///
HRESULT IDropTarget.Drop(IDataObject pDataObj, MouseButtonState grfKeyState, POINT pt, ref DROPEFFECT pdwEffect)
{
System.Diagnostics.Debug.WriteLine($"IDropTarget.Drop: effect={pdwEffect}");
var drgevent = CreateDragEventArgs(pDataObj, grfKeyState, pt, pdwEffect);
DragDrop?.Invoke(this, drgevent);
pdwEffect = drgevent.Effect;
CancelTimeout();
return HRESULT.S_OK;
}
private DragEventArgs CreateDragEventArgs(IDataObject pDataObj, MouseButtonState grfKeyState, POINT pt, DROPEFFECT pdwEffect)
{
var data = pDataObj ?? lastDataObject;
var drgevent = new DragEventArgs(data, grfKeyState, pt.X, pt.Y, pdwEffect, lastEffect);
lastDataObject = data;
return drgevent;
}
}
}