locked
Move cursor position during drag drop operation RRS feed

  • Question

  • Hi,

    I'm enhancing the behaviour of out of the box WF designer drag/drop experience.  One of the things I'm trying to do (in part) is when a toolbox item is drag over an existing activity designer and then dropped, I want the toolbox item's activity to be dropped close to the activity designer on which it was to be dropped, but not directly on top of the designer.

    To accomplish this, I detect when a drop operation is about to occur and then I attempt to move the cursor position next to the activity designer.  The problem I'm having is moving the cursor during the drop operation.  I've tried various ways to do this programmatically, but nothing seems to work.  Or, perhaps it is getting moved but then the workflow designer (and in particular the flowchart designer) does something special with mouse position or otherwise ignores it. 

    I started out posting this question on the WPF forum, but now I'm beginning to wonder if perhaps there is something unique to  the WF/flowchart designer that's causing the mouse not to move (or to move back to the original position perhaps) rather than a general issue with moving the mouse during drag/drop operations.

    Can anyone provide any guidance?

    Thanks,

    Notre

    Wednesday, September 22, 2010 6:37 PM

Answers

  • Hi Notre,

    I think you will find some really interesting bugs with this next approach, but at least it uses public API, just not a managed API. It might also be able to make it drop outside the flowchart, but that is untested.

      [DllImport("User32.dll", CharSet = CharSet.Auto)]
      private static extern IntPtr GetProp(IntPtr hwnd, string propertyNameStr);
    
      [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000122-0000-0000-C000-000000000046")]
      public interface IOleDropTarget
      {
        [PreserveSig]
        int OleDragEnter([In, MarshalAs(UnmanagedType.Interface)] object pDataObj, [In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
    
        [PreserveSig]
        int OleDragOver([In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
    
        [PreserveSig]
        int OleDragLeave();
    
        [PreserveSig]
        int OleDrop([In, MarshalAs(UnmanagedType.Interface)] object pDataObj, [In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
      }
    
      internal void flowchartDesigner_PreviewDrop(object sender, DragEventArgs e)
      {
        (sender as ActivityDesigner).PreviewDrop -= new DragEventHandler(flowchartDesigner_PreviewDrop);
        ActivityDesigner flowchartDesigner = sender as ActivityDesigner;
        Point p = flowchartDesigner.PointToScreen(new Point(flowchartDesigner.ActualWidth + 1.0, flowchartDesigner.ActualHeight + 1.0));
        e.Handled = true;
        Window w = Window.GetWindow(flowchartDesigner);
        IntPtr hwnd = new WindowInteropHelper(w).Handle;
        IntPtr pinterface = GetProp(hwnd, "OleDropTargetInterface");
        ////Guid iid = new Guid("00000122-0000-0000-C000-000000000046");
        object iunknown = EnterpriseServicesHelper.WrapIUnknownWithComObject(pinterface);
        IOleDropTarget dt = (IOleDropTarget)iunknown;
        System.Drawing.Point p2 = new System.Drawing.Point
        {
          X = (int)(p.X + 1e-9) - 50,
          Y = (int)(p.Y + 1e-9) - 50,
        };
    
        int effect = (int)e.Effects;
        int b = dt.OleDrop(e.Data, (int)e.KeyStates, p2, ref effect);
      }
    
    I couldn't think of any other public API ways of doing this for now.
    Tim

    • Marked as answer by Notre Thursday, September 30, 2010 11:20 PM
    Thursday, September 30, 2010 9:30 PM

All replies

  • If you do it in the previewdrop or drop handler, would be that the mouse coordinates have already been recorded and stored in the mouse event, and hence moving the cursor doesn't affect those stored coordinates... is that it, or something else?
    Tim

    Thursday, September 23, 2010 3:42 AM
  • Hi Tim,

    Interesting idea.  That could explain why the item is being dropped in the mouse coordinates prior to my programmatic move attempt.  But why doesn't the cursor at least move (visually) to the location I specified programmatically (even if the item is dropped at the old mouse coordinates, those prior to the programmatic attempt to move the cursor)?

    Thanks,

    Notre

    Thursday, September 23, 2010 4:35 PM
  • Hi Tim,

    I suspect that this problem is a bit abstract and difficult to appreciate without a code sample.  As a code sample would be too large to post here, I've emailed you a code sample instead.

    Please check your email (including spam filters) from notre_poubelle AT yahoo.com, with a subject of "Re: WF forum post - Move cursor position during drag drop operation").

    Hopefully this might help make things clearer and maybe you can provide a suggestion.

    Thank you,

    Notre

    Friday, September 24, 2010 6:50 PM
  • My suspicion seems correct - with reflector, found there is a private field in the DragEventArgs. And if you set it like this, then the point of dropping does get updated. However, the drop event is still being routed to the flowchart designer, and the flowchart designer handles the event even though it is now a drop point outside of the flowchart designer's screen area, and the flowchart designer will grow, which probably isn't what you want?

    Point p = flowchartDesigner.PointToScreen(new Point(flowchartDesigner.ActualWidth + 1.0, flowchartDesigner.ActualHeight + 1.0));

    e.GetType().GetField(

     

    "_dropPoint", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(e, p)

    Friday, September 24, 2010 9:18 PM
  • Hi Tim,

    Yes, your theory does seem to be 100% correct!  You're right that I don't want to drop outside the flowchart rectangle; my original sample was contrived.  But using the reflection code you provided, I was able to modify the drop position as desired.

    The only question is, is there any way to modify the drop point without using reflection, accessing non-public members?

    Thanks,

    Notre

    Monday, September 27, 2010 3:00 PM
  • Seems like WPF is going out of its way to make that difficult, but possibly you could intercept the original event, then raise a new event with RaiseEvents and DragEventArgs?
    Tim

    Monday, September 27, 2010 6:09 PM
  • Another good idea!  However, I think I might be stuck on the same problem then of using a non-public API (via reflection).

    I'm intercepting the original event, by using PreviewDrop.  This event handler can set the Handled property to true, and WF respects this and doesn't do any further drop behaviour.  So far, so good.  But now my problem is in raising the event.  I realize I can create a custom event and raise it.  But, I need to tie into the WF built flowchart design drag/drop behaviour.  In particular, I want the flowchart designer's Drop handler (and all its associated logic) to be fired (just with different coordinates).  From what I've been reading, it sounds like there's no way to raise an event that is not explicitly declared in the class -- unless we go back to using reflection and getting hold of the non-public "on<eventname>" method and invoking that method.

    Thanks,

    Notre

    Monday, September 27, 2010 8:03 PM
  • Hi Notre,
    Sorry should have explained in more detail. I haven't tried it out, but I was just thinking of using the public api UIElement.RaiseEvent using the also public DropEvent property (on DragDrop class)

    public static readonly RoutedEvent DropEvent;

    as the event. Not invoking the event handler directly.
    Tim

    Wednesday, September 29, 2010 1:34 AM
  • Hi Tim,

    Oh, I misunderstood what you were suggesting! Thanks for the clarification.

    I tried to do what you suggested, which I believe includes creating a new DragEventArgs object.  While the constructor for DragEventArgs is public, it is only the default constructor and the AllowedEffects, Data, and KeyStates properties are read only, so I'm not sure how I could set these.  And I guess the _dropPoint is a non-public field.  Looking at the class in Reflector, there is another constructor that allows all these items to be set, but it is an internal construcor :(

    Thank,

    Notre

    Wednesday, September 29, 2010 4:51 PM
  • Sorry, that is what always seems to happen when I suggest stuff without trying it out. :(
    Turns out I was looking at the wrong DragEventArgs class in the framework, there is more than one of them. I have some other ideas I hope I can try them out soon.
    In the meantime you could probably populate the internal constructor parameters with the properties you pull off from the original drag event.
    Tim

    Wednesday, September 29, 2010 8:37 PM
  • No worries - so far your ideas have been very good; we've just run into the snag of non-public API :)

    I can't populate (nor invoke) the internal constructor parameters without using reflection :)

    I look forward to any other ideas you have, when you get a chance to try them.  (I don't mind being the tester, if you want to continue to throw out untested ideas, if that's easier for you).

    Thanks,

    Notre

    Wednesday, September 29, 2010 9:31 PM
  • Hi Notre,

    I think you will find some really interesting bugs with this next approach, but at least it uses public API, just not a managed API. It might also be able to make it drop outside the flowchart, but that is untested.

      [DllImport("User32.dll", CharSet = CharSet.Auto)]
      private static extern IntPtr GetProp(IntPtr hwnd, string propertyNameStr);
    
      [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000122-0000-0000-C000-000000000046")]
      public interface IOleDropTarget
      {
        [PreserveSig]
        int OleDragEnter([In, MarshalAs(UnmanagedType.Interface)] object pDataObj, [In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
    
        [PreserveSig]
        int OleDragOver([In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
    
        [PreserveSig]
        int OleDragLeave();
    
        [PreserveSig]
        int OleDrop([In, MarshalAs(UnmanagedType.Interface)] object pDataObj, [In, MarshalAs(UnmanagedType.U4)] int grfKeyState, [In, MarshalAs(UnmanagedType.Struct)] System.Drawing.Point pt, [In, Out] ref int pdwEffect);
      }
    
      internal void flowchartDesigner_PreviewDrop(object sender, DragEventArgs e)
      {
        (sender as ActivityDesigner).PreviewDrop -= new DragEventHandler(flowchartDesigner_PreviewDrop);
        ActivityDesigner flowchartDesigner = sender as ActivityDesigner;
        Point p = flowchartDesigner.PointToScreen(new Point(flowchartDesigner.ActualWidth + 1.0, flowchartDesigner.ActualHeight + 1.0));
        e.Handled = true;
        Window w = Window.GetWindow(flowchartDesigner);
        IntPtr hwnd = new WindowInteropHelper(w).Handle;
        IntPtr pinterface = GetProp(hwnd, "OleDropTargetInterface");
        ////Guid iid = new Guid("00000122-0000-0000-C000-000000000046");
        object iunknown = EnterpriseServicesHelper.WrapIUnknownWithComObject(pinterface);
        IOleDropTarget dt = (IOleDropTarget)iunknown;
        System.Drawing.Point p2 = new System.Drawing.Point
        {
          X = (int)(p.X + 1e-9) - 50,
          Y = (int)(p.Y + 1e-9) - 50,
        };
    
        int effect = (int)e.Effects;
        int b = dt.OleDrop(e.Data, (int)e.KeyStates, p2, ref effect);
      }
    
    I couldn't think of any other public API ways of doing this for now.
    Tim

    • Marked as answer by Notre Thursday, September 30, 2010 11:20 PM
    Thursday, September 30, 2010 9:30 PM
  • Tim  - this is brilliant!  It seems to work just perfectly.  (Don't see any bugs yet).

    Thanks for your time & perseverance on this forum post!

    Notre

    Thursday, September 30, 2010 11:20 PM