How to determine whether a C# control is being unloaded? - c#

In my C# Windows Forms application, I have a user control that contains other controls and does its own logic. One of them is a delayed call (Timer-invoked) that does certain things after the user has finished a keyboard input (live filter text). It accesses the other controls for this, one of them is that text input control. This method is invoked 500 ms after the last input event.
Now I have a problem when the delayed call is running while the application is terminating. When I enter some text, then wait about 500 ms (it seems to work every time) and then press Alt+F4 to close the window, the application throws a NullReferenceException while trying to access the text input control. This doesn't happen when I close the window immediately after the last input or a second or more after.
It seems that the control is being disposed or something and its methods cannot access the child controls anymore. So, when the control is being put in that state (by whomever and whatever that state exaclty is), those timer need to be stopped first so that the controls can be safely disposed.
I have already tried to stop the timer in the OnHandleDestroyed method (overridden) and at the beginning of the Designer-generated Dispose method. Nothing helped.
This procedure works fine in regular Forms when stopping the timers in the overridden OnFormClosed method, before calling base.OnFormClosed(). I just cannot find a suitable event in a user control.

Try this in your UserControl:
bool isDisposed;
protected override void Dispose(bool disposeManaged)
{
if(!isDisposed)
{
if(disposeManaged)
{
//Dispose your timer here
}
isDisposed = true;
}
}
Another possibility is that one of your UI classes doesn't do its cleanup. Eg. it registers itself for an event but doesn't deregister when it's manually disposed. It is never collected by the GC and when the event is fired for the next time, it tries to access some members that were set to null during the Dispose(...) call before.
Another possibility is that you have a more complex race condition within your code but it's hard to say from here.

Related

Undead form is still generating events

I have a form that the user can open from another form:
private void btnEditTemplate_Click(object sender, EventArgs e)
{
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
EditReport.Close();
EditReport.Dispose();
}
}
As you can see, I've called close, dispose, and even placed the form in a using block. The form has some controls bound to static BindingLists that are maintained outside the form, and changing those lists triggers events.
Despite everything, if the user later opens another copy of this form, I am finding that events for the original form are still being run, even though it is closed and disposed.
What to do I need to do to KILL this thing? Stakes through the heart?
Update: as was indicated in the comments, the problem was the static BindingList objects were maintaining databindings to the old form. For me the solution was to get rid of the BindingList objects as I wasn't making any (intentional) use of their databinding properties anyway.
Ignoring the event handlers and data bindings at the moment, your code is little bit overkill at the moment. You could get away just this:
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
}
As you are showing it as a dialog by the time it got to your .Close() call the form was closed anyway. And the using statement calls .Dispose() for you.
Now, when you dispose a form you are basically removing the form from its parent, if you have one, disposing of all child controls, and freeing up the Windows handle.
However, it does not know how to detach your event handlers and it does not know how to remove data bindings. Since these things are references into your form the garbage collector will count the form as a live object and won't try to collect it.
So if you have any handlers within your form listening to external events and they fire then your form will try to handle them.
You need to make sure that when your form closes that you detach all handlers and unbind all data bindings.
Opening the form a second time has no direct relationship to the first, but it is likely that it caused your data source to start raising events or updating values and that's what tripped your original form.
In general, I find it good practice to write event subscription code to be tolerant of being called after you unsubscribe from the event. This can happen in a variety of ways.
I don't know what experimentation you did, but for example, if you wrote this:
private void btnEditTemplate_Click(object sender, EventArgs e)
{
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
EditReport.Close();
EditReport.Dispose();
}
using (frmReport EditReport2 = new frmReport())
{
EditReport2.ShowDialog();
EditReport2.Close();
EditReport2.Dispose();
}
}
then there may still be events in the message queue for the first dialog that will not get processed until you enter the modal message loop in the ShowDialog() call on the second instance.
Even with the Dispose() you have, the Garbage Collector will not reclaim the first object, because of the references to it in these subscriptions in the message queue. If you write your event subscriptions to do nothing if the object they are delivered to is already disposed, then there will be no harm done. what to check varies depending on what base class you are using. Worst case, you implement your own IsDisposed or IsClosed instance variable.

InvalidOperationException when using Invoke before the form was opened

