Link to home
Start Free TrialLog in
Avatar of stuengelman
stuengelmanFlag for United States of America

asked on

Need Help Disposing A Button in a .NET Dialog Popup

Hello,

I am building a VB.NET PC application using VS 2008 and .NET Framework 3.5.

I am trying to dispose a control (a button) created programmatically in a dialog popup (by executing code connected to another button).

My code is: Me.Controls("btnMXReset").Dispose(), where btnMXReset is the name of the button.

My code compiles without error, but crashes on execution.  The error message tells me nothing of value ("Object reference not set to an instance of an object").

Oddly, I'm using exactly the same syntax to dispose of User Controls on the popup, and that code works.

Can anyone supply me with the correct syntax?

Thank you, Stu Engelman
Avatar of rgn2121
rgn2121
Flag of United States of America image

Although I haven't used this to dispose, the syntax is probably correct otherwise you would have gotten the error long before you tried to debug.  I am more interested in the code before this, more specifically dealing with "...executing code connected to another button"

It seems at some point your btnMXReset becomes Nothing...
The syntax is OK, it is probably the timing and the logic in the application that causes the error.

Is the error happening on the call to Dispose or after that? What is the line that causes the error?

If you call Dispose and the Control is no longer part of the Form (might have been removed by Me.Controls.Remove, set to Nothing or Dispose is in the FormClosed event), Dispose will crash.

If there is code later that tries to use the Control, that code will crash because the Control will have been disposed?
Avatar of stuengelman

ASKER

Hi Rgn2121,

After the dialog pops, the Load handler for the dialog populates the form with a variable number of User Controls (depending on how many records are on a DB table), and then renders two control buttons on the bottom.  What's strange is that I'm having no problem using the Dispose method to eliminate the User Controls, but the code crashes when I try to Dispose either of the buttons.

The basic idea is that when the "dispose button" is pressed, it's supposed to perform all the Dispose operations (including disposing the "dispose button" itself).

The code for creating one of the buttons is shown in the snippet (this code is in the Load handler for the dialog popup).

It seems strange that the buttons could become Nothing, as the Close button operates (its handler btnMXClose_Clicked closes the popup).  Also, the "dispose button" operates too, in the sense that it properly disposes the User Controls, before crashing on trying to Dispose either of the buttons.  Thus, it seems unlikely that the buttons could have a value of Nothing after being rendered.  I think it's more likely a syntax issue of some type.

Stu
Dim btnMXClose As New Button
Me.Controls.Add(btnMXClose)
btnMXClose.Text = "Close"
btnMXClose.BackColor = Color.White
btnMXClose.Left = (Me.Width / 2) - (btnMXClose.Width / 2)
btnMXClose.Top = resettop
AddHandler btnMXClose.Click, AddressOf btnMXClose_Clicked

Open in new window

Hi James,

Please see my 09/13/11 09:09 AM reply to Rgn2121.

The crash occurs on the invocation of the Dispose method (just for the native button objects; the Dispose method is working perfectly for the User Controls).

An example of a line that crashes is: Me.Controls("btnMXClose").Dispose()

