Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Application.Current is null, but new Application() still fails?

Tags:

c#

wpf

I have an unmanaged application which uses a WPF assembly for some of its user interface. Because of this arrangement Application.Current is not created automatically. So when the first WPF window is loaded, my code does this:

        if (System.Windows.Application.Current == null)
        {
            new System.Windows.Application();
        }

This works the first time and is the approach I've seen recommended.

But if the user closes the (only) WPF window, and later loads it again, even though Current == null again an exception is thrown when the Application ctor is called.

It is clear in the documentation that you can only have one Application per AppDomain - but why then is Current null and yet I can't create it?


The exception that is thrown is of type InvalidOperationException and has the message:

Cannot create more than one System.Windows.Application instance in the same AppDomain.

Its InnerException is null.


To work around this I have tried to:

  • Construct Application using ShutdownMode = ShutdownMode.OnLastWindowClose

  • Explicitly call Current.Shutdown() when the WPF window is closed

but neither has made any difference.

Is there some other way to manually manage the lifetime of the Current object? Or should I instead attempt to create it when the unmanaged application starts, and then rely on it always being set for the lifetime of the process?

like image 589
StayOnTarget Avatar asked Feb 11 '20 14:02

StayOnTarget


2 Answers

The documentation you linked states the following in its remarks section:

Only one instance of the Application class can be created per AppDomain, to ensure shared access to a single set of application-scope window, property, and resource data. Consequently, the parameterless constructor of the Application class detects whether the instance being initialized is the first instance in an AppDomain; if it is not, an InvalidOperationException is thrown.

The part I highlighted implies that it is not checking if it is the only / single application currently running, but rather that it checks if any another Application instance has been initialized before (regardless of whether or not it has been closed yet).

Taking a look at the source code of the Application class confirms this: The Application class internally uses a static flag (_appCreatedInThisAppDomain) that is set only once when initializing the first Application instance. But apparently this flag is never reset, which prevents you from creating any more Application instances within the same AppDomain.

like image 69
bassfader Avatar answered Oct 22 '22 11:10

bassfader


This is easy in WinForms, not so much in WPF.

Obviously, we don't have an Application problem, we have an AppDomain problem.

I put a reasonable amount of effort into this, but couldn't get it to behave as I wanted it to, that is to destroy the old then recreate an AppDomain on a new Thread when the spacebar is pressed, I suppose that makes sense though given the scope.

It's a work around at best, and may not even be an option in your situation.

Is there some other way to manually manage the lifetime of the Current object?

As best I can tell, the simple answer is to just maintain a WPF message loop Thread for the life of the program (via ShutdownMode.OnExplicitShutdown), and use the Application.Current.Dispatcher to display WPF objects as needed.

Here's an example of what I mean, as implemented in a managed console application:

class Program
{
    static void Main(string[] args)
    {
        Thread t = CreateThread();

        t.Start();

        bool quit = false;
        while (!quit)
        {
            switch(Console.ReadKey().Key)
            {
                case ConsoleKey.Escape:
                    Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown());
                    quit = true;
                    break;
                case ConsoleKey.W:
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        var w = new Window() { Width = 500, Height = 500, Title = "WPF Window" };
                        w.Show();
                    });
                    break;
                case ConsoleKey.D:
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        var d = new Window() { Width = 500, Height = 500, Title = "WPF Dialog" };
                        d.ShowDialog();
                    });
                    break;
                case ConsoleKey.Spacebar:
                    //// Nope!
                    //Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown());
                    //t = CreateThread();
                    //t.Start();
                    break;
            }
        };
    }

    static Thread CreateThread()
    {
        var t = new Thread(() =>
        {
            if (System.Windows.Application.Current == null)
            {
                new System.Windows.Application();

                Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
            }

            Application.Current.Run();
        });
        t.SetApartmentState(ApartmentState.STA);

        return t;
    }
}

You'll need references to PresentationCore, PresentationFramework and WindowsBase to build this example.

I hope it at least inspires someone.

EDIT: FYI, this may not work anymore... It worked when I posted it, now two days later it does not. There was a cumulative update for .NET Framework (kb4538122) installed yesterday, but I'm not sure if this was the breaking change or not.

EDIT: I updated the code, it now works again.

like image 43
rfmodulator Avatar answered Oct 22 '22 12:10

rfmodulator