从Office插件启动WPF窗口

8

我创建了一个办公插件,其中包含一个 WPF 应用程序的实例。当用户单击插件上的按钮时,我通过以下方式打开不同的窗口:

MyViewModel viewModel = new MyViewModel(string infoFromOffice);
MyWindow view = new MyWindow();
view.DataContext = viewModel;

wpfApp.Run(view);

在调用wpfApp.Run()之前构建视图模型时,我遇到了当前同步上下文的问题。这里的答案解释了原因。有没有更好的方法从Office插件中启动WPF窗口?


1
出于好奇,调用wpfApp.Run(new MyWindow { DataContext = new MyViewModel(infoFromOffice) });会有什么不同吗? - Jay
谢谢您的建议,不幸的是这并不起作用。 - Coder1095
3个回答

1
我从未创建过Office插件,但我在其他类型的非WPF应用程序(Windows Forms,用于从WPF可视化生成.XPS文件的库等)中使用了WPF窗口。您可以尝试我在this question.中提出的方法。它展示了如何配置线程以便能够运行WPF应用程序。 如果您查看WPF应用程序的生成代码(“App.g.i.cs”),它似乎是这样启动的:
/// <summary>
    /// Application Entry Point.
    /// </summary>
    [System.STAThreadAttribute()]
    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public static void Main() {
        WpfApplication1.App app = new WpfApplication1.App();
        app.InitializeComponent();
        app.Run();
    }

我尝试使用以下代码从单元测试启动应用程序,并且它运行良好:
[TestMethod]
    public void TestMethod()
    {
        // The dispatcher thread
        var t = new Thread(() =>
        {
            var app = new App();
            // Corrects the error "System.IO.IOException: Assembly.GetEntryAssembly() returns null..."
            App.ResourceAssembly = app.GetType().Assembly;
            app.InitializeComponent();

            app.Run();
        });

        // Configure the thread
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
        t.Join();
    }

编辑

从您的代码来看,我认为受同步上下文影响的语句是创建窗口实例,而不是创建 ViewModel(除非您的 ViewModel 处理视图逻辑并实例化控件,这是不应该做的)。因此,您可以尝试将窗口的实例化移动到应用程序的线程中。类似于这样:

[TestMethod]
    public void TestMethod3()
    {
        // Creates the viewmodel with the necessary infomation wherever 
                    // you need to.
        MyViewModel viewModel = new MyViewModel(string infoFromOffice);

        // The dispatcher thread
        var t = new Thread(() =>
        {
            var app = new App();
            // Corrects the error "System.IO.IOException: Assembly.GetEntryAssembly() returns null..."
            App.ResourceAssembly = app.GetType().Assembly;
            app.InitializeComponent();

            // Creates the Window in the App's Thread and pass the information to it
            MyWindow view = new MyWindow();
            view.DataContext = viewModel;

            app.Run(view);
        });

        // Configure the thread
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
        t.Join();
    }

谢谢,这是一个有用的例子,但并没有解决问题。在这里,视图模型是在调用app.Run()之后创建的。由于我需要传递一些信息给我的视图模型,它必须在调用app.Run()之前实例化。这意味着在构建视图模型时没有调度程序,这会导致一些问题,特别是无法获取SynchronizationContext.Current。 - Coder1095
我相信问题在于窗口的实例化。请查阅编辑后的答案。 - Arthur Nunes
“除非您的ViewModel处理视图逻辑并实例化控件,否则它不应该这样做。”我认为这是关键。我的程序使用后台线程(任务)查询数据库,然后通过UI线程更新UI。如果我在视图模型构造函数中使用其中一个任务(在调用app.Run()之前),它会失败,因为SynchronizationContext.Current为空。我想解决方法就是不要在视图模型构造函数中执行数据库查询。感谢您帮助我解决这个问题。 - Coder1095

0
虽然Arthur的回答帮助指出了问题发生的原因,但实际上没有回答如何在调用App.Run()之后仍然从主机应用程序传递数据到视图模型的构造函数。我后来找到了一个(非常简单的)解决方案!对于任何感兴趣的人。

在App.xaml.cs中:

private string data;

public App(string infoFromOffice) {
    this.data = data;
}

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

    MyViewModel viewwModel = new MyViewModel(this.data);
    MyWindow view = new MyWindow();
    view.Show();
}

启动应用程序时:

App application = new App(infoFromOffice);
application.Run();

请注意,在 App.xaml 中需要删除启动 URI。这个非常简单的解决方案允许我向我的应用程序传递信息,但同时不要求在“非 WPF 环境”中构建视图模型,因此可以利用 Dispatcher 等功能。

0
这里有一个版本,使用应用程序域在单独的应用程序域和UI线程下多次打开wpf应用程序。在办公室AddIn中使用了这个版本。每次调用启动时,您都会获得一个新的应用程序。尚未验证WPF应用程序关闭时线程的关闭情况。

http://eprystupa.wordpress.com/2008/07/31/running-multiple-wpf-applications-in-the-same-process-using-appdomains/

公共类 WpfHelper {

   public static void Startup()
    {
        var appDomainSetup = new AppDomainSetup()
        {
            ApplicationBase = Path.GetDirectoryName(typeof(WpfHelper).GetType().Assembly.Location)
        };

        AppDomain domain = AppDomain.CreateDomain(DateTime.Now.ToString(), null, appDomainSetup);

        CrossAppDomainDelegate action = () =>
        {
            Thread thread = new Thread(() =>
            {
                var app = new WpfApplication.App();

                WpfApplication.App.ResourceAssembly = app.GetType().Assembly;

                app.MainWindow = new WpfApplication.MainWindow();
                app.MainWindow.Show();
                app.Run();
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();

        };

        domain.DoCallBack(action);
    }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接