One of my colleagues needed to modernize a tool we had for Excel. He asked me for suggestions, since he knew I’m into modern UI development. I know very little about the Excel API but after some googling I found out that you are limited to Winforms. There is however a control in Winforms called ElementHost that let you host WPF elements in Winforms. This coupled with a custom Bootstrapper for CM will enable you to develop sleek MVVM tools in Excel.
The Boostrapper will act as an entry point for our Caliburn enabled app. It needs to inherit from BootstrapperBase rather than the standard generic base class Bootstrapper of TModel.
public class Bootstrapper<TModel> : BootstrapperBase { private readonly StandardKernel kernel; public Bootstrapper(ElementHost host) : base(false) { kernel = new StandardKernel(); Start(); var model = kernel.Get<TModel>(); var view = ViewLocator.LocateForModel(model, null, null); ViewModelBinder.Bind(model, view, null); host.Child = view; } protected override void Configure() { kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope(); } protected override object GetInstance(Type service, string key) { return kernel.Get(service); } protected override IEnumerable<object> GetAllInstances(Type service) { return kernel.GetAll(service); } }
Its constructor takes the ElementHost as argument and then starts CM using the Start method on the base class. We need to hook up the ViewModel manually and its done like.
var model = kernel.Get<TModel>(); var view = ViewLocator.LocateForModel(model, null, null); ViewModelBinder.Bind(model, view, null);
First we create the ViewModel using Ninject, this way any dependecies for that model will be injected by Ninject. Then we ask CM to locate the WPF view using its convention based helper class ViewLocator. After we get the view we Bind it to the model using another CM helper class called ViewModelBinder.
Last but not least, we need to hook up the WPF element to the Winforms ElementHost.
host.Child = view;
Embedded resources
Alan Rutter asked me how to load embedded resources like themes etc.
App.xaml wont load in a none WPF project so you need to-do this manually like
var dict = new ResourceDictionary {Source = new Uri("Resources.xaml", UriKind.Relative)}; var app = new System.Windows.Application(); //This will load Application.Current Application.Current.Resources.MergedDictionaries.Add(dict);
Is the model you load a Window or a UserControl? Additionally do you have an App.xaml file to load your application wide resources. I am trying a similar thing and cannot get the App.xaml file resources to be seen.
Its neither, it can be of any object type but because of a bug in WPF I recommend you inherit from Caliburn.Micro.PropertyChangedBase. It will ensure that WPF data bind without memoryleaks and you also benefit from getting strongly typed PropertyChanged notification (NotifyOfPropertyChange). About the App.xaml problem I must admit I never needed it because we used the default theme etc. I will try and see what I can find out about that. Just a note you do not need the App.xaml for Caliburn.Micro to work. Thats what my custom bootstrapper is there for,.
Alan, please see updated blog
Thanks for your informative answers and good article. I was comparing your solution with a different article http://www.dzikiewicz.pl/blog/wpf-vsto which has a slightly different method but only shows how to launch a dialog and not how to use the document pane. I’ve combined the info with your solution. I instantiate an App instance and from my testing it appears that the styles in the App.xaml do appear to be applied to other pages without a problem. Due to a bug in WPF under .NET 4.0 though I had to place a dummy style tag in the resources section within App.xaml otherwise the rest of the resources are not loaded.
I’ve also just discovered a WinFormsInterOp sample (https://github.com/Caliburn-Micro/Caliburn.Micro/tree/master/samples/Caliburn.Micro.WinFormsInterop) that appears to be a recent addition. This shows the basics as well as how to get the Activate/Deactivate events etc to fire.