There are no issues with Remove or setting to Nothing (I'm not doing either of these).

Do you think syntax like Me.Controls("controlname").Remove might work better?  If this works, will the control be wiped out of RAM (either immediately, or later by the Garbage Collector)?

Thanks, Stu
If I understand well, you call Dispose on the button inside of the Click event of the button?

This is not permitted.

If this is the case, set and start a Timer at the end of the event button. This will enable the event to terminate, and you will be able to dispose of the button when the event fires.

You do not absolutely have to Dispose of the Button however. It is a good practice, but Dispose simply releases a bit of memory (not much for a button) faster than it would be otherwise. And since the Button has been included in the form's Controls collection, it will be Disposed by the form when you close it. If you are curious, give a look at the Dispose method of the form in the usually hidden .Designer.vb file for the form. This method is called when you close the form, and it calls Dispose on all the controls on the form.
Hi James,

I follow what you're saying, but I tried commenting out the Dispose call on the "dispose button" itself.  The same crash occured when I tried to Dispose the other button.  I still think I have some type of a syntax issue.

Stu
How many buttons do you have to dispose of. As I told you, Dispose is a good practice, but is usually not necessary. Most people do not dispose of their controls otherwise.

Here is another one to try if you absolutely want to dispose of your controls.

You are disposing of the buttons while the form still has a reference to them through its Controls collection. Since we do not know what the form does under the hood, it is possible that the message comes indirectly from there.

Keep the reference to as a class member so that you still have it when disposing of your controls, and remove the control from the Controls collection before disposing of it:

Me.Controls.Remove(btnName)
btnName.Dispose()
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
James - good ideas, will try them out.

Idle - I bet that's a BIG part of it (the User Controls ARE "named", which is why I can reference them). HAHAHAHA  - Thank you, will incorporate what you said.

To both - have been working about 10 hours, and I may not be able to reply again until late PM or early AM US EST (due to need to get some rest).  Sorry for the short delay, and thanks to both of you so much for your help.

Best regards, Stu
You do not have to name controls. If you keep a reference (variable) active, the 3 following lines do the same thing.
Me.Controls("btnMXClose").Dispose()
Me.Controls(btnMXClose).Dispose()
btnMXClose.Dispose()

Open in new window

If you use the second one, then you do not need to give a name to the control, and save the memory for 1 String.

Since a reference is only a pointer (32 bits), but a String is a reference PLUS 16 bits for every character, the reference takes less space in memory. Direct access to the button through its reference (btnMXClose.Visible as an example) is also faster than going through the collection (Me.Controls("btnMXClose").Visible)
Hi James,

Thanks, will take that into account.

Stu
While true James, the amount of memory being used and speed difference in the grand scheme of things is negligible.

The searching method can be quite useful for situations where you end up having to deal with many dynamic controls as you wouldn't need to keep a hard coded reference for each one (especially if you don't know how many might be created!).
Hi Idle,

Will keep your point in mid.

Thank you, Stu
*mid" s/b "mind"
Any approach can be good at that level. And I concur with you Idle, the amount of memory and speed difference would be negligible.

But in my way of thinking, negligible is not nothing. Performance issues are often an accumulation of those little negligible things. If you develop coding reflexes that automatically takes care of those negligible things, they wont accumulate. Thus, performance issues arise less often, and when you get one, you spend less time spent trying to point out possible sources of a performance issue. I value my own time more than performance, but my customers do not have the same idea.

For me, if I had many dynamic controls, I would simply add them to a collection, thus only one reference to keep. Over the best way to do things, we also all have our style :-).
Hi James,

Good points, I agree.

Stu
Hi Guys,

Making good progress.  Assigning the Name properties to the buttons eliminated the crash.  Now two additional issues popped up:

(1) The form takes a long time to dispose the User Controls; you actually see it flashing as it eliminates them one-by-one.  I use the following syntax to speed up initial rendering of controls:

Me.SuspendLayout()
'create controls programmatically
Me.ResumeLayout()
Me.PerformLayout()

The above logic does not speed up Dispose operations.  Would it help if I did a Remove before doing the Dispose?

(2) After running the dispose button handler, the popup is scolled to the bottom.  Apparently, Dialog Boxes don't have a ScrollTo method.  How do I get the scroller to go to the top?

Thanks, Stu
Use SendMessage() and WM_SETREDRAW to suppress repainting while you remove the controls:
https://www.experts-exchange.com/questions/21963638/Prevent-control-flashing-on-multiple-changes.html#17368567

For scrolling, try something like:

    this.ScrollToControl(someControlReferenceHere);
Hello again,

Further to my last post:

(1) I tried using the Remove method instead of the Dispose method for the User Controls, but the clearing speed was the same.

(2) I tried Me.ScrollToControl(lblExpenseList), where lblExpenseList is a label near the top of the form.  As before, I am getting unpredictable results.  Sometimes after the "refresh" (dispose button is pressed), the form auto-scrolls to the top user control, or just remains at the bottom.  What I'm really looking for is something equivalent to the ScrollToTop Method (WPF has it, but dialog boxes don't appear to have this method).  The idea is to scroll all the way to the top of the dialog as the final step of the dispose button handler.

Stu
Hi Idle,

In regard to:

