简体   繁体   English

从 Excel VSTO 和 Win Form 运行 WPF 应用程序

[英]Running WPF Application from Excel VSTO and Win Form

I have a WPF project using a lot of Resource Dictionaries and Entity Framework connected to a local database.我有一个 WPF 项目,它使用了很多连接到本地数据库的资源字典和实体框架。 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.现在,我正在尝试将此 WPF 项目连接到现有的 Excel VSTO 项目,并通过单击 Excel 功能区上的按钮运行 WPF 应用程序窗口。 I have modified App.xaml.cs as following:我修改了 App.xaml.cs 如下:

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.此外,我已经从 App.xaml 中删除了 StartupUri="MainWindow.xaml"。 On the Excel Ribbon I have a button that supposed to run the application:在 Excel 功能区上,我有一个应该运行应用程序的按钮:

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”.首先,当我单击按钮时,我在 MainWindow.xaml 的不同部分收到异常,如下所示:“在 'System.Windows.Baml2006.TypeConverterMarkupExtension' 上提供值抛出异常”。 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.我试图用一个空的 WPF 窗口重复相同的过程并且它有效,但现在我遇到了第二个问题。 When I click again on the button I get the exception;当我再次单击按钮时,出现异常; “Cannot create more than one System.Windows.Application instance in the same AppDomain.” “不能在同一个 AppDomain 中创建多个 System.Windows.Application 实例。”

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.我认为在一个大型项目中,通常有多个模块,每个模块作为一个 WPF 窗口,这些 WPF 窗口将从主应用程序 - Windows 窗体或 Office 功能区中调用。

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.编辑:我不想使用任务窗格和托管 WPF 控件。 I prefer to run WPF application as a separate window independent from Excel.我更喜欢将 WPF 应用程序作为独立于 Excel 的单独窗口运行。

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.首先,VSTO 项目应该引用 WPFproject.EXE 拥有的所有库——实体框架和 WPF 应用程序使用的所有 NuGet 包。 Also the Connection string should be also copied into Excel App.config.此外,连接字符串也应复制到 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. Excel 项目独立于 WPFproject.EXE,但看起来因为 Excel(或 Win 窗体应用程序)正在运行该节目,所以它必须具有所有引用。

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.”.当我在应用程序级别运行 WPF 项目时,它可以工作,但是当我再次单击该按钮时,我得到“无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例。”。 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.我的目标是每隔一次单击将 WPF 窗口置于最前面,或者在此期间关闭 WPF 应用程序时再次打开它。

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.我通过调用 MainWindow 而不是 App 找到了解决上述问题的方法。 It works when I tested a simple WPF window, but it doesn't open my original project:当我测试一个简单的 WPF 窗口时它可以工作,但它不会打开我的原始项目:

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.这两种解决方案都来自上面提供的链接 Peter Schneider。

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!谢谢!

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.在 vsto 项目中,如果还没有应用程序,我会初始化一个应用程序。 By initialize application's compoent, the resousece dictionary in app.xaml can be load.通过初始化应用程序的组件,可以加载 app.xaml 中的 resousece 字典。 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.我还将应用程序的shutdownmode 更改为explicitshutdown,以便在主窗口关闭后,我不需要再次初始化此应用程序,因为主窗口可能会反复使用。 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().如果它确实有一个主窗口,只需显示它并通过 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.在wpf项目中,我去掉了app.xaml中的启动uri并覆盖了OnStartup()来手动设置启动uri。 If the wpf project is the startup project (as in debugging wpf), set the starturi to where the mainwindow locates.如果wpf项目是启动项目(如调试wpf),将starturi设置为mainwindow所在的位置。 This is done by checking GetEntryAssembly(), as if the vsto is set as the startup project, there's no entry assembly.这是通过检查 GetEntryAssembly() 来完成的,就像将 vsto 设置为启动项目一样,没有入口程序集。

        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.使用上述方法仍然存在的问题是,当第一次显示窗口时,双击excel单元格时鼠标点会显示为忙。 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.我认为这与调度程序有关,因为当前的焦点在第一次单击时在 wpf 窗口上,而在第二次单击时切换到 excel。 I regard this as XXX problem because I couldn't found a better way.我认为这是 XXX 问题,因为我找不到更好的方法。

Use Wpf project as Class Library project instead of Windows Application .使用 Wpf 项目作为类库项目而不是Windows 应用程序 To do this, goto Properties of Wpf project->Application->Output Type->Class Library And also delete App.xaml file.为此,请转到 Wpf 项目的属性->应用程序->输出类型->类库,同时删除 App.xaml 文件。

Now you can use this project as Class Library project.现在您可以将此项目用作类库项目。

Now instead of using AppDomain you can show wpf window like below现在,您可以显示 wpf 窗口,而不是使用 AppDomain,如下所示

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

But above code don't let you edit in wpf window TextBox.但是上面的代码不允许您在 wpf 窗口 TextBox 中进行编辑。 For that you need to call MainWindow like following为此,您需要像下面这样调用 MainWindow

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>    

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM