How to use Caliburn Micro in a WinForms application with a single WPF form

We have a (massive) legacy WinForms application that opens a WPF form through a menu item. This WPF form will contain an Infragistics grid and some buttons / dropdowns.

This single form of WPF represents the nascent phase of the transition to WPF. Later, more application components will move to WPF and, ultimately, to the entire application.

As part of the migration, we would like to use Caliburn Micro. Therefore, it would be nice if we could start by using this single WPF form.

  • Can someone please provide some recommendations on how to use Caliburn Micro with a WPF form?
  • Or maybe tell me why it still doesn't make sense to use Caliburn Micro?

The documentation I have read so far includes bootstraps that ensure that the application starts with the correct root view model, and not the script above.

Many thanks!

+4
source share
3 answers

After a lot of Googling and going through the Caliburn Micro source code, I came up with an approach that works in a test application. I can’t publish a test application here for certain reasons, but here is the approach in a nutshell.

  • Create a WinForm using the button.
  • By clicking the button, show ChildWinForm
  • In the ChildWinForm download handler:

    // You'll need to reference WindowsFormsIntegration for the ElementHost class // ElementHost acts as the "intermediary" between WinForms and WPF once its Child // property is set to the WPF control. This is done in the Bootstrapper below. var elementHost = new ElementHost{Dock = DockStyle.Fill}; Controls.Add(elementHost); new WpfControlViewBootstrapper(elementHost); 
  • The bootloader above is what you need to write.

  • For more information on everything you need to do, see Configure Bootstrapper from the Caliburn Micro documentation .
  • For the purpose of this post, do it from the Caliburn Bootstrapper class.
  • In his constructor, he should do the following:

     // Since this is a WinForms app with some WPF controls, there is no Application. // Supplying false in the base prevents Caliburn Micro from looking // for the Application and hooking up to Application.Startup protected WinFormsBootstrapper(ElementHost elementHost) : base(false) { // container is your preferred DI container var rootViewModel = container.Resolve(); // ViewLocator is a Caliburn class for mapping views to view models var rootView = ViewLocator.LocateForModel(rootViewModel, null, null); // Set elementHost child as mentioned earlier elementHost.Child = rootView; } 
  • The last thing to note is to set the cal: Bind.Model property of the dependency in the XAML WpfControlView.

     cal:Bind.Model="WpfControls.ViewModels.WpfControl1ViewModel" 
  • The value of the dependency property is used as passed as a string in Bootstrapper.GetInstance (Type serviceType, string key), which then should use it to resolve WpfControlViewModel.

  • Since the container I use (Autofac) does not support row-only resolution, I decided to set the property to the fully qualified name of the view model. This name can then be converted to a type and used to resolve from the container.
+5
source

Following the accepted answer (good!), I would like to show you how to implement WinStorms Bootstrapper in the first ViewModel, such that:

  • You will not need to create a WPF window and
  • You do not need to directly contact the ViewModel from the view.

To do this, we need to create our own version of WindowManager, make sure that we do not call the Show method in the window (if applicable to your case) and enable the binding.

Here is the complete code:

 public class WinformsCaliburnBootstrapper<TViewModel> : BootstrapperBase where TViewModel : class { private UserControl rootView; public WinformsCaliburnBootstrapper(ElementHost host) : base(false) { this.rootView = new UserControl(); rootView.Loaded += rootView_Loaded; host.Child = this.rootView; Start(); } void rootView_Loaded(object sender, RoutedEventArgs e) { DisplayRootViewFor<TViewModel>(); } protected override object GetInstance(Type service, string key) { if (service == typeof(IWindowManager)) { service = typeof(UserControlWindowManager<TViewModel>); return new UserControlWindowManager<TViewModel>(rootView); } return Activator.CreateInstance(service); } private class UserControlWindowManager<TViewModel> : WindowManager where TViewModel : class { UserControl rootView; public UserControlWindowManager(UserControl rootView) { this.rootView = rootView; } protected override Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings) { if (isDialog) //allow normal behavior for dialog windows. return base.CreateWindow(rootModel, isDialog, context, settings); rootView.Content = ViewLocator.LocateForModel(rootModel, null, context); rootView.SetValue(View.IsGeneratedProperty, true); ViewModelBinder.Bind(rootModel, rootView, context); return null; } public override void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null) { CreateWindow(rootModel, false, context, settings); //.Show(); omitted on purpose } } } 

I hope this helps someone with the same needs. It certainly saved me.

+2
source

Here's what you can start with

  • Create ViewModels and inherit them from the PropertyChangedBase class provided by the CM environment.
  • Use Impagmentation EventAggregator if necessary for loosely coupled communications \ integration
  • Deploying an AppBootStrapper without a common implementation that defines the root view model.

Now you can use the first view approach and bind the view to the model using the attached Bind.Model property in the view. I created a sample application to describe the approach here .

+1
source

Source: https://habr.com/ru/post/1394085/


All Articles