Danno
Danno

Reputation: 199

DLL Trouble showing window from console app

I created a WPF application and then converted it into a DLL by removing the app.xaml and setting the build to Class Library. I'm using a C# Console application to test the DLL. For my first test i was able to get the application to show just fine if i put the mainwindow.show() in the try block underneath mainWindow = new MainWindow(). Now i need to be able to preload the wpf application and only display it when its needed instead of having to load it every time. The issue that i am having is that the call to show the wpf app is on a different thread and on the ShowWPFAppDLL() mainwindow is null. Any way I can get this to work?

Console Application:

namespace ConsoleApp
{
    class Program
    {
        static WPFAppDLL.LoadWpfAppDll loader = new WPFAppDLL.LoadWpfAppDll();

        static void Main(string[] args)
        {
            Thread worker = new Thread(new ThreadStart(LoadWpfApp));
            worker.SetApartmentState(ApartmentState.STA);
            worker.Name = "WpfThread";
            worker.IsBackground = true;
            worker.Start();

            Thread.Sleep(15000);
            ShowWpfApp();
            worker.Join();
        }

        private static void ShowWpfApp()
        {
            loader.ShowWPFAppDLL();
        }

        private static void LoadWpfApp()
        {
            loader.Load();
        }
    }
}

WPF Application (DLL):

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WPFAppDLL
{
    public class LoadWpfAppDll
    {
        MainWindow mainWindow = null;

        public void Load(string[] args)
        {
            Application application = new Application();

            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/DataTemplates/DataTemplate.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/GlobalStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/ImageResources.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/BrushesStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/TabControlStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/ButtonStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/LabelStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/TextboxStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/ComboBoxStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/DatagridStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/GroupBoxStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/CheckBoxStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/RadioButtonStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/Converters.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/ListBoxStyles.xaml", UriKind.RelativeOrAbsolute) });
            application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/WPFAppDLL;component/Resources/Styles/MessageBoxStyles.xaml", UriKind.RelativeOrAbsolute) });

            SplashScreenWindow splashWindow = new SplashScreenWindow();
            splashWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            splashWindow.Show();

            EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyIgnoreMouseButton));
            EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText));
            EventManager.RegisterClassHandler(typeof(TextBox), TextBox.MouseDoubleClickEvent, new RoutedEventHandler(SelectAllText));

            try
            {
                mainWindow = new MainWindow();
                mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                splashWindow.Close();
                application.Run();
            }
            catch (Exception ex)
            {
                splashWindow.Close();
                MessageBox.Show("Error starting application:" + Environment.NewLine + ex.ToString(), "Error Message", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public void ShowWPFAppDLL()
        {
            if (mainWindow != null)
            {
                mainWindow.Show();
            }
        }

        private void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
        {
            DependencyObject parent = e.OriginalSource as UIElement;
            while (parent != null && !(parent is TextBox))
            {
                parent = VisualTreeHelper.GetParent(parent);
            }

            if (parent != null)
            {
                TextBox textBox = (TextBox)parent;
                if (!textBox.IsKeyboardFocusWithin)
                {
                    textBox.Focus();
                    e.Handled = true;
                }
            }
        }

        private void SelectAllText(object sender, RoutedEventArgs e)
        {
            TextBox textBox = e.OriginalSource as TextBox;
            if (textBox != null)
            {
                textBox.SelectAll();
            }
        }
    }
}

Upvotes: 0

Views: 724

Answers (1)

user2819245
user2819245

Reputation:

You could implement a ManualResetEvent, which ShowWPFAppDLL is waiting for and which is set after the mainWindow is instantiated.

Also, you will have to make sure that whatever you do with mainWindow happens on the thread that is running the dispatcher responsible for mainWindow.

Your code could look similar to this:

public class LoadWpfAppDll
{
    private readonly ManualResetEvent _evtMainWindowInstantiated = new ManualResetEvent(false);

    private MainWindow mainWindow = null;


    public void Load(string[] args)
    {
        try
        {
            ......all the stuff you do in Load()...

            try
            {
                mainWindow = new MainWindow();
                mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                splashWindow.Close();

                application.Startup +=
                    (sender, e) => _evtMainWindowInstantiated.Set();

                application.Run();
            }
            catch (Exception ex)
            {
                splashWindow.Close();
                MessageBox.Show("Error starting application:" + Environment.NewLine + ex.ToString(), "Error Message", MessageBoxButton.OK, MessageBoxImage.Error);

                mainWindow = null;
            }
        }
        finally
        {
            //
            // Ensures that the _evtMainWindowInstantiated is always set, even in
            // failure case. If this would not be done, a failure of the Load
            // method could block other threads waiting on this ManualResetEvent
            // in one of the public methods below.
            //
            _evtMainWindowInstantiated.Set();
        }
    }


    private void InvokeOnMainWindowThread(Action action)
    {
        _evtMainWindowInstantiated.WaitOne();

        if (mainWindow == null)
        {
            ...something bad happened in Load()...
            ...do error handling or just return, whatever is appropriate...
        }

        //
        // Make sure that the action is invoked on the mainWindow thread.
        // If InvokeOnMainWindowThread is already called on the
        // mainWindow thread, the action should not be queued by the
        // dispatcher but should be executed immediately.
        //
        if (mainWindow.Dispatcher.CheckAccess()) action();
        else mainWindow.Dispatcher.Invoke(action, null);
    }


    public void ShowWPFAppDLL()
    {
        InvokeOnMainWindowThread(mainWindow.Show);
    }
}

Note that _evtMainWindowInstantiated is a ManualResetEvent which is never being resetted. Thus, once it is set in the Load() method _evtMainWindowInstantiated.WaitOne() will never block/wait again.

Also, i deliberately introduced a InvokeOnMainWindowThread method which takes care of handling _evtMainWindowInstantiated and executing actions on mainWindow's dispatcher thread. It pays off to have a dedicated method for this if you need to implement more than one public method like ShowWPFAppDLL.

Related to this, i made mainWindow private, since access to it needs to be managed by InvokeOnMainWindowThread. Allowing other classes access to mainWindow directly could lead to issues related to multi-threading or because Load() has not finished yet.

If you want to return some results from your public methods tinkering with mainWindow, you might implement an overload of InvokeOnMainWindowThread which takes a Func<T> delegate as an argument.

As a final note, i would suggest you let the WpfAppDll library also create and setup the thread for main window. It looks error-prone to me to let the "outside world" set up the thread which will run mainWindow's dispatcher (aka message loop).

Upvotes: 1

Related Questions