Seeking a Winforms System.ComponentModel.Design Design Surface BeginDrag/EndDrag Event Hook
Asked Answered
L

2

3

I am using C#, Winforms, VS 2017 Enterprise, and the full .NET Framework 4.7.2.

[TLDR section at end!]

I have been working extensively with the System.ComponentModel.Design namespace to create working Visual Studio-like Winforms designer. Setting up the environment for such an end-user forms designer requires deep understanding of objects and interfaces in the aforementioned namespace (and how to wire-up them all) along with toolbox and property grid components. Therefore, it's not possible for me to post a sample of the code. This question requires knowledge of the DesignSurface class (https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.design.designsurface?view=netframework-4.7.2).

The design surface seems to fire no event when a user drags a selected control across the designer surface. I need to hook into "begin-drag"/"end-drag" events so that I can perform clear and then re-render an information panel that reflects the current position of all controls. (I do not want to use timers to intermittently refresh that information.)

There is an ISelectionService interface, which I've implemented. But that gives information only about which controls/components are selected. It doesn't help capture the event that fires when a control-drag operation begins or ends.

Details of design-surface events appear here: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.design?view=netframework-4.7.2

I have tried to leverage IComponentChangeService's ComponentChanged event, but that fires only after a control-drag operation ends (and I need to detect when the control-drag operation begins and ends)...

As a last resort, I used Spy++ to see what events fire when a control is selected and dragged across the design surface. Spy++ helped me identify the initial WM.LBUTTONDOWN message and various mouse-move messages, etc., but leveraging those messages would require a lot of additional coding to ensure that the mouse button was clicked on a designer-surface control, that the control was in fact selected, and that the mouse button remains down, etc.--and even then, I still would have no assurance that the control isn't being resized versus moved. Of course, ideally, I would want to hook into the designer-surface's logic that responds to drag-begin event.

