ive checked out some of the other questions and obviously the best solution is to prevent the behavior that causes this issue in the first place, but the problem is very intermittent, and very un-reproduceable.
I basically have a main form, with sub forms. The sub forms are shown from menus and/or buttons from the main form like so:
private void myToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
xDataForm.Show();
xDataForm.Activate();
}
catch (ObjectDisposedException)
{
MessageBox.Show("ERROR 10103");
ErrorLogging newLogger = new ErrorLogging("10103");
Thread errorThread = new Thread(ErrorLogging.writeErrorToLog);
errorThread.Start();
}
}
and the sub forms are actually in the main form(for better or worse. i would actually like to change this but would be a considerable amount of time to do so):
public partial class FormMainScreen : Form
{
Form xDataForm = new xData();
...(lots more here)
public FormMainScreen(int pCount, string pName)
{
InitializeComponent();
...
}
...
}
The Dispose function for the sub form is modified so that, the 'close' and 'X' buttons actually hide the form so we dont have to re-create it every time. When the main screen closes, it sets a "flag" to 2, so the other forms know that it is actually ok to close;
protected override void Dispose(bool disposing)
{
if (FormMainScreen.isExiting == 2)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
else
{
if (xData.ActiveForm != null)
{
xData.ActiveForm.Hide();
}
}
}
So, the question is, why would this work over and over and over again flawlessly, but, literally, about every 1/1000 of the time, cause an exception, or rather, why is my form being disposed?
I had a suspicion that the garbage collector was getting confused, because it occurs slightly more frequently after it has been running for many hours.
No offense, but this seems to be a very convoluted solution to a problem that was solved a very long time ago.
You shouldn't be doing anything in the Dispose() method other than disposing other disposables (and even then only if the disposing flag is true.) So I would not modify the method that the designer generates for you.
So the immediate answer to your question as to why this is happening is almost certainly related to the timing of the garbage collector calling your Dispose method.
Instead you should probably consider using a MDI (multiple document interface) parent form and your "sub forms" are called MDI children. You could then handle the FormClosing event in the children like so.
(Note that if you are opposed to MDI, you can do basically the same thing by using form Owners.)
// MDI child
private void Form_FormClosing(object sender, FormClosingEventArgs e) {
if (e.CloseReason == CloseReason.UserClosing) {
e.Cancel = true;
Hide();
}
}
When the form is closing because of various reasons such as closing in code, the parent form is closing, Windows is shutting down, etc. then the closing will not be cancelled. Only when the window is being closed because the user closed the child form directly will you hide it.
To show a MDI child inside of a MDI parent, you can do the following:
MyParentForm parentForm = new MyParentForm();
parentForm.IsMdiContainer = true;
parentForm.Show();
MyChildForm childForm = new MyChildForm();
childForm.MdiParent = parentForm;
childForm.Show();
try
{
// Validate form not disposed before using. Initialize as needed.
if (xDataForm == null || xDataForm.IsDisposed)
{
xDataForm = new MyDataFormName();
}
xDataForm.Show();
xDataForm.Activate();
}
Related
I have a frame and few pages in my WPF application.
My navigation is controlled by buttons. On each button I have click handler that creates new page with some parameters:
private void ButtonProductionAuto_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionAuto(someobject, this);
}
private void ButtonProductionManual_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionManual(someobject, this);
}
When I'm switching between pages - previous pages still exist in memory and they react on some custom events.
(edit)
This is my code related with events:
public PageProductionAuto(ref TechModbus modbus, MainWindow wnd)
{
// ...
wnd.KeyDown += Wnd_KeyDown;
wnd.KeyUp += Wnd_KeyUp;
m.OnReadFinished += Modbus_OnReadFinished;
// ...
}
How can I dispose these pages or how can I avoid double-fire on my events when page is opened second time?
You should unregister the events on leaving the page.
GarbageCollector will then "dispose" (it's actually not a dispose) by itsself when there are no more references on those objects(PageProductionAuto and PageProductionManual).
Quoting MS:
The reason WPF controls don't implement IDisposable is because they have nothing to dispose. They have no handle to clean up, and no unmanaged resources to release. To ensure your memory is cleaned up, just make sure nothing has a reference to the controls once you're finished with them.
Your question is incomplete. But I can answer the question about "how to avoid multiple instances" part of it. To dispose your pages, you have to detach your events, remove them from the "openedPages" collection, and dispose where possible.
List<object> openedPages = new List<object>();
private void ButtonProductionAuto_OnClick(object sender, RoutedEventArgs e)
{
var page = openedPages.FirstOrDefault(p => p.GetType().Equals(typeof(PageProductionAuto)));
if(page == null)
{
page = new PageProductionAuto(someobject, this);
opendPages.Add(page);
}
else
{
page.SetObjects(someobject, this); // create a method to set "someObject" to your page.
}
FrameMain.Content = page;
}
What I did to avoid this was I have a window with 2 frames in it. I placed one of each source XAML in each frame so frame 1 was XAML 1 and frame 2 was XAML 2. Then I just edited the visibility to collapsed and visible. Then you don't have any page changes or instances getting created. You just create the original 2 instances.
I am having an odd problem with protecting a section of code. My application is a tray app. I create a NotifyIcon inside my class (ApplicationContext). I have assigned a balloon click handler and a double click handler to the NotifyIcon object. there is also a context menu but I am not showing all code. Only important pieces.
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
Both handlers launch or create/show my form:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
There is a problem with the above code. Maybe more than 1. Issue: it is possible to display 2 dashboard forms which is not what I want. If user double clicks on tray icon while balloon message is displaying causes a race condition in openDashboard. I can reproduce this easily. So I added a lock around the code in openDashboard code and, to my surprise, that did NOT prevent 2 dashboard forms from displaying. I should not be able to create 2 MainForms. Where am I going wrong here?
here is the updated code with lock statement:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
Note: lock object was added to the class and initialized in constructor.
private object dashBoardFormlocker;
UPDATE: Showing more code. this is how code gets started :
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
UPDATE 2: Provide more code for clarity
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
The main UI thread must always pump a message loop to support communication from COM components.
So when you do a blocking operation from the UI thread like locking or joining a thread, (EDIT: edited based on Peter Duniho's fix) the UI thread will enter an 'alertable' state, allowing COM to dispatch certain type of messages, which in turn can cause re-entrancy issues like in your scenario.
Look at the answer to this question (Why did entering a lock on a UI thread trigger an OnPaint event?) for a much more accurate explanation.
Looking at the source code of ManagementObjectSearcher.Get there is a lock (inside Initialize), and since you call it from the constructor of your form, it may lead to the second event triggering while the form's constructor has not finished. The assignment to the dashBoardFormlocker variable only happens after the constructor finishes, so that would explain why it was null on the second entry.
The moral of the story is never do blocking operations on the UI thread.
Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know for sure what the problem is. But the guess by answerer tzachs seems reasonable. If so, you can fix your problem by changing your method to look like this:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
In that way, any re-entrant attempt to open the window will be detected. Note that you still need the check for null before actually activating; you can't activate a window that hasn't actually finished being created yet. The subsequent call to Show() will take care of activation anyway, so ignoring the activation in the re-entrant case shouldn't matter.
I am new to WPF and have been hunting for an answer, surely this is not difficult?
I have created a main window with links to multiple windows, and I want them to run modelessly alongside one another, but I don't want to open multiple instances of the SAME window.
In simple terms, I can have Windows A, B, C open at once, but not Windows, A, A, B, C, C.
I need to implement a check for the window I'm trying to open (in this case, EditSettings).
If open - activate it
if not open, open it.
I have the following code in Main, which is not working.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
EditSettings winEditSettings = new EditSettings();
string isOpen = null;
if (isOpen == "true")
{
winEditSettings.Activate();
}
else
{
winEditSettings.Show();
isOpen = "true";
}
}
}
Now I know what's wrong with this logic - every time I press the button to open EditSettings, it's setting isOpen to null again. If I don't set a value to isOpen, the If condition breaks.
I could initialise the variable 'isOpen' as a public variable outside the MenuItem_Click method, but then I think I would need an isOpen variable for each window I create!! Surely there is a better way?
The other option I tried is:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
EditSettings winEditSettings = new EditSettings();
if (winEditSettings.IsLoaded)
{
winEditSettings.Activate();
}
else { winEditSettings.Show(); }
I can't figure out why this isn't working, I tried isVisible, isLoaded, isActive - nothing is stopping the window from opening more than once. Thank you for the help!
There are people who'll perhaps throw a fit at the idea, but whenever I've needed to do this, I made the child window objects part of the application. Then, in your MenuItem_Click(), test if winEditSettings is null, instead.
It's still a member variable for each window (like your provisional isOpen solution), but having the window objects available can have advantages later, if you need to bridge information between the windows. In my cases, we wanted to be able to close all the child windows together, which (most trivially) meant keeping track of those objects in a central location.
Alternatively, if you want the setup completely decoupled, you could take a singleton-like approach and put the logic into your child window classes. Specifically, you could call EditSettings.Activate and let the class keep track of whether a window needs to be created or the existing window merely Show()n.
If I were handed your code to rewrite, I'd move it something like this:
private static EditSettings winEditSettings = null;
public static void WakeUp()
{
if (winEditSettings == null)
{
winEditSettings = new EditSettings();
}
winEditSettings.Activate(); // This may need to be inside the block above
winEditSettings.Show();
}
Both of those are part of the class (static), rather than an instance. Your application object therefore calls EditSettings.WakeUp() inside the original MenuItem_Click(), and never actually sees the child window, itself.
If you change your mind about the decoupled architecture later, by the way, you can add a get accessor to your winEditSettings and keep everybody fairly happy.
if (_adCst == null)
{
_adCst = new AddCustomerPage();
_adCst.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
_adCst.WindowState = System.Windows.WindowState.Normal;
_adCst.ResizeMode = System.Windows.ResizeMode.NoResize;
_adCst.Activate(); // This may need to be inside the block above
_adCst.Show();
}
else
{
if (!_adCst.IsLoaded == true)
{
_adCst = new AddCustomerPage();
_adCst.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
_adCst.WindowState = System.Windows.WindowState.Normal;
_adCst.ResizeMode = System.Windows.ResizeMode.NoResize;
_adCst.Show();
}
_adCst.Activate();
}
My suggestion would be that you set some form of a counter. This will prevent more than one instance of the window being opened.
int windowOpen = 1;
private void button_Click(object sender, RoutedEventArgs e)
{
if (windowOpen == 1)
{
WindowA winA = new WindowA();
winA.Show();
windowOpen++; //increments windowOpen by 1, windowOpen will now = 2
}
else if (windowOpen > 1)
{
MessageBox.Show("Window is already open");
}
}
I hope this helps.
For anyone else with this question, I have found another solution - which works except that it doesn't manage to bring the open window to the front (Activate). It does, however, prevent opening the same window more than once.
foreach (Window n in Application.Current.Windows)
if (n.Name == "winEditSettings")
{ winEditSettings.Activate(); }
else
{ winEditSettings.Show(); }
Can anyone speculate on why the window is not brought to the front, with Activate()?
EDIT
For others with this question, placing the winEditSettings.Activate() outside of the foreach loop does everything I'm trying to achieve:
foreach (Window n in Application.Current.Windows)
if (n.Name == "winEditSettings")
{ }
else
{ winEditSettings.Show(); }
winEditSettings.Activate();
This will stop multiple instances of the same window from opening, and will bring the window to the front if the user attempts to reopen it.
When a user clicks the X button on a form, how can I hide it instead of closing it?
I have tried this.hide() in FormClosing but it still closes the form.
Like so:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
(via Tim Huffman)
I've commented in a previous answer but thought I'd provide my own. Based on your question this code is similar to the top answer but adds the feature another mentions:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
If the user is simply hitting the X in the window, the form hides; if anything else such as Task Manager, Application.Exit(), or Windows shutdown, the form is properly closed, since the return statement would be executed.
From MSDN:
To cancel the closure of a form, set the Cancel property of the FormClosingEventArgs passed to your event handler to true.
So cancel then hide.
Based on other response, you can put it in your form code :
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
According MSDN, the override is preferred:
The OnFormClosing method also allows derived classes to handle the
event without attaching a delegate. This is the preferred technique
for handling the event in a derived class.
If you want to use the show/hide method I've actually done this myself for a menu structure a game I've recently done... This is how I did it:
Create yourself a button and for what you'd like to do, for example a 'Next' button and match the following code to your program. For a next button in this example the code would be:
btnNext.Enabled = true; //This enabled the button obviously
this.Hide(); //Here is where the hiding of the form will happen when the button is clicked
Form newForm = new newForm(); //This creates a new instance object for the new form
CurrentForm.Hide(); //This hides the current form where you placed the button.
Here is a snippet of the code I used in my game to help you understand what I'm trying to explain:
private void btnInst_Click(object sender, EventArgs e)
{
btnInst.Enabled = true; //Enables the button to work
this.Hide(); // Hides the current form
Form Instructions = new Instructions(); //Instantiates a new instance form object
Instructions.Show(); //Shows the instance form created above
}
So there is a show/hide method few lines of code, rather than doing a massive piece of code for such a simple task.
I hope this helps to solve your problem.
Note that when doing this (several answers have been posted) that you also need to find a way to ALLOW the user to close the form when they really want to. This really becomes a problem if the user tries to shut down the machine when the application is running, because (at least on some OS) this will stop the OS from shutting down properly or efficiently.
The way I solved this was to check the stack trace - there are differences between when the user tries to click the X vs when the system tries to end the application in preparation for shutdown.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
StackTrace trace = new StackTrace();
StackFrame frame;
bool bFoundExitCommand = false;
for (int i = 0; i < trace.FrameCount; i++)
{
frame = trace.GetFrame(i);
string methodName = frame.GetMethod().Name;
if (methodName == "miExit_Click")
{
bFoundExitCommand = true;
Log("FormClosing: Found Exit Command ({0}) - will allow exit", LogUtilityLevel.Debug3, methodName);
}
if (methodName == "PeekMessage")
{
bFoundExitCommand = true;
Log("FormClosing: Found System Shutdown ({0}) - will allow exit", LogUtilityLevel.Debug3, methodName);
}
Log("FormClosing: frame.GetMethod().Name = {0}", LogUtilityLevel.Debug4, methodName);
}
if (!bFoundExitCommand)
{
e.Cancel = true;
this.Visible = false;
}
else
{
this.Visible = false;
}
}
This is the behavior of Modal forms. When you use form.ShowDialog() you are asking for this behavior. The reason for this is that form.ShowDialog doesn't return until the form is hidden or destroyed. So when the form is hidden, the pump inside form.ShowDialog destroys it so that it can return.
If you want to show and hide a form, then you should be using the Modeless dialog model
http://msdn.microsoft.com/en-us/library/39wcs2dh(VS.80).aspx
form.Show() returns immediately, you can show and hide this window all you want and it will not be destroyed until you explicitly destroy it.
When you use modeless forms that are not children of a modal form, then you also need to run a message pump using Application.Run or Application.DoEvents in a loop. If the thread that creates a form exits, then the form will be destroyed. If that thread doesn't run a pump then the forms it owns will be unresponsive.
Edit: this sounds like the sort of thing that the ApplicationContext is designed to solve. http://msdn.microsoft.com/en-us/library/system.windows.forms.applicationcontext.aspx
Basically, you derive a class from ApplicationContext, pass an instance of your ApplicationContext as an argument to Application.Run()
// Create the MyApplicationContext, that derives from ApplicationContext,
// that manages when the application should exit.
MyApplicationContext context = new MyApplicationContext();
// Run the application with the specific context.
Application.Run(context);
Your application context will need to know when it's ok to exit the application and when having the form(s) hidden should not exit the application. When it's time for the app to exit. Your application context or form can call the application context's ExitThread() method to terminate the message loop. At that point Application.Run() will return.
Without knowing more about the heirarchy of your forms and your rules for deciding when to hide forms and when to exit, it's impossible to be more specific.
I have a form that can open a sub form (with ShowDialog). I want to make sure that the sub form is being disposed properly when the main form is done.
I tried adding the subform to the components member of the main form, but at the moment I got a ArgumentNullException.
I know I can just instantiate the components myself, but isn't that a bit dangerous? One day I'll add a component in the designer view, and that will generate the new Container() line in the designer.cs file, and I'll never know I have two component instanses running around the heap.
Is there an easier way to make sure the sub form is being disposed?
EDIT - moved my solution to an answer
If you're using the form in showdialog, one could assume that after you've received the result you could dispose the form there?
using(MyDialog dlg = new MyDialog())
{
result = dlg.ShowDialog();
}
You usually cannot override the Dipose methods of a form, because it's alread defined in the Form.Designer.cs file. There's a little trick how to add your own disposing logic to a form.
Using the following class:
public class Disposer : Component
{
private readonly Action<bool> disposeAction;
public Disposer(Action<bool> disposeAction)
{
this.disposeAction = disposeAction;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.disposeAction(disposing);
}
public static Disposer Register(ref IContainer container, Action<bool> disposeAction)
{
Disposer disposer = new Disposer(disposeAction);
if (container == null)
container = new System.ComponentModel.Container();
container.Add(disposer);
return disposer;
}
}
Keep a list of subforms and add the following line to constructor of the mainform:
Disposer.Register(ref this.components, this.MyDisposeAction);
When your mainform is disposed, all your subforms will also be disposed, e.g.:
private void MyDisposeAction(bool disposing)
{
if (disposing)
{
foreach (var subForm in this.subForms)
{
subForm.Dispose(disposing);
}
}
}
As per MSDN, Modal forms invoked via ShowDialog() are not disposed automatically and the onus is on the developer to dispose them:
When a form is displayed as a modal dialog box, clicking the Close button (the button with an X at the upper-right corner of the form) causes the form to be hidden and the DialogResult property to be set to DialogResult.Cancel. Unlike modeless forms, the Close method is not called by the .NET Framework when the user clicks the close form button of a dialog box or sets the value of the DialogResult property. Instead the form is hidden and can be shown again without creating a new instance of the dialog box. Because a form displayed as a dialog box is not closed, you must call the Dispose method of the form when the form is no longer needed by your application.
The relevant point here is "when the form is no longer needed". In your case, you seem to need the form for subsequent actions, therefore wrapping the ShowDialog() call in a using construct would not serve the purpose.
My suggestion would be to dispose of the Dialog in the Dispose method of your Main form, or as early as possible:
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
// dlg is a variable of type Form2(the dialog)
if (dlg != null)
{
dlg.Dispose();
dlg = null;
}
base.Dispose(disposing);
}
My current workaround:
I've added the Components property to the form, and am using it to access the collection
private IContainer Components{ get { return components ?? (components = new Container());}}