The exception is this:
System.InvalidOperationException: Invoke or BeginInvoke cannot be
called on a control until the window handle has been created.
First I'll explain the relations in my app.
There's a form named MainForm and another form named AssetsForm. The MainForm is creating an instance of the AssetsForm in the MainForm's constrcutor but doesn't AssetsForm.Show() it yet.
There is class named AssetsSource which implements the IObservable and sends data for display to the AssetsForm which implements the IObserver. When AssetsForm receives data to display, it creates a BackgroundWorker which handles the data and update a TreeView.
I've implemented the following wrong code to handle the UI updates from the BackgroundWorker:
private void Invoke(Control control, Action action)
{
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
control.Invoke(action);
}
}
It's wrong because instead of Invoke(action) I should have written action(); But I will refer to this later. Anyway, an InvalidOperationException was thrown from the Invoke(action) line of code. I can infer that the InvokeRequired evaluated to FALSE, although I update the TreeView from a BackgroundWorker !!
In MSDN it is written about Control.Invoke:
The Invoke method searches up the control's parent chain until it
finds a control or form that has a window handle if the current
control's underlying window handle does not exist yet. If no
appropriate handle can be found, the Invoke method will throw an
exception.
What is the parent chain and what is the window handle ? When the window handle is created ? I guess all this had to do with the fact that the AssetsForm is closed.
When I removed that line and uses only action(); as it should be, the program doesn't crash.
When the AssetsForm is opened before the AssetsSource sends updates to AssetsForm, by debugging I can see that InvokeRequired is evaluated to TRUE and BeginInvoke of the TreeView updates itself.
To sum everything up, I don't understand why when the AssetsForm is closed, then the InvokeRequired is false and the UI update (TreeView) is allowed to be from the thread that didn't create the TreeView.
As long as the window is not shown, Winforms doesn't need to stick on the UI-thread mechanismn. Therefore InvokeRequired returns false.
If you call Show() the window is opened and all UI activities need to be run through the event loop and therefore through the UI-thread.
Background: The restriction to handle UI activities only through the main thread is due to the fact that only one (windows) event loop is handling all UI related activities. To ensure that all activities are running in the correct order, all actions need to be run through one thread (at least in winforms). As long as the form is not shown, no events are triggered and therefore there's no need to enforce that all actions are run through the main thread.
EDIT: Add some background description.

Which event is launched right after Control is fully loaded?

I've got user control that overrides WebBrowser control.
There's a method which takes an area of a BackgroundImage from its Parent Form and makes it its background in html code. It works like that:
Makes itself invisible.
Captures Parent form.
Makes itself visible.
Takes the specific area of captured image and sets it as its background.
Refreshes itself.
I want that method to be fired right after the control is fully loaded. I can't do it in constructor, because some important objects and parent's properties which I need to use are still null.
I put it in OnVisibleChanged event and set bool variable to false, so it runs only once, but it fires every time I rebuild my project (even without running - it creates bitmap that I use as background in html code, but different directory because I use Directory.CurrentDirectory() method). Tried also fire it with OnLoad event, but it makes the same problem as OnVisibleChanged.
Is there more appropriate event for that than OnVisibleChanged?
PS. I was always wondering if every event has to fire event of it's base class. Is it necessary to do it? I don't see any diffrence at all, without it everything works great. If it's better to leave it there, should it be on the beginning or on the end of the event method?
There's no concept of 'fully loaded', creating a control is an atomic operation in Winforms and signaled by OnHandleCreated(). What you are looking for here is having the control fully painted. Painting is a low priority task in Windows, performed only when nothing else needs to be done.
Get that notification by overriding WndProc() and catching WM_PAINT:
bool fullyPainted = false;
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == 15 && !fullyPainted) {
fullyPainted = true;
// etc...
}
}

Form.Load event triggered every time

