【问题标题】:Launching WPF windows from an Office add in从 Office 加载项启动 WPF 窗口
【发布时间】:2012-11-16 14:59:29
【问题描述】:

我创建了一个包含 WPF 应用程序实例的 Office 插件。当用户单击加载项上的按钮时,我通过执行以下操作来启动不同的窗口:

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

wpfApp.Run(view);

在调用wpfApp.Run() 之前构建视图模型时,我稍后会使用当前的 SynchronizationContext 来解决问题。答案here 解释了原因。有没有更好的方法从 Office 插件启动 WPF 窗口?

【问题讨论】:

  • 出于好奇,打电话给wpfApp.Run(new MyWindow { DataContext = new MyViewModel(infoFromOffice) });有什么区别
  • 感谢您的建议,不幸的是这不起作用。

标签: c# wpf mvvm ms-office office-addins


【解决方案1】:

我从未创建过 Office 加载项,但我在其他类型的非 WPF 应用程序(Windows 窗体、从 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();
    }

编辑

查看您的代码,我认为对 SynchronizationContext 有意义的语句是创建 Window 实例,而不是创建您的 ViewModel(除非您的 ViewModel 处理 View 逻辑并实例化控件,否则它不应该这样做) .所以你可以尝试将Window的实例化移动到App的线程中。像这样的:

[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 的问题。
  • 我相信问题出在窗口的实例化上。请参阅编辑后的答案。
  • “除非您的 ViewModel 处理视图逻辑并实例化控件,否则它不应该这样做”。我认为这是这里的关键。我的程序使用后台线程(任务)来查询数据库,然后通过 UI 线程更新 UI。如果我在视图模型构造函数中使用这些任务之一(在调用 app.Run() 之前)它会失败,因为 SynchronizationContext.Current 为空。我想修复只是不在视图模型构造函数中执行数据库查询。感谢您帮助我。
【解决方案2】:

虽然 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 等。

【讨论】:

    【解决方案3】:

    这是一个使用应用程序域在不同的应用程序域和 UI 线程下多次打开 wpf 应用程序的版本。在办公室插件中使用它。每次调用启动时,您都会获得一个新应用程序。尚未验证 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);
        }
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      • 1970-01-01
      • 1970-01-01
      • 2018-05-02
      相关资源
      最近更新 更多