I have created an office add-in that holds an instance of a WPF Application. When the user clicks buttons on the add-in, I launch different windows by doing the following:
MyViewModel viewModel = new MyViewModel(string infoFromOffice);
MyWindow view = new MyWindow();
view.DataContext = viewModel;
wpfApp.Run(view);
In constructing view models before my call to wpfApp.Run()
I hit probelms with the current SynchronizationContext later on. The answer here explains why. Is there a better way of launching WPF windows from an office add-in?
I've never created an Office add-in, but I have used WPF windows in other kinds of non-WPF applications (Windows Forms, libraries to generate .XPS files from WPF visuals, etc). You could try the approach I suggested in this question.. It shows how to configure a thread so it will be able to run the WPF app.
If you take a look on the generated app's code ("App.g.i.cs") of a WPF app, it seems it is started like this:
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static void Main() {
WpfApplication1.App app = new WpfApplication1.App();
app.InitializeComponent();
app.Run();
}
I tried launching an app from a unit test with the following code and it worked well:
[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();
}
EDIT
Looking at your code I believe that the statement sensible to the SynchronizationContext is the creation of the Window instance, not the creation of your ViewModel (unless your ViewModel deals with View logic and instantiates controls, something it shouldn't be doing). So you can try to move the instantiation of the Window to the thread of the App. Something like this:
[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();
}
While Arthur's answer helped point to why the problem was happening, it did not actually answer how to pass data to a view model from a host application while still having the view model constructor call be after the call to App.Run()
. I have since found a (very simple) solution! For anyone who is interested.
In 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();
}
When launching the app:
App application = new App(infoFromOffice);
application.Run();
Note that the startup URI needs to be removed in App.xaml. This very simple solution allows me to pass information to my app, but at the same time does not require that the view model be constructed in a "non WPF environment" and so can make use of the Dispatcher etc.
Here is a version using application domain to open the wpf application multiple times under separate application domains and UI thread. Used this in an office addin. Each time startup is called you get a new application. Have not verified how well the threads shutdown when the wpf app is closed.
http://eprystupa.wordpress.com/2008/07/31/running-multiple-wpf-applications-in-the-same-process-using-appdomains/
public class 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);
}
}