1
votes

I have a WPF project using a lot of Resource Dictionaries and Entity Framework connected to a local database. Everything is working fine when I am testing the project in a separate Solution.

Now, I am trying to connect this WPF project to an existing Excel VSTO project and run the WPF application window by clicking on a button on Excel Ribbon. I have modified App.xaml.cs as following:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        MainWindow view = new MainWindow();
        view.Show();
    }
}

Also, I’ve removed the StartupUri="MainWindow.xaml" from App.xaml. On the Excel Ribbon I have a button that supposed to run the application:

private void button1_Click(object sender, RibbonControlEventArgs e)
{
    App application = new App();
    application.Run();
}

Now I have two separate problems:

First, when I click the button I get exceptions on a different parts of the MainWindow.xaml like this: “Provide value on 'System.Windows.Baml2006.TypeConverterMarkupExtension' threw an exception”. Most likely the connection to the resources is missing.

<ResourceDictionary>
   <vm:ViewModelLocator x:Key="Locator"/>
   <ResourceDictionary.MergedDictionaries>
     <ResourceDictionary Source="Dictionaries\DataGridDictionary.xaml" />
     <ResourceDictionary Source="Dictionaries\DarkTheme.xaml" />
     <ResourceDictionary Source="Dictionaries\WindowStyle.xaml" />
   </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

I have tried to repeat the same procedure with an empty WPF window and it works, but now I am running to a second issue. When I click again on the button I get the exception; “Cannot create more than one System.Windows.Application instance in the same AppDomain.”

I think in a large project it is normal to have several modules, each one as a WPF window and these WPF windows to be called from the Main application - Windows form or Office Ribbon.

Could you please suggest how to fix both issues above? Thank you!

Edit: I don’t want to use Task Pane and hosting WPF controls. I prefer to run WPF application as a separate window independent from Excel.

3
Have you seen this ?Peter Schneider
Thanks for the comment. It partially works, but it doesn’t fix both issues.v31

3 Answers

2
votes

I have found solutions for both issues, but when I fix the first one the second one doesn’t work and vice versa.

First thing is that the VSTO project should have references to all libraries that WPFproject.EXE has – Entity Framework and all NuGet’s packages used by the WPF application. Also the Connection string should be also copied into Excel App.config. Excel project is independent from WPFproject.EXE, but it looks like because Excel (or Win form app) is running the show, it must have all references.

When I run the WPF project on the application level it works, but when I click on the button again I've got “Cannot create more than one System.Windows.Application instance in the same AppDomain.”. My goal is every other click to bring the WPF window to the front, or to open the WPF application again if it is closed in the meantime.

private void button1_Click(object sender, RibbonControlEventArgs e)
{
    var t = new Thread(() =>
    {
        var app = new App();
        App.ResourceAssembly = app.GetType().Assembly;
        app.InitializeComponent();
        System.Windows.Threading.Dispatcher.Run();
    });

    t.SetApartmentState(ApartmentState.STA);
    t.Start();
}

I have found a solution of the problem above by calling the MainWindow instead of App. It works when I tested a simple WPF window, but it doesn’t open my original project:

private void button1_Click(object sender, RibbonControlEventArgs e)
{
   var t = new Thread(() =>
   {
       MainWindow window = new MainWindow();
       window.Closed += (s2, e2) => window.Dispatcher.InvokeShutdown();
       window.Show();
       System.Windows.Threading.Dispatcher.Run();
   });

   t.SetApartmentState(ApartmentState.STA);
   t.Start();
}   

Both solutions are from the link Peter Schneider provided above.

What is the difference between both approaches? Understanding the mechanism probably will help me to fix the issues. Could you please suggest where I can find more information on this topic?

Thanks!

1
votes

I've encounter the same issue as you, but still not found a satisfactory way to solve it. However, this is what I used in my code.

In vsto project, I initialize a application if there's no application yet. By initialize application's compoent, the resousece dictionary in app.xaml can be load. I also change the app's shutdownmode to explicitshutdown, so that after mainwindow closed, I don't need to initialize this application again consider the mainwindow may be used over and over. Everty time the button click, I will check if the mainwindow has been initialized before. If it does have a mainwindow, just show it and bring it to front by Activate().

private void button1_Click(object sender, RibbonControlEventArgs e)
{
            // if it's the first time run, initialize an application so the resourcedictionary is loaded by initializeComponent.
            if (System.Windows.Application.Current == null)
            {
                // initialize an wpf application and load its resources dictionaries and take control of app's shutdown
                _app = new App {ShutdownMode = ShutdownMode.OnExplicitShutdown};

                _app.InitializeComponent();
            }

            // initialize mainwindow if it's the first time to call this
            if (_app.MainWindow == null)
            {
                _app.MainWindow = new MainWindow();
                _app.MainWindow.Closing += (s1, e) => { Dispatcher.ExitAllFrames();};
            }

            // bring main window to front 
            _app.MainWindow.Show();
            _app.MainWindow.Activate();

            Dispatcher.Run();
}   

In wpf project, I removed the startup uri in app.xaml and overwrite OnStartup() to set startup uri by hand. If the wpf project is the startup project (as in debugging wpf), set the starturi to where the mainwindow locates. This is done by checking GetEntryAssembly(), as if the vsto is set as the startup project, there's no entry assembly.

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            // if current assembly is the entry point, set the startup uri to main window.
            if (Assembly.GetEntryAssembly() == GetType().Assembly)
                StartupUri = new Uri("Windows/MainWindow.xaml", UriKind.Relative);
        }

The promblem remains by using above method is that, when the fisrt time the window is shown, the mouse point will appear as busy when double click the excel cell. However, this will not appear after click the second, third or any other cell. I think it's related to dispatcher, because the current focus is on the wpf window on first click, and switch to excel on second click. I regard this as XXX problem because I couldn't found a better way.

0
votes

Use Wpf project as Class Library project instead of Windows Application. To do this, goto Properties of Wpf project->Application->Output Type->Class Library And also delete App.xaml file.

Now you can use this project as Class Library project.

Now instead of using AppDomain you can show wpf window like below

MainWindow window = new MainWindow();
window.Show();

But above code don't let you edit in wpf window TextBox. For that you need to call MainWindow like following

MainWindow window = new MainWindow();
window.ShowDialog();

When you add any resource dictionary then do like below

<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary x:Key="dataDict" Source="Dictionaries\DataGridDictionary.xaml"/>
    <ResourceDictionary x:Key="darkThemeDict" Source="Dictionaries\DarkTheme.xaml" />
    <ResourceDictionary x:Key="winStyleDict" Source="Dictionaries\WindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>