A parent form starts a child form that embeds the Chromium Embedded Framework through CEFSharp. The user has the ability to close the child form. The user can restart the child form. When that happens the application terminates.
if (debugForm == null || debugForm.IsDisposed)
{
if (debugForm != null && debugForm.IsDisposed)
{
debugForm = null;
}
debugForm = new CEFdebugger();
....
}
When the form has been run once, closed and restarted the line
debugForm = new CEFdebugger();
causes the application to terminate. In the output window I have
... has exited with code 1073741855 (0x4000001f)
According to this thread this indicates I should enable debugging unmanaged code, but that doesn't seem to have helped.
In the FormClosing event for CEFdebugger I have added
private void CEFdebugger_FormClosing(object sender, FormClosingEventArgs e)
{
Cef.Shutdown();
this.Controls.Remove(browser);
browser = null;
}
That hasn't helped. The application just terminates.
The problem was caused by the statement
Cef.Shutdown();
in CEFdebugger_FormClosing. By commenting it out and testing Cef.IsInitialized in the form constructor the problem was resolved.
Related
I'm having an issue closing a program when there is a large amount of dialogs open. I was able to reproduce the bug using the following functions. The parent form on which this runs on is created using Application.Run(mainform).
When button1 is clicked, it creates a very basic form that opens another dialog of itself when "ok" is pressed. It then initiates the OnShutdown() function. This waits 15 seconds, then attempts to close all windows and exit the program. When more than 6 dummy windows are open, the function closes all the dummy windows but not the main window.
The steps for me reproducing the problem is:
Click button1
Close created heavy dialog
Click on on the msgbox
Click on button1 again
On the new heavy dialog, I press Ok to open another heavy dialog
Continue to open more heavy dialog, until 6-10 are open concurrently
Once the 15 seconds are up, all the dialogs close, but the parent form remains open, when it should be closed.
private void button1_Click(object sender,EventArgs e) {
BasicTemplate heavy = new BasicTemplate();
heavy.ShowDialog();
OnShutdown();
}
private void OnShutdown() {
timerSignals.Enabled=false;//quit receiving signals.
string msg = "";
msg+=Process.GetCurrentProcess().ProcessName+" ";
msg+=Lan.g(this,"will shut down in 15 seconds. Quickly click OK on any open windows with unsaved data.");
MsgBoxCopyPaste msgbox = new MsgBoxCopyPaste(msg);
msgbox.Size=new Size(300,300);
msgbox.TopMost=true;
msgbox.Show();
ODThread killThread = new ODThread((o) => {
Thread.Sleep(15000);//15 seconds
//Also happens with BeginInvoke()
Invoke((Action)(() => { CloseOpenForms(true); }));
Thread.Sleep(1000);//1 second
Invoke((Action)Application.Exit);
});
killThread.Start(true);
return;
}
public void Start(bool isAutoCleanup) {
_isAutoCleanup=isAutoCleanup;
if(_thread.IsAlive) {
return;//The thread is already running.
}
if(_hasQuit) {
return;//The thread has finished.
}
_dateTimeStart=DateTime.Now;
_thread.Start();
}
//Code for "OK" button on the heavy dialog created above
private void butOK_Click(object sender,EventArgs e) {
BasicTemplate formp = new BasicTemplate();
formp.ShowDialog();
}
private bool CloseOpenForms(bool isForceClose) {
for(int f=Application.OpenForms.Count-1;f>=0;f--) { //Count backwards to avoid out of bounds
if(Application.OpenForms[f]==this) {
continue;
}
Form openForm=Application.OpenForms[f];//Copy so we have a reference to it after we close it.
openForm.Hide();
if(isForceClose) {
openForm.Dispose();
}
else {
openForm.Close();//Attempt to close the form
if(openForm.IsDisposed==false) {
openForm.Show();//Show that form again
return false;
}
}
}
return true;
}
When line 3's "heavy.ShowDialog()" is changed to "heavy.Show()", the holdup no longer takes place. When multiple heavy dialogs are opened using a different button, the issue no longer takes place (up to ~20 new dialogs).
The shutdown sequence is run as a thread to allow the user to save any changes they have made before the program's database updates. I'm not sure if what I've implemented correctly shows the bug, but it at least produces similar events.
The current problem is that the "mainScreen" shows up, but immediately closes, I have no idea why. This is the piece of code that handles the close and opening of the new form.
Edit: .this refers to Login.cs (sorry)
if(templogin == true && permission.Equals("1"))
{
mainScreen.IsAdmin();
this.Close();
mainScreen.ShowDialog();
}
I think, you should not close the application(as login form seems main form while you started the application) before another form run:
Do like this :
if(templogin == true && permission.Equals("1"))
{
this.Hide();
mainScreen.IsAdmin();
mainScreen.ShowDialog();
this.Close();
}
If you want to run 2 forms (switching from one to another), then you should do it in Main
Instead of
Application.Run(new StartupForm());
you'll have to use
var startup = new StartupForm();
startup.ShowDialog();
if(somecondition) // when StartupForm is closed and return something (or property is set, etc)
{
var main = new MainScreen();
main.ShowDialog();
}
You don't need Application.Run at all when using ShowDialog.
In my MainWindow I have a button that can be used to open a Process (native OpenProcess call) and perform some checks on it's memory, but the method called on Click is asynchronous:
<Button Content="Attach" Click="OnClickAttach"/>
private async void OnClickAttach(Object sender, RoutedEventArgs e)
{
AttachmentResult result = await m_ViewModel.Attach();
switch (result)
// Different MessageBox depending on the result.
}
Now, let's see the ViewModel portion of code...
// MemoryProcess class is just a wrapper for Process' handle and memory regions.
private MemoryProcess m_MemoryProcess;
public async Task<AttachmentResult> Attach()
{
AttachmentResult result = AttachmentResult.Success;
MemoryProcess memoryProcess = NativeMethods.OpenProcess(m_SelectedBrowserInstance.Process);
if (memoryProcess == null)
result = AttachmentResult.FailProcessNotOpened;
else
{
Boolean check1 = false;
Boolean check2 = false;
foreach (MemoryRegion region in memoryProcess)
{
// I perform checks on Process' memory regions and I eventually change the value of check1 or check2...
await Task.Delay(1);
}
if (!check1 && !check2)
{
NativeMethods.CloseHandle(memoryProcess.Handle);
result = AttachmentResult.FailProcessNotValid;
}
else
{
// I keep the Process opened for further use. I save it to a private variable.
m_MemoryProcess = memoryProcess;
m_MemoryProcess.Check1 = check1;
m_MemoryProcess.Check2 = check2;
}
}
return result;
}
Now... here comes the problem. When the user closes the application, if a Process is opened, I must properly close its handle. So in my MainWindow I have the following code:
protected override void OnClosing(CancelEventArgs e)
{
m_ViewModel.Detach();
base.OnClosing(e);
}
And in my ViewModel I have the following code:
public void Detach()
{
if (m_MemoryProcess != null)
{
if (m_MemoryProcess.Check1)
// Do something...
if (m_MemoryProcess.Check2)
// Do something...
NativeMethods.CloseHandle(m_MemoryProcess.Handle);
m_MemoryProcess = null;
}
}
The Attach() method can take very long time, more than 2 minutes sometimes. I need to find a solution for the following issues:
If the user closes the application while Attach() method is running and before memoryProcess is saved to the private variable, the Process handle will not be closed.
If I save the MemoryProcess instance to the private variable just at the beginning of the Attach() method, there is a risk for the user to get a NullReferenceException if he closes the application while the Attach() method is processing its foreach loop.
I absolutely don't want to make the user wait for Attach() method to complete before letting him close the application. That's horrible.
How can I do this?
IMO, if you do not explicitly and specifically target to create separate detached/independent processes like, for example, through:
using PInvoke.CreateProcess
using
(new System.Management.ManagementClass("Win32_ProcessStartup"))
.Properties["CreateFlags"].Value = 8;
or maintaining child process alive upon app closing by launching them through separate shell scripts or other processes remaining to run after app closing;
creating a new thread in another independent process using CreateRemoteThread
etc.
or finding already run independently processes, you don't need to and probably should not "close" or dispose spawned by app processes. Windows (operting system) will close any unclosed spawned by app processes.
Also, I believe that it is impossible to execute any code in an application once it has started exiting or being closed.
PS (off-topic comment):
I do not even see that you close (really one should kill) or dispose your processes in your code...
I need to be sure that all forms will be closed if I user somehow close the main form. So I decided to hide Close() function and I wrote something like this
public new bool Close()
{
List<Form> formsList = new List<Form>(Application.OpenForms.Count);
foreach (var form in Application.OpenForms)
formsList.Add((Form)form);
formsList.Reverse();
foreach (var form in formsList)
{
if (form.IsDisposed) continue;
Invoke(new GenericEventHandler(() => { form.Close(); }));
}
return Application.OpenForms.Count == 0;
}
So if all forms are closed successfully I return true and thanks to this I know that user can be logged out from application.
However it seems that form.Close() function is not fired immediately. After calling form.Close() formclosed event is not fired immediately as well as collection Application.OpenForms is not modified. The amount of opened forms is noted changed.
What could be a reason?
Why dont you just use
Application.Exit();
If the main form is created in the standard way in WinForms via Application.Run(new MainForm()); then when it closes, the application will exit, closing all other forms. So I don't think you need to worry about manually closing all child forms.
Well, if the user closes the main form, then upon closing it your application will definitely exit, since your form is responsible for keeping the application alive when it was passed to Application.Run().
Now, if you want to close your app when a form is closed, in that form you do this:
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
Application.Exit();
}
In general I suggest not to use new keyword to any members of Control and/or Form class. If you need your own method use your own name of it.
In your code you don't really need to use Invoke when closing child forms.
If you need to be sure that all your child Forms are closed before you actually execute logic related to MainForm.Close you may subscribe to MainForm.FormClosing event with handler similar to follwing:
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
bool allChildFormsClosed = !closeAllChildForms();
if(!allChildFormsClosed)
{
e.Cancel = true;
MessageBox.Show("Failed to close one or more child forms.");
// better add some yes|no dialog to force exit if necessary
}
}
You may implement closeAllChildForms in a similar way as you did above - just: remove the formsList.Reverse(); and the 'Invoke..' - just call Close directly and update: if (this == form || form.IsDisposed) continue;
If for some reason, Application.Exit isn't cutting it for you, you could try this code (put it in the form's FormClosing event):
while (Application.OpenForms.Count > 1)
{
Form form = Application.OpenForms[1];
form.Close();
while (!form.IsDisposed) Application.DoEvents();
}
or if you want it to time out if it can't close all the forms after a while,
while (Application.OpenForms.Count > 1)
{
Form form = Application.OpenForms[1];
form.Close();
DateTime StartTime = DateTime.Now;
while (!form.IsDisposed && (StartTime - DateTime.Now).TotalMilliseconds < 1000)
Application.DoEvents(); //1-second timeout here
if (!form.IsDisposed) //if it's still not closed
{
//send a message to the user that the application can't exit right now
mbox("some forms are still open or whatever");
e.Cancel = true;
return;
}
}
My WinForms app has a simple modal login form, invoked at startup via ShowDialog(). When I run from inside Visual Studio, everything works fine. I can just type in my User ID, hit the Enter key, and get logged in.
But when I run a release build directly, everything looks normal (the login form is active, there's a blinking cursor in the User ID MaskedEditBox), but all keypresses are ignored until I click somewhere on the login form. Very annoying if you are used to doing everything from the keyboard.
I've tried to trace through the event handlers, and to set the focus directly with code, to no avail.
Any suggestions how to debug this (outside of Visual Studio), or failing that - a possible workaround?
Edit
Here's the calling code, in my Main Form:
private void OfeMainForm_Shown(object sender, EventArgs e)
{
OperatorLogon();
}
private void OperatorLogon()
{
// Modal dialogs should be in a "using" block for proper disposal
using (var logonForm = new C21CfrLogOnForm())
{
var dr = logonForm.ShowDialog(this);
if (dr == DialogResult.OK)
SaveOperatorId(logonForm.OperatorId);
else
Application.Exit();
}
}
Edit 2
Didn't think this was relevant, but I'm using Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase for it's splash screen and SingleInstanceController support.
I just commented out the splash screen code, and the problem has disappeared. So that's opened up a whole new line of inquiry...
Edit 3
Changed title to reflect better understanding of the problem
UI focus/redraw/etc. issues usually are rather straightforward to debug by using remote-debugging. I.e. use a second PC (virtual is just enough) where your application runs.
See this MSDN article for details.
Run this in your form code behind. It will tell you which control has focus by giving you the type and name of the control. Run it in form_shown because its the last event in the form load process.
private void Form1_Shown(object sender, EventArgs e)
{
Control control = FindFocusedControl(this);
MessageBox.Show("The focused control " + control.Name + " is of type " + control.GetType());
}
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
If the answer isn't obvious after that, tell us what you get.
I've found a hack...er...I mean...workaround, that fixes the problem. The solution was buried in one of the comments of this answer (thanks, P. Brian Mackey, for providing the link to the related question!)
The workaround is to minimize the main window while the splash screen is displayed, then set it's WindowState back to Normal before showing the login form.
In the code below, see the lines commented with "HACK".
public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
this.IsSingleInstance = true;
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that
/// initializes the splash screen.
/// </summary>
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new SplashScreen();
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that configures
/// the splash screen and main form.
/// </summary>
protected override void OnCreateMainForm()
{
// SplashScreen will close after MainForm_Load completed
this.MainForm = new OfeMainForm();
// HACK - gets around problem with logon form not having focus on startup
// See also OfeMainForm_Shown in OfeMainForm.cs
this.MainForm.WindowState = FormWindowState.Minimized;
}
}
public partial class OfeMainForm : Form
{
// ...
private void OfeMainForm_Shown(object sender, EventArgs e)
{
// HACK - gets around problem with logon form not having focus on startup
// See also OnCreateMainForm in Program.cs
this.WindowState = FormWindowState.Normal;
OperatorLogon();
}
// ...
}
This is working for now, but I'm wondering if I should explicitly open the Logon form from the SingleInstanceController, rather than from my main form.