Seeking a Winforms System.ComponentModel.Design De

2020-07-22 16:39发布

问题:

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://docs.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://docs.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?

回答1:

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)
{
}


回答2:

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.