Finally, my requirement is to detect when a single selected control is dragged or when multiple-selected controls are dragged as a group. In both cases, I need to know when the drag starts and when it ends. (To be clear: I'm referring to controls that already are on the design surface--I am not referring to controls on the toolbox that are drag-dropped onto the designer surface...)

TLDR: What I'm seeking is a way to hook into the event that fires as soon as a control or a group of controls already on a designer surface is/are dragged, and a way to hook into the event that fires when that drag operation has ended.

Any thoughts?

Lockman answered 16/1, 2019 at 18:53 Comment(5)
Based on Reza's answer here (#52215765), I'm thinking there might be a way to hook into the events fired by every control on the design surface by obtaining a handle to each control's designer. I'd still have to handle the case where multiple controls are selected on the design surface, then dragged. Not sure if these thoughts might help someone more knowledgeable about this thank I am, but I thought it might help spawn some ideas...Lockman
It may helps: Get all controls of the current form at design-time. Also, IComponentChangeService has ComponentAdded and ComponentRemoved events, which helps you to track component collection changes.Kepner
Thanks, but I had tried those before. I specifically need to know when an existing control--one(s) already on the design surface--are dragged and (ideally) when that drag operation has ended. I need to perform a related task in response to that event.Lockman
I see, the link which I shared (my answer) is just a way to detect all existing controls and the component change service is just to help tracking existing controls. It will not an answer to your question, but probably is part of the solution.Kepner
In fact it doesn't have anything to do with the solution. The solution is using BehaviorService and handling BeginDrag and EndDrag events.Kepner
K
3

You can get BehaviorService and subscribe to its BeginDrag and EndDrag events.

  • BeginDrag: Occurs when the BehaviorService starts a drag-and-drop operation.
  • EndDrag: Occurs when the BehaviorService completes a drag operation.

Example

You first need to get an instance of BehaviorService, for example if you have access to a designer, or a designer host or a site, you can get the behavior service this way:

var behaviorSvc = (BehaviorService)Site.GetService(typeof(BehaviorService));

Then subscribe to the events:

behaviorSvc.BeginDrag += BehaviorSvc_BeginDrag;
behaviorSvc.EndDrag += BehaviorSvc_EndDrag;

and handle the events:

private void BehaviorSvc_EndDrag(object sender, BehaviorDragDropEventArgs e)
{  
}

private void BehaviorSvc_BeginDrag(object sender, BehaviorDragDropEventArgs e)
{
}
Kepner answered 19/1, 2019 at 15:34 Comment(9)
WOW--I think you nailed it! But I am having trouble with line 1 because when I try to use GetService with typeof(BehaviorService), I get a null object even when Site is valid. I looked at the service container for the IDesignerHost and it contains 17 Services (ISelectionService, ComponentSerializationService, etc.) and 5 _fixedServices--but no BehaviorService. I then tried to add the BehaviorService to the service container but cannot figure out how to instantiate the BehaviorService class... I will experiment further but I'm sure you're on to the answer...Lockman
No luck for me yet on obtaining the BehaviorService instance--but please don't do any further research/verification on this until I've had a chance to get it wokring or determine where I'm stuck..... Thanks!Lockman
You're welcome. I've tested the solution in Visual Studio in Windows Forms designer and it works properly. The test scenario for me was getting the service from Site of a BaseForm in OnHandleCreated of the BaseForm, when I was designing a derived form. Then handled those events and by drag-drop, I changed the text of the form, showing 'BeginDrag' and 'EndDrag'.Kepner
I cannot express my gratitude enough other than to up-vote each and every comment you wrote along with accepting and up-voting your answer. Before posting this message, I had read about BehaviorService but dismissed it because (a) I thought I could use it only for my own custom-coded designers and (b) I couldn't find any useful examples showing how to use that service. Also, you correctly pointed out that I was trying to locate the service on the designer object before the service was added by the framework, so your last comment helped quite a bit.Lockman
Now that I know how to respond to events on the BehaviorService object, it opens me up to a whole new level of possibilities for offering a better end-user designer experience. THANK YOU SO MUCH!!!Lockman
@Lockman While I'm fine with the edit, I can say it can be a separate answer as well. Feel free to post it as an answer and you will have my vote. This way all the users can share their comments about it. I also have a comment: You probably can get the service from IDesignerHost service as well.Kepner
Thanks for the suggestion, Reza. Will do shortly!Lockman
Do you guys know how I can cancel this D&D process? This BehaviorDragDropEventArgs has no Property that makes it possible. I already examined BehaviorService code. It has a CancelDrop property, but it's internal, and BehaviorService is sealed, so I cannot inherit it.Outcome
@Outcome I don't have access to VS now, but if the internal method is working, it's fine, call it by reflection.Kepner
L
3

To supplement Reza's fine and correct answer (which I accepted as an answer) on this rather specialized topic:

There many possible/viable ways to wire-up the Winforms designer and its dependent objects (toolbox, property grid, etc.), so the sequence/timing of when particular designer services are added to the design surface's service container will differ. Some services must be explicitly added (such as INameCreationService or IToolboxService); others are added by the framework.

I don't have not yet concluded exactly when the framework adds the BehaviorService to the service container, but I have concluded that this service is added to any working design service (that is, one that lets you drag already-added controls around the design surface using the mouse).

Additionally, many objects in the System.ComponentModel.Design namespace offer access to the collection of available services by exposing the GetService() method. (Reza's answer leverages the Site object's GetService method.) If you find that the GetService(typeof(BehaviorService)) fails to return a valid BehaviorService object, it usually means that the BehaviorService hasn't yet been added to the design surface... I say "yet", because dragging controls already placed on the designer surface requires the presence of a BehaviorService in the host's service container. So if your attempt to get an instance of the BehaviorService using the GetService method fails, chances are that the BehaviorService simply has not yet been added by the designer framework. To solve that problem, you'll usually just need to move your code for hooking BehaviorService events somewhere downstream.

To inspect which services already have been added to the design surface, you can inspect the design surface object's ServiceContainer object's Services collection, which contains the comprehensive list of services. As Reza suggests, a good place to add the code needed to hook into BehaviorService's events is in the base form's OnHandleCreated event handler.

Lockman answered 20/1, 2019 at 22:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.