0

I am experiencing a problem in my C# code.

A bit of background: I am working on an HMI where I need to detect mouse movement anywhere on the HMI screen. The screen is divided into many different controls, so I use this recursive function in order to be able to detect a mouse movement over any control:

private void SetMouseTriggers(Control c1)
{
    // For all of the child controls in this control
    for (int i = 0; i < c1.Controls.Count; i++)
    {
        // Get the individual child control
        Control c2 = c1.Controls[i];

        // Add a MouseEventHandler to this control's MouseMove event
        c2.MouseMove += new MouseEventHandler(OnMouseMove);

        // Recursively call this function
        SetMouseTriggers(c2);
    }
}

// This function is entered whenever the mouse moves anywhere on screen
private void OnMouseMove(object sender, MouseEventArgs e)
{

}

I call this function for the parent control (i.e. SetMouseTriggers(ParentControl)) to check all child controls. This part works fine.

My issue is that I encounter an error "Cannot access a disposed object" when I attempt to use a specific feature of the HMI, which is to change users.

enter image description here

The strange part is, the error only occurs when the mouse is located over certain controls of the HMI. When the mouse is located over other HMI controls, the program works with no issues.

I tried to debug the program but I don't have access to the source of where the problem is actually occurring.

enter image description here

enter image description here

If it helps, here is the disassembly:

enter image description here

My next thought was to check the isDisposed property of each control to see if I can at least determine which control is causing the problem. See below the recursive function I created to detect this. I placed the program into debug mode and set a breakpoint if the control was disposed, but I could never hit this breakpoint in various scenarios that I tested.

private void CheckAllControls(System.Windows.Forms.Control c1)
{
    // For all of the child controls in this control
    for (int i = 0; i < c1.Controls.Count; i++)
    {
        // Get the individual child control
        System.Windows.Forms.Control c2 = c1.Controls[i];

        // Check if the control is disposed
        if (c2.IsDisposed) 
        { // Breakpoint set here
        }

        // Recursively call this function
        CheckAllControls(c2);
    }
}

This tells me that the control must be disposed somewhere in the System.Windows.Forms.dll library.

My questions are:

  1. Is there a way around this even though I don't have access to the code where the problem is occurring?

  2. Even if there isn't a way around this, can someone explain what is happening and why? (i.e. Why is this problem only occurring when the mouse is located over certain controls?)

ImaginaryHuman072889
  • 4,953
  • 7
  • 19
  • 51

2 Answers2

1

try that example, I just subscribed into the disposed event, and unsubscribed from the delegate when it disposed. and added some writes that might help to debug and understand the code later.

private void CheckAllControls(System.Windows.Forms.Control c1)
        {
            // For all of the child controls in this control
            for (int i = 0; i < c1.Controls.Count; i++)
            {
                // Get the individual child control
                System.Windows.Forms.Control c2 = c1.Controls[i];

                // Check if the control is disposed
                if (c2.IsDisposed)
                { // Breakpoint set here
                }
                c2.Disposed += C2_Disposed;
                // Recursively call this function
                CheckAllControls(c2);
            }
        }

        private void C2_Disposed(object sender, EventArgs e)
        {
            try
            {
                 Debug.WriteLine(string.Format("{0} just disposed", ((Control)sender).Name));
                ((Control)sender).MouseMove -= OnMouseMove;
                ((Control)sender).Disposed -= C2_Disposed;
            }
            catch (Exception ex) {
                Debug.WriteLine(ex);
            }
        }
        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            //your logic...
        }
Or Yaacov
  • 3,597
  • 5
  • 25
  • 49
  • This is a good idea (+1) but unfortunately my problem persists. The only difference I did from your example is that I put `c2.Disposed += C2_Disposed` in my other function (`SetMouseTriggers`). It seems that remaining subscribed to the event was not causing the exception, since unsubscribing from it does not solve the issue. – ImaginaryHuman072889 Aug 08 '18 at 16:15
0

The Exception is probably occurring because some part of the user control which have fired the event is disposed afterwards it has fired the event and before the event handler has actually run.

It depends on the controls your application is using. I can see on the error message that the exception is coming from an object called "UserGrid". Grids, tables etc. are usually consist of several smaller controls. As the number of the controls can be handled efficiently is finite, most of these controls only maintain ONE subcontrol (one table line / cell, one grid element etc.) for the currently edited cell (where the mouse is), and all the others are just rendered pictures. This may cause several object creation / disposal in the background. This is just an idea, but it can cause your disposal problem.

I think this exception occurs in the event handler, so you should not put the lines

// Check if the control is disposed
if (c2.IsDisposed) 
{ // Breakpoint set here
}

into the recursive registration method, but into the event handler:

// This function is entered whenever the mouse moves anywhere on screen
private void OnMouseMove(object sender, MouseEventArgs e)
{
    // Check if the control is disposed
    if (sender.IsDisposed) 
    { // Breakpoint set here
    }
}

Regarding the recursive solution, it would be much more efficient to register a low-level event filter to capture mouse move events all over your window. You can find an example for it here: How do I capture the mouse move event

Gergely Hamos
  • 401
  • 4
  • 9
  • he must to unsubscribe from the event, if the object is disposed. and not check in the event handler. – Or Yaacov Aug 08 '18 at 16:11
  • I don't think the exception is occurring in the event handler. (It is occurring in the library.) The link you posted for how to capture a mouse move event looks helpful, but the line `InitializeComponent();` is not recognized by the compiler. Am I doing something wrong? – ImaginaryHuman072889 Aug 08 '18 at 16:43
  • Also, with the link you provided, is it possible to know the mouse coordinates? This is the important part to me. The useful thing about this recursive function is that the `MouseEventArgs` contains coordinate information. – ImaginaryHuman072889 Aug 09 '18 at 10:40
  • It is also possible that while your recursive function is still running, some component gets disposed while you are enumerating its child components. To overcome this, I still recommend to use a low level event handler. Regarding the event handler, yes, you can get the coordinates. X and Y are the low and hi 2 bytes of the "LParam" property of the corresponding message event. if (m.Msg == WM_MOUSEMOVE) { int x = m.LParam.ToInt32() & 0xFFFF; int y = (m.LParam.ToInt32()>> 16) & 0xFFFF; } – Gergely Hamos Aug 09 '18 at 21:02
  • Thanks. Do you know why `InitializeComponent()` is not recognized by the compiler? – ImaginaryHuman072889 Aug 10 '18 at 14:19