0

I try to remove a EventHandler with .NET Reflection. When I use my own testclass (changed the argument from Control that to TestClass that), it works.

But when I want to do it with a WindowsForms Control, I can't get the public field of the event (strictly speaking I dont get any public instance field). What differs between my TestClass and a WindowsForms Control. The "MouseDown" event and the event in my TestClass are both public.

        static void  RemoveHandler( Control that )
        {
            //adding an eventhandler for testing
            that.MouseDown += (obj,arg) => { };

            string eventName = "MouseDown";
            //this works, but I need the Field, to get the Invocationlist
            var testEventInfo = that.GetType().GetEvent( eventName, BindingFlags.Public | BindingFlags.Instance );

            if ( testEventInfo != null )
            {
                //this fails, Declaring Type of my Event "MouseDown" is Control  testEventFieldInfo stays null
                var testEventFieldInfo = testEventInfo.DeclaringType.GetField( testEventInfo.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField );

                //trying to get all public events fails too, fields is always empty
                // var fields = testEventInfo.DeclaringType.GetFields( BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField );
                if ( testEventFieldInfo != null )
                {
                    var testEventFieldValue = (System.Delegate)testEventFieldInfo.GetValue( that );
                    if ( testEventFieldValue != null ) // if its null no Delegates are hooked
                    {
                        var invocationList = testEventFieldValue.GetInvocationList();
                        //var removeMethod = testEventInfo.GetRemoveMethod();

                        foreach ( var eventHandlerDelegate in invocationList )
                        {

                            testEventInfo.RemoveEventHandler( that, eventHandlerDelegate );
                        }
                    }
                }
            }
        }

What I tried is dealing with inheritance. (TestClass is a drived Class, and the base Class declares the event) Found out that .GetEvent gets also Event from all base classes, but GetField does not, so I added the access to the .DeclaringType of the Field. All this works with my TestClass, but not with a Control-derrived type (i.e. Button or ListBox)

What I found out too is, that there is a field "EventMouseDown", which is NonPublic and Static. But I cant find any Documentation on this. And the Name I hooked on is "MouseDown"

So maybe you know, where is my lack of knowledge. Thank you very much.

woelfchen42
  • 419
  • 2
  • 8
  • What is the value of `testEventInfo.Name` ? Moreover `public` field is unlikely and [in sources](https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,5029) (give it few seconds to load) it doesn't looks like there is a field. If you tell us what are you trying to achieve there could be a much easier solution. See [XY problem](https://meta.stackexchange.com/q/66377/299295). – Sinatr Jun 02 '20 at 11:33
  • The value of testEventInfo.Name is "MouseDown". I only try to find out with GetEvent if an event with that name exists. – woelfchen42 Jun 02 '20 at 12:05
  • I try to achieve to hook off all "MouseDown" Eventhandlers of all Controls in a Form (without knowing them). So maybe reflection is a (the) tool to do so. – woelfchen42 Jun 02 '20 at 12:11
  • Why do you want to hook all mouse down events? What are you trying to achieve? – Sinatr Jun 02 '20 at 12:16
  • And I need to do this from outside, I cant derrive all Controls from a special Control wich has a RemoveAllMouseDownHnadlers()-method. – woelfchen42 Jun 02 '20 at 12:17
  • Ok, from the start: I have a older WinForm-App, and now I need to implement a ContextSensitive Help Feature. I hope, that I find a way not to touch every single control (1000 maybe). All that runs on a kiosksystem (so I cant use the F1-Key). – woelfchen42 Jun 02 '20 at 12:31
  • I need something like: switch the app in Helpmode, the select the control -> show the help-html-file.I cannot use the so called HelpProvider from WinForms, because, one can use it only in modal Dialog with minimize.button switched off. In addition we have a complete different Design. – woelfchen42 Jun 02 '20 at 12:31
  • So I thought: Switch on helpmode will hook off all delegates from "MouseDown", "MouseUp", "Click" and "DoubleClick" and put them away in a dictionary (where the control is the key). Then hook on a "MouseDown"-Handler, which shows the Help-Page. If helpmode goes off, hook off alle the Help-Eventhandlers from all"MouseDown"-Events and hook on all Handlers back from the dictionary to its control. (I know, its a bit hairy, but its a try, not to touch all the controls and theyr Click- or MouseDownHandlers) – woelfchen42 Jun 02 '20 at 12:32
  • And it works, but only in my testclass with its testevent – woelfchen42 Jun 02 '20 at 12:33
  • Aside from that , on this kiosksystem I only have a touchscreen with single klick, no doubleklick, no longklick or something like this. – woelfchen42 Jun 02 '20 at 12:37
  • Consider to [edit] comments into question. Some control may add/remove events while you are in this mode. I wouldn't go the way you are trying to. I'd try to disable all mouse input (by making transparent [overlay](https://stackoverflow.com/q/4503210/1997232) on top of form? mouse hook?) and for keys there is already [Form.KeyPreview](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.keypreview). I wouldn't discard subclassing, since you anyway have to edit forms to add help id. – Sinatr Jun 02 '20 at 12:41
  • I understand your consideration, and I know its dirty. The idea is to use the Controls path through the containers and the name of the control as a key for the help-page. I have the same thing ready in a WPF-solution, where I use the logicalTree to address the right help-Page. The customer then can switch to help mode in helpmode to editHelpmode and add his own helptext. But in Wpf its very easy do to so, because I have routedEvents (Preview up the Path and normal events down the path) and I can mark MouseDown as handled, so that all other events are not fired in the control – woelfchen42 Jun 02 '20 at 12:51

1 Answers1

0

Maybe, I've just found a way, it seem to work, but I dont know why (let me know, if you know how this WindowsFormsEvent are structured) The answer is a modification of this article: GetField unable to obtain EventClick

//Important is, that the Field is NoPublic and Static and is Named "Event~"
//What does the .GetValue(null)
var field = testEventInfo.DeclaringType.GetField( "EventMouseDown", BindingFlags.NonPublic | BindingFlags.Static ).GetValue( null );
//Then you take the so called EventHandlerList which is also Static 
// and NoPublic and use your instance to get the Value of it
var eventList = (EventHandlerList)testEventInfo.DeclaringType.GetProperty( "Events", BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( that );
//the trick is now, to use the field as a key for the eventList
var mouseDownEvent = eventList[field];
if ( mouseDownEvent != null)
{
    var invList = mouseDownEvent.GetInvocationList();   
    foreach (var eventHandlerDelegate in invList )
    {
        testEventInfo.RemoveEventHandler( that, eventHandlerDelegate );
    }
}
//Now the invocation List ist empty
var modifiedEventList = (EventHandlerList)testEventInfo.DeclaringType.GetProperty( "Events", BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( that );
var modifiedMouseDownEvent = eventList[field];
//modifiedMouseDownEvent is null now, no delegate is attached anymore

But why is the event named "EventMouseDown" and it's NoPublic and Static? I dont know, it just works for me. Maybe I can save the handlers and save them, and hook them back later, as I need it.

woelfchen42
  • 419
  • 2
  • 8