Form gets disposed on Hide() - c#

I have a C# WinForms application running on .NET Framework 4.0.
When the user is inactive for a certain period of time, I want it to hide all the displayed forms and show an icon in the notification area. When the user clicks that icon, a login form appears and if the credentials are valid, it opens the exact forms that were open before.
To do this, I store the list of open forms in a List of Form objects and hide them, like this. This method is called by a Timer:
private void LogOut()
{
foreach (Form form in Application.OpenForms)
if (form.Visible)
{
GlobalVariables.formList.Add(form);
form.Hide();
}
}
When the credentials are validated, I try to make the forms visible again, like this:
//Show the previous forms.
foreach (Form form in GlobalVariables.formList)
form.Visible = true;
//Clear the forms list.
GlobalVariables.formList.Clear();
If I only have the MainForm open when I hide the forms, it shows it back fine when logging back in. If I have any other forms open (which are opened using ShowDialog() from the MainForm), the program will crash on form.Visible = true; and give me the following error message:
ObjectDisposedException was unhandled
Cannot access a disposed object
How can I fix this problem? An alternative way of doing what I'm trying to achieve would also be great.
Please note that using a try - catch block to determine if the form has been disposed and just relaunch the form is not an option as the user may have unsaved input in the hidden forms.
I couldn't manage to find anything related online in over 3 hours of search so any help would be much appreciated!
EDIT: After trying various things, I have noted that the problem only occurs on forms I have opened forms using ShowDialog(). If I only have forms opened using Show(), everything works fine.
However in my case, using Show() is not an option because I cannot have the user click on things in the parent form. Hiding the parent form is not an option either as he needs to see information in the parent form.

Clearly hiding a form is more impactful than you counted on. Your code was involved in a security review that Microsoft conducted on Winforms. Very thorough, not often visible in the way it behaves but very visible in the source code. One rule is imposes is that a user should never lose control over the application.
A dialog is very troublesome that way. The core problem is that ShowDialog() creates a modal window that disables all the other windows. That creates an opportunity for malware, very easy to take advantage of, all it has to do is hide a dialog and you snookered the user. There isn't any way that the user can gain control of the app again. The one window that was enabled is hidden with no way for the user to re-activate it again. All the other windows are disabled so trying to click on them, or their taskbar button, will not have any effect. All that's left is for the user to use Task Manager to kill the app. And if the user account is locked down then that's not an option either.
I can hear you sputter by now: "But, but, it is my code that hides the dialog, not malware!" That's not the way it works in Windows, there's no way to tell that it actually was your code that did it. Not only because it could be injected code, it doesn't even have to be code that runs in your process. Any code can do it, it is part of the winapi.
So there's a specific counter-measure against this built into Winforms, it will automatically close a form if it is hidden while operating in dialog mode. Which of course has a big impact, code that was written after the ShowDialog() call will now run. Anything is possible, but a sure-fire mishap in your case is that this disposes another window and an attempt to revive it will die.
The rough guidance here is that you are doing it wrong. You are trying to build a security system on top of one that's already highly secure and heavily tested. And it is very risky, handling passwords yourself is a very good way to make the overall system much less secure. The average user will of course favor picking the same password as he used to login to Windows. Makes it much easier for an attacker to harvest that password.
Call LockWorkStation() instead.

After many hours of trial and error, I found out what the problem was.
My understanding of modal forms was that code would continue executing in the parent form only after the modal form was closed. In fact, the specification found on MSDN states:
A modal form or dialog box must be closed or hidden before you can continue working with the rest of the application.
This introduced a subtle bug in the way I handled the forms. This is the code I used to display the forms:
using (var theForm = new CreateInvoice())
{
theForm.ShowDialog();
if (theForm.Updated)
{
GetInvoiceStatus();
}
}
The using statement disposes of theForm as soon as the statement exits. Normally, this works perfectly fine as it would be called only when the user closes theForm. However, because ShowDialog() permits the parent form to continue its work when it is hidden, this meant that the code actually exited the using statement, which effectively disposed of theForm, resulting in my error.

Testing, it seems that Hide()ing a modal dialog - closes it. It actually triggers the FormClosing event.
Tested like this: (Also, see this answer.)
private void button1_Click(object sender, EventArgs e)
{
Form1 f1 = new Form1();
f1.ShowDialog();
}
private void button2_Click(object sender, EventArgs e)
{
Hide();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
MessageBox.Show("Closing");
}
EDIT
I noticed this actually doesn't solve the mystery, just adds more information. Here's another piece of information: When you're setting Visible to true - you're not showing it modal again. This time it's equivalent to Show().

