【问题标题】:Running WPF Application from Excel VSTO and Win Form从 Excel VSTO 和 Win Form 运行 WPF 应用程序
【发布时间】:2017-05-24 18:03:51
【问题描述】:

我有一个 WPF 项目,它使用大量连接到本地数据库的资源字典和实体框架。当我在单独的解决方案中测试项目时,一切正常。

现在,我正在尝试将此 WPF 项目连接到现有的 Excel VSTO 项目,并通过单击 Excel 功能区上的按钮来运行 WPF 应用程序窗口。 我已将 App.xaml.cs 修改如下:

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

另外,我已经从 App.xaml 中删除了 StartupUri="MainWindow.xaml"。在 Excel 功能区上,我有一个应该运行应用程序的按钮:

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

现在我有两个不同的问题:

首先,当我单击按钮时,MainWindow.xaml 的不同部分出现异常,如下所示:“Provide value on 'System.Windows.Baml2006.TypeConverterMarkupExtension' throw an exception”。很可能缺少与资源的连接。

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

我试图用一个空的 WPF 窗口重复相同的过程,它可以工作,但现在我遇到了第二个问题。当我再次单击按钮时,我得到了异常; “不能在同一个 AppDomain 中创建多个 System.Windows.Application 实例。”

我认为在一个大型项目中,有几个模块是正常的,每个模块都是一个 WPF 窗口,这些 WPF 窗口可以从主应用程序(Windows 窗体或 Office 功能区)调用。

您能否建议如何解决上述两个问题? 谢谢!

编辑:我不想使用任务窗格和托管 WPF 控件。我更喜欢将 WPF 应用程序作为独立于 Excel 的单独窗口运行。

【问题讨论】:

  • 你见过this吗?
  • 感谢您的评论。它部分有效,但不能解决这两个问题。

标签: c# .net wpf excel xaml


【解决方案1】:

我已经找到了解决这两个问题的方法,但是当我修复第一个问题时,第二个问题不起作用,反之亦然。

首先,VSTO 项目应该引用 WPFproject.EXE 拥有的所有库——实体框架和 WPF 应用程序使用的所有 NuGet 包。此外,还应将连接字符串复制到 Excel App.config 中。 Excel 项目独立于 WPFproject.EXE,但看起来因为 Excel(或 Win 表单应用程序)正在运行显示,它必须具有所有引用。

当我在应用程序级别运行 WPF 项目时,它可以工作,但是当我再次单击该按钮时,我得到“无法在同一个 AppDomain 中创建多个 System.Windows.Application 实例。”。我的目标是每隔一次单击将 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();
}

我通过调用 MainWindow 而不是 App 找到了上述问题的解决方案。当我测试一个简单的 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();
}   

这两种解决方案都来自上面提供的 Peter Schneider 链接。

这两种方法有什么区别?了解机制可能会帮助我解决问题。您能否建议我在哪里可以找到有关此主题的更多信息?

谢谢!

【讨论】:

    【解决方案2】:

    我也遇到了和你一样的问题,但是还没有找到满意的解决方法。但是,这是我在代码中使用的。

    在 vsto 项目中,如果还没有应用程序,我会初始化一个应用程序。通过初始化应用程序的组件,可以加载 app.xaml 中的资源字典。我还将应用程序的关闭模式更改为显式关闭,以便在主窗口关闭后,我不需要再次初始化此应用程序,因为主窗口可能会反复使用。 每次单击按钮时,我都会检查主窗口之前是否已初始化。如果它确实有一个主窗口,只需显示它并通过 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();
    }   
    

    在 wpf 项目中,我删除了 app.xaml 中的启动 uri 并覆盖 OnStartup() 以手动设置启动 uri。如果 wpf 项目是启动项目(如调试 wpf 中),请将 starturi 设置为主窗口所在的位置。这是通过检查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);
            }
    

    使用上述方法存在的问题是,第一次显示窗口时,双击excel单元格时鼠标点会显示为忙。但是,单击第二个、第三个或任何其他单元格后,这不会出现。我认为它与调度程序有关,因为当前焦点在第一次单击时在 wpf 窗口上,并在第二次单击时切换到 excel。我认为这是 XXX 问题,因为我找不到更好的方法。

    【讨论】:

      【解决方案3】:

      使用 Wpf 项目作为 类库 项目而不是 Windows 应用程序。 为此,请转到 Wpf 项目的属性-> 应用程序-> 输出类型-> 类库 并删除 App.xaml 文件。

      现在您可以将此项目用作类库项目。

      现在您可以显示 wpf 窗口,而不是使用 AppDomain,如下所示

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

      但上面的代码不允许您在 wpf 窗口 TextBox 中进行编辑。 为此,您需要像以下一样调用 MainWindow

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

      当您添加任何资源字典时,请执行以下操作

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-08
        • 1970-01-01
        • 2013-01-15
        • 1970-01-01
        • 1970-01-01
        • 2013-02-28
        • 2014-05-24
        • 2017-09-26
        相关资源
        最近更新 更多