(1) Could you please provide some VB sample code (I can't read C#)?  What I'm doing now is shown in the code snippet.

(2) As mentioned, I tried the ScrollToControl method, but it didn't work.  If dialog boxes don't have a ScrollToTop method, is there any method provided so I can scroll to stated coordinates (i.e., (0,0))?

Stu
Me.SuspendLayout()
For i = 1 To ctlcounter
    Me.Controls("mxrow" & CStr(i)).Dispose()
Next
Me.Controls("btnMXReset").Dispose()
Me.Controls("btnMXClose").Dispose()
Me.ResumeLayout()
Me.PerformLayout()

Open in new window

Sorry about the C# code...I switch back and forth so often I forget where I am and it all looks the same to me!

VB Code would look something like:  
Public Class Form1

    Private Const WM_SETREDRAW As Integer = &HB

    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal handle As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        SendMessage(Me.Handle, WM_SETREDRAW, False, 0) ' Turn OFF updates

        ' ... do some stuff in here ...

        SendMessage(Me.Handle, WM_SETREDRAW, True, 0) ' Turn ON updates
        Me.Refresh()
    End Sub

End Class

Open in new window


>> "If dialog boxes don't have a ScrollToTop method"

How is your form a "dialog"?...how was it created and shown...and how did you make it scrollable?
I tried using the Remove method instead of the Dispose method for the User Controls, but the clearing speed was the same.

Let's clarify something, because I see that you do not understand what Dispose does.

I repeat, although it is a good practice, you do not have to call Dispose on controls, any controls. It will eventually be done automatically by the system.

You manipulate a variable through a variable. You do not always see it that way, but when you name a Button something like btnClose, a variable called btnClose is created. When you add the control (Me.Controls.Add(btnMXClose)) to the collection of Controls, you are simply passing this pointer to a collection that sometimes works will all controls.

The button itself is somewhere in memory. That pointer is only the location of the button. The pointer exists as long as you still have your variable OR the controls is part of the collection.

If you lose your variable (End Sub for a local variable / Setting the variable to Nothing), the pointer still exists in the Controls collection.

If you Remove the control from the collection, you simply remove that copy of the variable. You do not destroy the Object.

When you have both removed the control and lost the variable, the pointer is lost. This is not much, this is only the address in memory (4 bytes on 32-bits computers). You do not see the control anymore, but the Object itself, all the properties of the Control are still in memory. They are managed by something called the Garbage Collector. It's role is to optimize the use of memory.

So, losing a variable and calling Remove does not do much, it clears up 4 bytes.

Dispose does something completely different. But it still does not clear up the memory used by the control. It calls the object and tells it to clean up the ressources it could be using. If the object opened a file, started a timer in Windows, those will be closed. But the object will still be in memory. .NET used Managed code. The Garbage Collector is the manager. The memory used by the object will be cleared only when the GC decides so. You have no control over that (yes you have, but you should not do it, otherwise you obliterate the usefullness of a Garbage Collector, so I won't tell you how). If you did not call Dispose while you had the pointer to the object, the Garbage Collector will do it when it decides to reclaim the memory used up by the object.

What happens when you call Dispose on a Control then? Almost Nothing for most controls.

What happens if you do not call Dispose? If you look at the code hidden in the .designer.vb file that is hidden under your Form, you will see that it contains a Dispose method. This method is automatically called when you close the form. This Dispose method loops through all the controls on your form and call their Dispose method.

You do not need to implicitely call Dispose, it is done for you by the code generator by the Form Designer.

What happens if the application crashes? The Gargage Collector knows it because he (or she?) is aware of everything that happens in the framework memory space. It does nothing though.

When it will need some memory, it will look through the useless objects lying there, select one, call its Dispose and then clean up that space in memory.

When you shutdown Windows, the Garbage Collector calls Dispose on all the objects still lying in its memory space, to make sure that all files, network connections, database connections, you name it, has been take care of.

So all that discussion about having problem disposing of a simple Button is over the head. Dispose will be called when needed.

I repeat. This is a good practice, but not necessary. There are a few cases where it can help, but its hard to tell because we do not know the inner workings of all those 9000+ classes in the framework. When it can help, usually the documentation suggests calling Dispose before losing the reference (the pointer / the variable). It could help if you had 2000 buttons to dispose of, because it would give the GC a better choice when come time to reclaim memory with as little impact as possible on the performance of running applications.

But all this discussion because you are not able to dispose of one Button is greatly overrated. Don't call Dispose on those Buttons. The system will do it for you when you close the form, when the garbage collector decides that it's time to do so, or when you shutdown windows.
In this case, calling Dispose() also removes the control from the form:  
Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Button2.Dispose()
    End Sub

End Class

Open in new window

Idle-Mind-500007.flv
Idle,

Your code for suppressing the very lengthy "flickering" as my logic disposes the User Controls does not help.  I get exaclty the same long flickering as when I do no layout suspension, or I suspend layout using:

Me.SuspendLayout()
'Dispose of multiple User Controls via FOR Loop
Me.ResumeLayout()
Me.PerformLayout()

I tried my logic shown just above using the Remove method, but it makes no difference.  In both cases I get the long flickering.

I also tried your suppression code using Remove instead of Dispose, but the result is the same.

I feel really stumped here.  My logic shown just above does a beautiful job speeding up the rendering of my User Controls when I instantiate them programmatically.  The issue is that I need the ability to refresh the entire dialog (purge and reinstantiate all the User Controls) via a "Refresh" button.  I simply can't get the "purge" portion of the logic to operate quickly.

Stu
Can you show your attempt with WM_SETREDRAW?....
Hi James,

Thanks so much for the lesson on Dispose and Remove.  Please tell me if this understanding is correct:

(1) Remove eliminates an object from a collection, but in some cases it may still be accessible directly or as part of a different collection.
(2) Dispose completely eliminates all access to an object.
(3) In both cases above, RAM used by the object is not cleared until the GC does this for you.

Stu
Hi Idle,

Please see code snippet.

Thanks, Stu
At Popup's Class Level:
-----------------------
Private Const WM_SETREDRAW As Integer = &HB
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal handle As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

Within Handler For Reset Button
-------------------------------
SendMessage(Me.Handle, WM_SETREDRAW, False, 0)
For i = 1 To ctlcounter
    Me.Controls("mxrow" & CStr(i)).Dispose()
Next
Me.Controls("btnMXReset").Dispose()
Me.Controls("btnMXClose").Dispose()
SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
Me.Refresh()
mdgExpListMgr_Load(Nothing, Nothing)

Note: the final line above calls the dialog's Load handler to recreate the controls on the form.

Open in new window

What else is being added to the form?...what other code is there?...what is in the UserControls?

How many controls are we talking about?

I'm not getting any flickering at all...
Almost right for (1), confused for (2), right on the spot for (3). These concepts can be hard to grasp because they involve things that happens under the hood, things that we do not see unless we have special tools to examine the computer memory while the application is running. So let's clarify, you were almost there.

 -----

(1) Almost, because it can be something else than a Collection, but for the purpose of your previous discussion, lets stick to the collection.

You lose the access when you lose all reference. In you original code, you had two references. The variable btnMXClose, as well as the Controls collection once the button was added to the collection.

Assuming that it is a local variable, when you get out of your procedure, you lose btnMXClose. But since the Controls collection is managed by the Form and you still have access to it, there is one reference left. When you Remove the button from the collection, you cannot access it throuh Controls. You have lost your second reference, none is left. The control is not accessible, but still in memory.

Probably no big deal for a Button.

But if you had used an object that "works" with the system, let's say a FileStream object that opens a file in Windows. And let's say that you forgot to Close the file before losing all your references. Who will close the file?

This one is easy to grasp, a file left opened and probably locked. But since we do not know the inner working of classes, it is not always evident that you might need to force some kind of cleanup before losing a reference to an object. And even when you know you should do it, you could forget (not closing the file).

That's were Dispose comes into play.

-----
(2) Dispose does not eliminates the access to an object, this is done when you lose all references (variables and properties) that would enable you to manipulate the object.

Dispose is a safeguard. A standard method that Microsoft has designed so that it will be called at some point no matter what, as explained in my previous post.

Not all classes need a Dispose, and most do not have one.

But a programmer who knows how to program a class in .NET and who knows that his class uses external resources outside of .NET will provide a Dispose method. This method will clean up everything that was started by the class and could cause problems if it is not stopped (opened files and database connections, timers, and a few others).

That is the sole role of Dispose.

It can be called by you, by the garbage collector when it needs to free memory, by Windows that is closing. A form automatically calls Dispose on it's controls when you close it. The thing is that except for a power failure on the computer, Dispose will be called at some point in time to make sure that everything is terminated properly.

-----

(3) You got just right.
Hi James,

Thanks, I follow now.

Stu
Hi Idle,

There are about 200 User Controls.  Each User Control contains a combobox, two textboxes, a checkbox, and three buttons.

The only other things on the form are the background, a label, and two buttons.

The Reset button's handler disposes the 200 User Controls and the two buttons, and then calls the dialog's Load handler to recreate them.  The recreate process is just as fast as the original load.  The problem is the time required to dispose the 200 User Controls.  The system flickers as it disposes each User Control, and the process takes about 30-60 seconds.

Stu
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
This combo seemed to work for me...
Public Class Form1

    Private Const WM_SETREDRAW As Integer = &HB
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal handle As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        SendMessage(Me.Handle, WM_SETREDRAW, False, 0)
        Me.SuspendLayout()

        For i As Integer = 1 To 200
            Dim uc1 As New UserControl1
            uc1.Name = "UserControl1" & i
            uc1.TextBox1.Text = "Instance #" & i
            uc1.Location = New Point(25, 25 + (uc1.Height + 7) * i)
            Me.Controls.Add(uc1)
        Next

        Me.ResumeLayout()
        SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        SendMessage(Me.Handle, WM_SETREDRAW, False, 0)

        For i As Integer = 1 To 200
            Me.Controls("UserControl1" & i).Dispose()
        Next

        SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
        Form1_Load(Nothing, Nothing)
    End Sub
End Class

Open in new window

Hi Idle,

Have already tried the method in your last post.

Will try the idea James put forward (have a short background at the top, followed by a solid background color).

Stu
Idle and James,

I tried two additional tests, each time invoking the line Me.BackgroundImage = Nothing before doing anything else in the Reset button handler.  The first test used the SuspendLayout strategy, and the second used Idle's SendMessage strategy.  In both cases I got the long flickering.

Stu
In my last submission, I'm using both SuspendLayout and SendMessage...lines #7 and #8.
Idle,

I'm having no speed issue on User Control instatntiation and rendering; the sole issue is the disposal speed.  Should I try the layered SuspendLayout and SendMessage within the Reset handler logic that performs the disposal?

Thanks, Stu
Ideas are running out.

If you try to add the controls to a brand new form, with nothing else in it, do you still get the problem? If so the problem could be in the UserControl itself.
I come back to it.

If the problem is with disposal, and knowing that disposal is not required, why don't you just skip the dispose. It will be done eventually by the Garbage Collector when it will need resources.
Hi James,

The disposal is required.  The point of the Reset button is to eliminate all the User Controls and refresh them from the database.  The popup itself is not closed on this operation, so the GC does not enter into the issue.

Stu
Hello guys,

See the code in the code snippet (the handler for the Reset button).  You'll see I turned the background image off.  I tried it as shown, and also with the order of the SendMessage and SuspendLayout strategies nested in reverse.  In both cases, the long flickering occured.

Stu
Me.BackgroundImage = Nothing
SendMessage(Me.Handle, WM_SETREDRAW, False, 0)
Me.SuspendLayout()
For i = 1 To ctlcounter
    Me.Controls("mxrow" & CStr(i)).Dispose()
Next
Me.Controls("btnMXReset").Dispose()
Me.Controls("btnMXClose").Dispose()
SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
Me.ResumeLayout()
Me.PerformLayout()
mdgExpListMgr_Load(Nothing, Nothing)

Open in new window

James,

Some good news.  Your solution on the other ticket regarding the background image leaving traces of itself as I scroll provided a solution to the "scroll to top" issue in this ticket.  I simply added the line "Me.ScrollControlIntoView(pbExpListBackground)" at the bottom of the Reset button handler, where pbExpListBackground is the PictureBox containing the upper background image.

So all we have left is the flickering issue.

Stu
Hi James and Idle.  Closing this ticket out and awarding points to both of you.  Idle resolved the dispose issue, and James pointed me to the solution of the "scroll to top" issue.  Will set up a separate ticket for the flickering problem that still remains.  Thanks so much to both of you for your help.  Stu