One suggestion to the design: instead of saving forms(views), you should save the data that the form holds(Model) and destroy the form. When you need the form again, create it back with the data(model). First, this can resolve this mysterious dispose problem, second, each form will need GDI resource, which are limited, if two many forms, you'll encounter the memory and GDI problems.
As how to do this, please refer the MVC or MVP design pattern.
BTW my guess on this problem: when you make the form visible, it will try find its parent, but its parent may be already disposed. I encountered this problem once, it throws the object disposed exception.

Related

How to detect when a winforms application creates a new window?

I'm using a webbrowser control in my form application and I want to block any popup/alert/prompt window that it can create.
Currently, I am implementing various methods to block popups like:
Canceling various events that fire when a new window is created.
Changing global IE settings through the registry to make it show less
alerts and prompts.
Using browser feature controls to block some popups.
Injecting javascript into every page to disable functions that can create new windows.
Extending the web browser control with new events by implementing things like IDocHostShowUI which allow me to
block certain popups.
Using the "hidden" events of the base activeX webbrowser object like NewWindow2 and NewWindow3.
All of this combined blocks 99% of all the windows that the webbrowser control can create (the 1% being some extremely rare cases like a javascript prompt() function called from within an iframe which document is located on a different domain than the parent window, still haven't found a way to block that :D).
But it's a lot of code, making it a big mess which can sometime interfere with normal browsing.
I want to know if there is a different approach. Since most of the windows that are created by the webbrowser control are actually created by my application's process, is there a way to detect when my application creates any kind of window that is not the main form and close it instantly or block it's creation entirely?
UPDATE:
I have tried overriding the WndProc method for the webbrowser control, but no messages are sent when a prompt appears.
When overriding it for the entire form, I do get some messages when a prompt appears, but they are related to losing focus and not to the actual creation of a prompt. So I am able to detect when a prompt is created, but still not able to block it's creation.
If you just want to close any window except the current one, you can use the code below inside the timer_tick event:
private void timer1_Tick(object sender, EventArgs e)
{
for (int i = 0; i < Application.OpenForms.Count; i++)
{
Form form = Application.OpenForms[i];
if (form != this)
{
form.Close();
i--;
}
}
}

How to know if the dialog box is closed completely?

I have a parent form Form1 which is opened in fullscreen mode. There's a button in Form1 which opens a form Form2 (using .ShowDialog() method). When the form is closed, the program captures the screenshot of the Form1. But in the screenshot I can still see the Form2 which still has some opacity during closing (I'm using Microsoft Windows 7 Pro which animates the closing of the form by reducing the opacity of the form).
So how can I know when the Form2 is 100% closed, so that I can take screen shot of the program?
Edit: I'm using Graphics.CopyFromScreen Method in Form1 to capture the screen shot.
And Form2 is closed by the button click in Form2 using this.Close() method.
If there's a way to know for sure the Windows desktop manager animation has completed, it's not in .NET itself. You'd have to find a native API that provides this detail (and I'm not sure one exists…I'm not aware of one if it does) and execute it via p/invoke.
I think commenter/answerer dotctor has provided a couple of good options: just delay long enough to account for the animation, or shift the offending window out of the way as it's closing (and if you do reuse the same Form2 instance, shift it back before displaying it again).
That said, you might also consider using the Control.DrawToBitmap() method to do the screenshot, since you seem to want the image to be the full-screen image of the Form1 window. It has some limitations (which you can read about in the docs), but as long as none of those apply in your case, it might be a more convenient way to get the image you want.
I came up with an easy solution that contains no waiting.
add an handler to FormClosing event on Form2 and set its location to somewhere off screen.
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
this.Location = new Point(-4000,-4000);
}

How to close winform with TopMost=true, when another application opens on top of my app

I have a C# .Net 3.5, winform (displaying graph) that needs to remain open as a front screen. I refresh the graph during the datagridview RowEnter event of the calling window. I open graph winform as show(), and use TopMost = true. Everything works fine till I open another application like Word; Graph window still remains in the front of Word.
Is there an application event gets triggered when another application opens on top of my application, when I can close the open graph screen. Or, please let me know if you have a suggestion about the different approach.
Look at Form.Deactivate Event. It is raised when the form loses focus and is no longer the active form. You can use this event to Close the form
Sorry but I can't get the sense of your question. You set TopMost=true and the next moment you want it to hide behind another application... ????
This solution method is 100% working, considering that the login form name is loginForm.
Simply create the following method:
private void hideLogin()
{
if (System.Windows.Forms.Application.OpenForms["loginForm"] != null)
{
System.Windows.Forms.Application.OpenForms["loginForm"].Hide();
}
}
Call this method through task:
Task HideLoginTask = new Task(hideLogin);
HideLoginTask.Start();

