I've written a custom control (an immediate child of Forms.Control) in C++/CLI that will offload most of its processing and all of its painting to a separate legacy-ish MFC control. I'm wrapping this control in a C# application. I need to be able to drag items onto the C++/CLI control from a UserControl on the same form in the application.
For some reason, my custom control isn't receiving any of the drag/drop events in spite of my setting AllowDrop to true. (I've verified that AllowDrop is true at runtime.) I never get an opportunity to examine/manipulate the DragEventArgs because the callbacks are never called.
I've verified that drag and drop otherwise functions normally. If I replace the custom control with a Panel, for example, I get the dragdrop callbacks just fine.
Is there something additional that must be implemented in a Control to support dragdrop callbacks? Is there something subtle that must be dealt with for the system to recognize that a control is being hovered over and should be the target of a dragdrop operation?
Notes:
The MFC control that will do the painting is not yet available to me, so I'm just painting a simple gradient background (via OnPaintBackground) that I'm trying to paint to. I'm also painting a smaller gradient rectangle in OnPaint.
I have tried setting AllowDrop = true both before the custom control's handle is created (via designer/constructor code) and after the handle is created (via OnHandleCreated override). No difference in behavior.
The drag cursor never changes from the default "no drag available" cursor over the custom control.
I'm calling this.DoDragDrop() from an event handler for a MouseDown event on a subcontrol of the UserControl.
DragDrop seems to work ok with another custom control I created in C# in the same assembly as the user control. It captures the callbacks just fine. Only my C++/CLI control isn't seeing them. The initializecomponent goo is irrelevant. I can comment it all out with no change.
I figured out my mistake, so I'll answer it here in case someone else makes a similar mistake. It looks like I did hint at the answer in my question, though I didn't realize it at the time.
In my question, I stated that
I have tried setting AllowDrop = true
both before the custom control's
handle is created (via
designer/constructor code) and after
the handle is created (via
OnHandleCreated override). No
difference in behavior.
This is actually where the breakdown occurred. I failed to read the OnHandleCreated documentation that specifically states I needed to remember to call the base class' implementation of OnHandleCreated.
Reflector shows that Control::OnHandleCreated is responsible for setting up Control's internal event notification structure. I never called Control::OnHandleCreated, so the internal event notification structure was never getting properly built up. This eventing structure (reflector again shows) is responsible for firing OnDragOver, etc, so the missing eventing structure resulted in my missing events.
Answer:
Be sure that you call the base class implementation of OnHandleCreated when you override it!
Related
If you create a standard C# WinForms application, you fill find that a form has two events: Move and LocationChanged.
Move is raised when the form moves and LocationChanged is raised when the form location property changes.
Surely if the form moves, the location property will change, too?
What is the difference between the two events? In which case will one fire and not the other?
The Move and LocationChanged events are declared on the Control class, which is then inherited by ScrollableControl, ContainerControl and finally Form.
According to the source code, OnLocationChanged calls OnMove before it invokes the LocationChanged event handler. So, the OnMove event will be raised first and then LocationChanged. You could in theory handle both events knowing that Move will be occur first.
If you look through the source you'll see that LocationChanged is raised when the bounds change (or similar events). You'll also notice that the only thing which actually invokes OnMove is in fact OnLocationChanged.
According to MSDN, the LocationChanged event:
Occurs when the Location property value has changed.... This event is
raised if the Location property is changed by either a programmatic
modification or through interaction.
It makes no such distinction for OnMove, where it merely states:
Occurs when the control is moved.
Which is curious since the two events are tied to each other.
This is however how one specific class handles these events. I did a bit of searching through the reference source and I couldn't find anything (inheriting from Control) which explicitly called OnMove other than the instance I've already cited. That doesn't mean they don't exist or that one couldn't invoke it separately in their own subclass of Control.
Both Move and LocationChanged events are interconnected. I believe there is no situation when one if fired and the other is not. The difference is that they belong to different categories of events.
The Move event has [SRCategoryAttribute("CatLayout")] attribute.
The LocationChanged event has [SRCategoryAttribute("CatPropertyChanged")] attribute.
Lets say I have my user-control somewhere in the visual tree. Parent and children are 3rd-party controls that I cannot modify. I want to filter keyboard events in my control so that children controls do not receive some keyboard events, but the parent controls do.
I'll try to explain what I want to achieve with some diagrams. If controls do not handle keyboard events, all events bounce through the visual tree:
But, f.e. when user presses A,
Child2.OnPreviewKeyDown() should NOT be called
but Parent2.OnTextInput should still receive an event
I can achive (1) by setting e.Handled = true in MyControl.PreviewKeyDown. The problem is that in this case TextInput event is not generated:
Is there a way to achieve behavior like on the 2nd picture?
Added:
The problem I'm trying to solve is that a 3rd-party control (Child 2) steals some input in OnPreviewKeyDown (and marks event as handled), and I'm trying to avoid that.
What you can generally do in WPF to handle a suppressed event is add a handler in code and re-raise the event. To do this, you use the UIElement.AddHandler() method, for example:
child2.AddHandler(UIElement.TextInput, new TextCompositionEventHandler(nameOfYourHandlerFunction), true);
The 'true' boolean value is what makes nameOfYourHandlerFunction fire even if the Handled flag is set. The event won't automatically re-bubble by doing that, so you need to raise the event again.
base.RaiseEvent(e);
This works for events that have a routing strategy of Bubble.
I have the following situation (simplified): In a WinForms Form, I have a GroupBox. Inside it are some UserControl_A objects, each of which contains several UserControl_B objects (both being derived from UserControl, of course).
I have a ContextMenuStrip for the GroupBox which is working fine with each right click, regardless whether on any UserControl_A/B object or the GroupBox background itself.
But I also need to process left clicks. Handling the Click and MouseClick events of the GroupBox is working, but only on the background. As soon as the pointer is inside a UserControl_A or UserControl_B object, nothing happens. I have tried to handle the Click and MouseClick events of both UserControl classes, but the handlers are never called at all.
Any hints what is going wrong here? Or how one can debug such an event handling problem?
Thank you very much in advance,
Stefan
Addition: I have now made a completely new, stripped-down project to explore the situation, and everything works fine. So how can I detect what's going wrong with my real project?
You have to add your UserControls as child control to GroupBox:
groupBox.Controls.Add(yourUserControl); // correct way
in this type, right click works for all child of your parent control (GroupBox).
form.Controls.Add(groupBox);
form.Controls.Add(yourUserControl); // wrong way
you put Mouse event in UserControl_A (i think it's class inherits Control)
the event will trigger when Mouse (over, down, etc.......) on that(UserControl)
when you on (your GroupBox ) the events blong to the groupBox
I'm trying to clear some variable on a panel, for example, if I had a bool which lets me know when I'm click dragging on a panel's surface I set this to false when a MouseUp event occurs (this may or may not be correct way to do this but serves as an example).
If while click dragging I then alt-tab to another application the panel itself doesn't appear to get any notifications, like focus->leave / mouseup for example, is there something I'm missing, an event I've overlooked?
There seems to be a way of doing this by using the forms Deactivate event, which I suppose I could just call a suspend type method on my panel if I create a new Panel class, but I was wondering if something already existed that would propagate all children on a form with some notification that our form is no longer the main focus.
Reliably getting these kind of notifications requires that you use the Capture property. Set it to true on the MouseDown event. It ensures that all mouse messages are directed to your panel, even if the mouse is no longer hovering the panel. That however still doesn't cover rude focus changes, like Alt+Tab or Alt+Esc. You also need to implement the MouseCaptureChanged event to know when the operating system stepped in.
In general, if you are trying to implement Drag + Drop then you ought to use DoDragDrop(). When it returns you can always be sure that the drag operation is completed, for whatever reason. The return value of the method tells you what happened. Note that this also supports switching to another window, albeit that it is not very discoverable, you drag to the task bar button to force a switch.
I'm creating a base class for a button that inherits from Control as opposed to ButtonBase.
I'm using reflector to look at ButtonBase to make sure I don't overlook anything important and I'm puzzled with the contents of the WndProc method.
There's checks in there for things like button up, click and capture changed, which as far as I can tell are all handled within the relevent 'On' methods of the class.
Does anyone know why they are in there?
It is a wrapper for the native Windows button control as well. In a nutshell:
0x00f5 = BM_CLICK: run OnClick()
0x2111 = BN_CLICKED notification : run OnClick()
a bunch of workarounds to deal with OwnerDraw.
You don't have to worry about any of this since you don't wrap a native button and don't need owner draw. Do make sure you implement IButtonControl so your button behaves properly when Enter and Escape is pressed and it is selected as the form's Accept/CancelButton. Not strictly necessary, but it is automatic when you inherit from ButtonBase instead of Control.