c# Windows Forms: Form Close Event Cancel Button - c#

I have a Form_Closing event that prompts the user if the file has been changed to save if changes have been made to the file (standard Yes/No/Cancel options). Cancel is where things don't work as they should.
If I select File -> New and there is an existing file with changes I get prompted as expected, bit when I select Cancel the new form is presented rather than staying on the current form and I end up with two forms open at once.
Here is MainForm (File New) code:
if (editForm != null)
{
// Close existing Editor form
editForm.Close();
// Open new form
editForm = new EditorForm(this);
// Close Form Events
editForm.Closing += new CancelEventHandler(EditorForm_Closing);
editForm.Show();
editForm.Focus();
else
{
// Open new Editor
editForm = new EditorForm(this);
// Close Form Events
editForm.Closing += new CancelEventHandler(EditorForm_Closing);
editForm.Show();
editForm.Focus();
}
Here is my EditForm_Closing:
if (editForm != null)
{
if (editForm.diagramComponent.Model.Modified)
{
DialogResult res = MessageBox.Show(this, "The project has been modified. Save changes?", "Save changes", MessageBoxButtons.YesNoCancel);
if (res == DialogResult.Yes)
{
if (!editForm.HasFileName)
{
if (this.saveEditorDialog1.ShowDialog(this) == DialogResult.OK)
{
this.ActiveDiagram.SaveSoap(this.saveEditorDialog1.FileName);
editForm.FileName = this.saveEditorDialog1.FileName;
}
}
else
{
this.ActiveDiagram.SaveSoap(editForm.FileName);
}
}
else if (res == DialogResult.Cancel)
{
e.Cancel = true;
}
}
Not sure how to make the correlation between the Cancel close event and my File -> New. Any help is greatly appreciated. Thank you.
EDIT: Added my EditForm_Closing Event.

Try replacing your main form's code with the following:
if (editForm != null) {
// try closing existing Editor form
editForm.Close();
if(!editForm.IsDisposed) // close was canceled.
return;
}
// Open new form
editForm = new EditorForm(this);
// Close Form Events
editForm.FormClosing += new FormClosingEventHandler('suitable method here');
editForm.Show();
editForm.Focus();

Your Closing event handler should set the editForm property back to null. So check it like this:
if (editForm != null) {
editForm.Close();
if (editForm != null) return; // Close was cancelled
// etc..
}
Or just use a private boolean member.

Related

Why is this C# code executing out of sequence? Not ASYNC (at least I don't think it is)

Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}

How to hide/close all forms and show login when logging out a user?

I cannot figure out how to close all open forms and open the login form when the user has left the application idle for a period of time.
The below code almost works but the non-login form(s) are still visible and clickable in the taskbar.
What am I missing here? Also is there something else I should initially be setting var frm to?
public static void Logout()
{
var frm = Form.ActiveForm;
for (int ix = Application.OpenForms.Count - 1; ix >= 0; --ix)
{
frm = Application.OpenForms[ix];
if (frm.GetType() != typeof(Login))
{
logoutIdleUser = true;
}
}
if (logoutIdleUser)
{
// Logout user
MessageBox.Show("You will be logged out.", "Session Expired!");
frm.Hide();
new Login().Show();
}
}
Your close operation isn't coupled with your search for open forms, so you're only closing one form. By changing your for loop to contain your close events, you resolve that problem. Also, I changed to a foreach since we're using frm as a variable anyway, to make it more readable. Instead of checking the active form, we use it as the loop variable and declare it there.
foreach (Form frm in Application.OpenForms)
{
if (frm.GetType() != typeof(Login))
{
frm.Hide();
}
new Login().Show();
MessageBox.Show("You will be logged out.", "Session Expired!");
}
This is the implementation I have gone with. It shows the messagebox on top of the login form, as opposed to the previous form.
It is not exactly what I was looking for in the question but it is acceptable for the project. Thanks to #CDove for the help.
public static void Logout()
{
foreach (Form frm in Application.OpenForms)
{
if (frm.GetType() != typeof(Login))
{
frm.Hide();
logoutIdleUser = true;
}
}
if (logoutIdleUser)
{
new Login().Show();
MessageBox.Show("You will be logged out.", "Session Expired!");
}
}

How to perform action for specific forms in the class of the BaseForm?

I want to let my program know which form is opened and then do some specific action for that form. Forms are opened in a main-window with an overview and the user can click on an image to open a new form. This main-window is not my BaseForm for the forms I want to open.
To check what form is open, I tried the following code:
bool thisOne;
bool theOtherOne;
private void FormCheck()
{
foreach (Form form in Application.OpenForms)
{
if (form is frmRectangle)
{
thisOne = true;
theOtherOne = false;
break;
}
if (form is frmCircular)
{
theOtherOne = true;
thisOne = false;
break;
}
}
}
I set the form-relatedbooleans to true, so I can use them in another function, something like this:
private void ActionTime()
{
if (thisOne)
Debug.WriteLine("ThisOne is open");
//Do some stuff for the ThisOne form
if (theOtherOne)
Debug.WriteLine("TheOtherOne is open");
//Do some stuff for TheOtherOne form
}
ActionTime is called by a ClickEvent, but the action never happens... I guess something's going wrong it the foreach-loop.

Alert user about the running process during installation

protected override void OnBeforeInstall(IDictionary savedState)
{
base.OnBeforeInstall(savedState);
DialogResult result = DialogResult.None;
if (isExcelIsRunning())
{
result = MessageBox.Show("Excel is still running. Please save your work and close Excel, and then click \"Retry\" button to continue with installation.", "", MessageBoxButtons.RetryCancel, MessageBoxIcon.Warning);
}
while (result == DialogResult.Retry)
{
if (!isExcelIsRunning())
{
break;
}
}
}
//check if excel is currently running
private bool isExcelIsRunning()
{
bool flag = false;
Process[] xlProc = Process.GetProcessesByName("excel");
if (xlProc != null)
flag = true;
return flag;
}
The above is the code I am about to use for my Installer Class.
What I want to achieve here is that I need the installer to check whether or not Excel is currently running during the installation. And if it is running, then there should be a pop-up message at the start of installation to alert user to close off Excel and the installer should pause until there's no Excel instance found in the process list any more.
Back to the code, I don't think that while block is quite right, as it could cause an endless loop after user clicks "Retry" button if in the mean time Excel is still running.
So what'd be the better approach? Would anyone here take a look at the above code and see where I can improve?
I think your code would be something like this:
while(isExcelRunning())
{
var result = MessageBox.Show("Excel is still running. Please save your work and close Excel, and then click \"Retry\" button to continue with installation.", "", MessageBoxButtons.RetryCancel, MessageBoxIcon.Warning);
if (result != DialogResult.Retry)
{
//Handle Cancel
break;
}
}
This will keep displaying the alert in a loop, either until Excel exits, or they press cancel, at which you can do whatever you need to do; then we exit the loop (or you could return entirely out of the method).
The key here is display the alert repetitively, either until Excel is gone or they choose to cancel. If you need to do some code after the loop based on the response; perhaps something like this:
var userHasCancelled = false;
while(!userHasCancelled && isExcelRunning())
{
var result = MessageBox.Show("Excel is still running. Please save your work and close Excel, and then click \"Retry\" button to continue with installation.", "", MessageBoxButtons.RetryCancel, MessageBoxIcon.Warning);
if (result != DialogResult.Retry)
{
userHasCancelled = true;
}
}
if (userHasCancelled)
{
//User cancelled...
}
else
{
//Continue.
}

How restart the Window?

WPF
I need CLEAN and START again the window SetPathCharger.xaml when the user clic on "Yes" the message box, the problem is the application send a error InvalidOperationException.
public void ExitProgram(string message)
{
var restart = MessageBox.Show("Do you want do it again?",
"Question", MessageBoxButton.YesNo,
MessageBoxImage.Question).ToString();
if (restart == "Yes")
{
_setPathCharger.ShowDialog();
}
if (restart == "No")
{
Environment.Exit(0);
}
}
How can I do this?
You should just create and show a new SetPathCharger window, instead of reusing the current one. Something like:
_setPathCharger = new SetPathCharger();
_setPathCharger.ShowDialog();
Assuming ExitProgram is in some outer scope and is triggered after closing _setPathCharger then I suppose you are trying to ShowDialog() a disposed object.
Try to:
_setPathCharger = new SetPathCharger();
_setPathCharger.ShowDialog();

Categories