[Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1157
  • Last Modified:

Massive Memory Leak When Using Custom Mouse Cursor (C#)

An expert had previously helped me before on this site create a custom mouse icon when I am dragging an item from a textbox onto a panel. This was done using the control.givefeedback method like so:

private void listBox1_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
            e.UseDefaultCursors = false;
            Bitmap bmp = new Bitmap(252, 192);
            Graphics g = Graphics.FromImage(bmp);
            g.Clear(Color.Black);
            g.Dispose();
            panel1.Cursor = new Cursor(bmp.GetHicon());
            bmp.Dispose();
        }

This essentially creates a black box that you can move around the panel until you let go of the mousebutton, where it then sticks in place. This was all working fine until I had reports of crashing. I did some memory tests and it appears the more I drag around the mouse when the custom cursor is created the more memory it consumes, until the app eventually runs out of memory. If the cursor has been created, but I do not move the mouse the memory still increases but not as fast as if I move it all over the screen. When I let go of the mouse button the memory stops increasing, but it never decreases. This leads me to believe I am creating an object somewhere repeatedly that is not being released, but for the life of me I cannot figure out where. Any ideas?
0
Pretzel_Jesus
Asked:
Pretzel_Jesus
  • 5
  • 4
  • 3
  • +2
1 Solution
 
alex_pavenCommented:
I would suggest only creating the Cursor object once and re-using it. Something like, if (myCursor == null) myCursor = new Cursor(..)
then just keep using myCursor.
0
 
Pretzel_JesusAuthor Commented:
I was not really familiar with the givefeedback event prior to using it in this example, so you are saying this event is repeatedly firing causing the curosor objects to be perpetually created over and over causing the problem?
0
 
oobaylyCommented:
You're never disposing the cursor you've set. It would be far more efficient to set the cursor only once, (ie. in the DragEnter event). Then dispose the custom cursor, and reset it to default (or whatever you want) once you're done, (ie. in the DragDrop event).
0
Get quick recovery of individual SharePoint items

Free tool – Veeam Explorer for Microsoft SharePoint, enables fast, easy restores of SharePoint sites, documents, libraries and lists — all with no agents to manage and no additional licenses to buy.

 
alex_pavenCommented:
Basically yes, the GiveFeedback event is fired every time there's a state change for the dragged item, as in dragging over a different control that could act as a drop target etc., which means it can be fired quite a few times during a drag operation. oobayly summarized it quite nicely. Also, more details here.
0
 
anarki_jimbelCommented:
I believe it's here:

panel1.Cursor = new Cursor(bmp.GetHicon());

With every event handler invocation you create a new cursor instance. It's not obvious if these instances are distroyed, and why not. Really, memory leaks are not so trivial to cure.

Try to create one instance of the cursor somewhere so you can make a reference to it from your handler:

panel1.Cursor = alreadyExistingCursor;
0
 
Pretzel_JesusAuthor Commented:
When I use the same code in the dragenter event, and remove it from the givefeedback event, the black box never appears...
0
 
oobaylyCommented:
GiveFeedback is paired with MouseMove, so removing the cursor in that event won't work as it will be removed immediately. As I suggested, use the DragDrop event.
0
 
Pretzel_JesusAuthor Commented:
I cant create the cursor in the dragdrop event... since that occurs AFTER I want the custom mouse icon created. It needs to begin as soon as the mouse enters the panel... which I assume is the drahEnter event. I also tried to dragOver event for laughs, but neither created the custom mouse icon.
0
 
alex_pavenCommented:
Alright, let's break it down: place the code that creates the cursor somewhere, it doesn't even need to be in the DragEnter event, more likely in the form load or something similar, e.g.:
Bitmap bmp = new Bitmap(252, 192);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.Black);
g.Dispose();
myCursor = new Cursor(bmp.GetHicon());

Note that you need myCursor in a broader scope, so declare it outside any methods, in the parent class:
Cursor myCursor;

Then in the GiveFeedback, simply:
e.UseDefaultCursors = false;
panel1.Cursor = myCursor;

0
 
Pretzel_JesusAuthor Commented:
Sorry guys, I am still a bit new at this. I tried this in the main class:

private Cursor cursor()
        {
            Bitmap bmp = new Bitmap(CursorWidth, CursorHeight);
            Graphics g = Graphics.FromImage(bmp);
            g.Clear(Color.Black);
            g.Dispose();
            Cursor cursor = new Cursor(bmp.GetHicon());
            return cursor;
        }

private void listBox1_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
            e.UseDefaultCursors = false;
            panel1.Cursor = cursor;
        }

And Im getting the error:

Cannot convert method group 'cursor' to non-delegate type 'System.Windows.Forms.Cursor'. Did you intend to invoke the method?

What did I do wrong?
0
 
oobaylyCommented:
DragDrop is the place to remove the cursur, not create it. Have you set the AllowDrop property, as this needs to be enabled for the DragEnter event to be raised.
0
 
Pretzel_JesusAuthor Commented:
Thank you I got it now with your code. I was using it incorrectly at first. This fixed my problem!
0
 
alex_pavenCommented:
Oh alright, sorry, here's what I meant:


private Cursor cursor;
 
//place this in Form_Load or Main method
Bitmap bmp = new Bitmap(CursorWidth, CursorHeight);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.Black);
g.Dispose();
cursor = new Cursor(bmp.GetHicon());
 
private void listBox1_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
   e.UseDefaultCursors = false;
   panel1.Cursor = cursor;
}

Open in new window

0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Sorry about the leak...further research shows that this is one of the few cases in .Net where a managed call returns a pointer to an un-managed resource!  We actually have to release the icon with a call to the DestroyIcon() API.

Here is an example in VB.Net:
Imports System.Runtime.InteropServices
Public Class Form1
 
    <DllImport("user32", CallingConvention:=CallingConvention.Cdecl)> _
    Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
    End Function
 
    Private iconPtr As IntPtr = IntPtr.Zero
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If Not iconPtr.Equals(IntPtr.Zero) Then
            DestroyIcon(iconPtr)
        End If
        Dim bmp As New Bitmap(ilMain.Images(1))
        iconPtr = bmp.GetHicon()
        NotifyIcon1.Icon = Icon.FromHandle(iconPtr)
    End Sub
 
    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        NotifyIcon1.Icon = Nothing
        If Not iconPtr.Equals(IntPtr.Zero) Then
            DestroyIcon(iconPtr)
        End If
    End Sub
 
End Class

Open in new window

0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 5
  • 4
  • 3
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now