MDI application issues

General description of application:
Main form as MDI Container. On application start, if there is no xml file for database configuration (it is checked in Main form) Main form i call another form as showdialog() to fill all database info to build connection string. Then i close form and open another for login, then i get back to Main form, which has Split Container (2 panels: 1-menu on top, 2-content from child forms).
I open forms with:
private void PlanButton_Click(object sender, EventArgs e)
{
plan.TopLevel = false;
KontenerMenu.Panel2.Controls.Add(plan);
plan.Dock = DockStyle.Fill;
plan.Show();
}
and close form with:
private void Plan_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = false;
this.Hide();
}
Problems i have with app:
1. When i hit Cancel button when i open ShowDialog() form for database app crashes. Cancel button is simply:
private void cancelButton_Click(object sender, EventArgs e)
{
Application.Exit();
}
2. I have problem with clicking button to open/close/open again child forms. When i hit 'X' and want o open, app crashes with exception that it cannot refer to non-existing object
3. I have several buttons when i hit one and then another one it is always below the first one and not on the top
4. For example my form is 200x200 and in right down corner i have button (so location let's say 190x190) and i hit maximize button. My button is still on 190x190 and i would like to have it on down right corner. I couldn't find any property for that. Is there any or i have to write some code for that.
I'm not sure I understood your questions. Please make them clear.
But as an answer to question #4, there's an anchor property that does what you want.
Instead of trying to exit the application from within the dialog form itself you should return a DialogResult value and test that in the main form. The cancel button on the dialog doesn't need any code, just set its DialogResult property to 'Cancel' and if you have an Ok button set its DialogResult to 'OK'.
DialogForm f = new DialogForm();
DialogResult r = f.ShowDialog();
if (r == DialogResult.Cancel)
{
Close();
}
I can immediately see a number of problems with you code, including:
If you're going to add controls dynamically using Controls.Add, you should make sure the controls you're adding are dynamically created using new(). I get a sense that you don't have a clear understanding of object lifetimes and the WindowForms control life cycle.
The Application.Exit method should be used only in unusual cases. It's purpose is to achieve exactly the result you're observing - to immediately "crash" the application. The easiest way to have a button close a modal dialog is the set the DialogResult property of the button.
Winforms has a very elegant system for placement of control on a variable sized window. In order to use this system, you should familiarize yourself with the Anchor and Dock properties that are available on all controls.
It looks like what you're doing is attempting to learn WinForms by trial and error. You can do this, but it will take much longer and be much more painful that getting a hold of a good tutorial, book, or perhaps even attending a class if you can manage it. That will allow you to take these issues one at a time and have a much more enjoyable learning experience.

What is the best way to call a method right AFTER a form loads?

I have a C# windows forms application. The way I currently have it set up, when Form1_Load() runs it checks for recovered unsaved data and if it finds some it prompts the user if they want to open that data. When the program runs it works alright but the message box is shown right away and the main program form (Form1) does not show until after the user clicks yes or no. I would like the Form1 to pop up first and then the message box prompt.
Now to get around this problem before I have created a timer in my Form, started the timer in the Form1_Load() method, and then performed the check and user prompt in the first Timer Tick Event. This technique solves the problem but is seems like there might be a better way.
Do you guys have any better ideas?
Edit: I think I have also used a background worker to do something similar. It just seems kinda goofy to go through all the trouble of invoking the method to back to the form thread and all that crap just to have it delayed a couple milliseconds!
I would use Form1_Shown()
Use the Shown event. It seems to suit what you need, and will only display the first time the form is shown.
Form f1 = new Form();
f1.Shown += new EventHandler(f1_Shown);
public void f1_Shown(object sender, EventArgs e)
{
// Show dialog in here
}
Try the "Shown" event:
Form.Show Event
Using a Windows.Forms.Timer is a good, stable, well-known, and easily understood technique for doing what you want. I would avoid any other timer objects.
The form's Shown event works well.
Overload / override the Show method. (My preferred technique for greater control.) In this method, I would do the checking needed. When ready, I would call the base.Show method, then do any other processing, such as message boxes, prompts, logging, or whatever.

Categories