繁体   English   中英

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

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

我有一个 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 的不同部分收到异常,如下所示:“在 'System.Windows.Baml2006.TypeConverterMarkupExtension' 上提供值抛出异常”。 很可能缺少与资源的连接。

<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 的单独窗口运行。

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

首先,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。

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

谢谢!

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

在 vsto 项目中,如果还没有应用程序,我会初始化一个应用程序。 通过初始化应用程序的组件,可以加载 app.xaml 中的 resousece 字典。 我还将应用程序的shutdownmode 更改为explicitshutdown,以便在主窗口关闭后,我不需要再次初始化此应用程序,因为主窗口可能会反复使用。 每次单击按钮时,我都会检查主窗口之前是否已初始化。 如果它确实有一个主窗口,只需显示它并通过 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设置为mainwindow所在的位置。 这是通过检查 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 问题,因为我找不到更好的方法。

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

暂无
暂无

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

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