Cannot set visibility or call Show, ShowDialog or EnsureHandle after a window has been closed

Nzall picture Nzall · Apr 29, 2014 · Viewed 14.5k times · Source

This is my App constructor for my WPF application:

public partial class App
{
    public App()
    {
        Run(new Login(false));
    }


}

And this is my Login constructor:

public Login(bool ignoreSettings)
    {
        InitializeComponent();
        if (ignoreSettings)
        {
            TxtUsername.Text = SettingsSaver.LoadUsername();
            TxtCrmUrl.Text = SettingsSaver.LoadCrmUrl();
            return;
        }
        if (string.IsNullOrWhiteSpace(SettingsSaver.LoadUsername()) || string.IsNullOrWhiteSpace(SettingsSaver.LoadCrmUrl())) return;
        try
        {
            CrmConnector.ConnectToCrm();
            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();
        }
        catch (SecurityAccessDeniedException)
        {
            MessageBox.Show(@"Uw inloggegevens zijn niet correct. Gelieve deze te controleren en opnieuw te proberen.");
        }
        finally
        {
            Close();
        }
    }

It starts the App constructor and goes through the Login constructor just fine, but once it reaches the App Constructor again after finishing the Login constructor, it crashes with an InvalidOperationException, with additional information: "Cannot set visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after the window has been closed.

The goal of the constructor is as follows: When the application is first started, I want to check if there are existing settings for this application. If they exist, I want to use those settings to connect to a 3rd party (Dynamics CRM 2011), open the main application window, and then close the Login screen. if they are not there, I want the user to set the settings.

HOWEVER, I also want to be able to start this window from a button on my main screen, in which case it should ignore the default settings and launch the login window again, allowing me to set the settings again.

I already managed to get it to work using 2 constructors, but Resharper complains when i does that because I basically ignore the parameter in the second constructor (the one which I launch from the button on the main screen. I'm trying to have 1 unified constructor so Resharper does not complain. Is that possible?

Edit: I don't want to keep my login window because I create a new window when I change the settings, using the following code in my MainWindow:

private void BtnSettings_Click(object sender, RoutedEventArgs e)
    {
        Login login = new Login(true);
        login.Show();
        Close();
    }

edit: some clarification: I don't want to show multiple windows. What I want is:

  1. on startup, launch Login.xaml;
  2. when Login.xaml is launched, check if the settings have already been set;
  3. if no settings, show Login.Xaml for setting;
  4. if Settings set, start MainWindow with saved settings.

In addition, I have a button on MainWindow which has to force-start Login.xaml but not check if there are settings. These are currently separate constructors and I would like to make 1 constructor of them.

Answer

satnhak picture satnhak · Apr 29, 2014

Your update makes it a bit clearer what it is you want to achieve. I suggest restructuring the Login window to make it more single responsibility and pushing the validation logic up into the App class so that it is responsible for managing initialization flow. A recipe is as follows:

Alter App.Xaml.cs so that it looks something like this; importantly there is no StartupUri:

<Application 
    x:Class="MyNamespace.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources />
</Application>

Where MyNamespace is the namespace of your App class.

Now you are going to manually start the application from App.OnStartup

public partial class App
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        base.OnStartup(e);

        if (!AreSettingsSet())
        {
            this.MainWindow = new Login();
            this.MainWindow.ShowDialog(); // Waits until closed.

            // Recheck the settings now that the login screen has been closed.
            if (!AreSettingsSet())
            {
                // Tell the user there is a problem and quit.
                this.Shutdown();
            }
        }

        this.MainWindow = new MainWindow();
        this.MainWindow.Show();
    }

    private bool AreSettingsSet()
    {
        // Whatever you need to do to decide if the settings are set.
    }
}

To summarise: remove your validation logic from the Login window to App, only show Login if needed and only show MainWindow if the settings are actually valid.