I was wondering if I am doing correctly.
I instantiate a Form (let's call this Form_B) within my class (also a form) and handle Form_B's Load event. Within this event I do some initialization.
Form_B can be displayed by the user multiple times, and I call ShowDialog on my instance variable.
The problem is that the Load is called each time I show the form. I tried debugging and also tried with Show() instead of ShowDialog(). Show() fails as I closed the window but ShowDialog() does not fail, but calls Load every time it is displayed.
Is it incorrect to continue using the instance once the form is closed?
Thanks,
Stefan
Using the Load event to initialize a form is an anachronism from the VB6 days. It was really important back then, that unfortunately carried over in the design of the Winforms designer. It made Load the default event for a form.
That is however not the .NET way, you initialize a class object with the constructor. The only time you need to override OnLoad() (another .NET way, events are for code in other classes) is when you care about the size and position of the form. It won't be the design Size and Location when the user changed the Windows theme or runs the video adapter at a higher DPI setting. So you might want to use OnLoad to move the window or rearrange the controls. Not actually a very common thing to do.
So, fix your problem first by using the constructor instead. If you still need OnLoad then just use a bool flag that keeps track of whether or not it already ran.
private bool initialized = false;
protected override void OnLoad(EventArgs e) {
if (!initialized) {
initialized = true;
// etc...
}
base.OnLoad(e);
}
And yes, this only works if you use ShowDialog(). A form that's displayed with Show() automatically disposes itself when it is closed. That doesn't happen with ShowDialog() to avoid problems retrieving the dialog results. Re-creating the dialog instance is the better way, unless you really care about keeping the last entered values. That's however a really expensive way to do so, form objects take a lot of .NET and Windows resources.
That is the correct behaviour of the Load event, each time it is loaded it is called. If you want to reuse the form and avoid the the Load event, rather than close the form you should hide it and use the show method to bring it out when needed.
The load event is called once all the components of the form are loaded. If you redisplay the form, its components load again and therefore the Load event is triggered once more.
You could trigger a custom event that would only be triggered in your form's constructor if that's what you're looking for but I think it's bad practice to use a form after it's been closed.
I'm having the same problem. After searching awhile, I think the "ShowDialog" is an exception.
Since it's 2018 right now, MS has opened .Net. I've checked the source and found this.
this.CalledOnLoad = false;
this.CalledMakeVisible = false;
in the ShowDialog() function.
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Form.cs,ab288b84e00f8282

C# How To: Trying to call button twice in same class?

Did some searches here & on the 'net and haven't found a good answer yet. What I'm trying to do is call a button twice within the same class in C#.
Here's my scenario -
I have a form with a button that says "Go". When I click it the 1st time, it runs through some 'for' loops (non-stop) to display a color range. At the same time I set the button1.Text properties to "Stop". I would like to be able to click the button a 2nd time and when that happens I would like the program to stop. Basically a stop-and-go button. I know how to do it with 2 button events, but would like to utilize 1 button.
Right now the only way to end the program is the X button on the form.
I've tried different things and haven't had much luck so far so wanted to ask the gurus here how to do it.
BTW, this is a modification of a Head First Labs C# book exercise.
Thanks!
~Allen
You would need to use Multithreading (launch the process intensive code asynchronously in a separate thread), for instance, using the BackgroundWorker object in .NET 2+. This would be necessary because your UI will not respond to the user's click until the loop running in the Start method is completed. It is quite irrelevant if you use the same button or another one to toggle the process, because the processor is busy processing the loop.
The BackgroundWorker has a property called WorkerSupportsCancellation which needs to be true in this scenario. When the user clicks Stop you would invoke the CancelAsync method of the BackgroundWorker.
See MSDN for a good example. Also DreamInCode has a good tutorial which seems quite similar to your requirement.
Why not create two buttons, hide one when the other is visible? That should be a lot of easier to handle.
Or you can add a bool field to indicate which operation branch to execute.
One simple solution would be to add a boolean member to your form that is, e.g., true when the button says "Go" and false when the button says "Stop".
Then, in your button's event handler, check that boolean value. If the value is true, then start your operation and set the value to false when you change the button's text to say "stop". Vice-versa for the other case. :)
There are other techniques that I might prefer if this were production code, perhaps including considering the design of the form more carefully, but as this is clearly a learning exercise I believe that a simple boolean flag indicating the current state of the form is just what you're looking for.
Note that I would strongly discourage you from checking the value of the button text to determine what state the object is in. Whenever possible, as a general rule of good design, you want your visual state to be "decoupled" from your underlying object's state. That is to say, your visual widgets can depend on your underlying objects, but your underlying objects should not depend on your visual widgets. If you tested the text of the button, your underlying logic would depend on your visual state and that would violate this general rule.
If your problem is related to the fact that you can't cancel the operation while it's being performed, you'll want to look into using a BackgroundWorker to perform your long-running activity.
Another option would be to check the current text on your button to determine what to do:
void btnStartStop_Click(Object sender, EventArgs e)
{
if (btnStartStop.Text == "Go")
{
btnStartStop.Text = "Stop";
// Go code here
}
else
{
btnStartStop.Text = "Go";
// Stop code here
}
}
Are you getting your second button click event? Put a breakpoint in your click handler and run your code. When you click the second time, do you ever hit your breakpoint?
If your loop is running continuously, and it is in your button click handler, then your loop is running in the UI thread. You probably don't get to "see" the second button click until after the loop is completed. In addition to the branch code that you see above, try either inserting a DoEvents in your loop processing (this is a place where your loop will temporarly give up control so that messages can be processed). Or, (better) have a look at the backgroundworker class -- do most of your processing in a different thread, so that you UI can remain responsive to button clicks.
Cerebrus is right about using the Background Worker thread. However if you are doing a WPF app then it won't be able to update the UI directly. To get around this you can call Dispatcher.BeginInvoke on the main control/window.
Given code like:
Private Delegate Sub UpdateUIDelegate(<arguments>)
Private Sub CallUpdateUI(<arguments>)
control.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Background, New UpdateUIDelegate(AddressOf UpdateUI), <arguments>)
End Sub
Private Sub UpdateUI(<arguments>)
'update the UI
End Sub
You can call CallUpdateUI from the Background Worker thread and it will get the main thread to perform UpdateUI.
You could set the Tag property on the button to a boolean indicating whether the next action should be "Stop" or "Go", and reset it each time you click the button. It's an Object property, though, so you'll have to cast it to bool when you read it.

Categories

Resources