Search code examples
wpfcaliburn.microautocad

Caliburn micro in no Application object mode, like in AutoCAD dll plugin


I am using Caliburn Micro to develop WPF application. A few of views of this application needs to be loaded in an AutoCAD environment. The AutoCAD programming environment allows developement of AutoCAD plugins (of type dll) and load them into the AutoCAD environment.

Because of AutoCAD plugin type(dll), the plugin does not have an Application Object, so the bootstrapper has to be customized for that. According to the Caliburn Micro documentation here (Scroll down to "Using Caliburn.Micro in Office and WinForms Applications") we can inherit the non-generic bootstrapper and pass "false" to the base constructor's "useApplication" parameter. So, I went ahead and created the customized bootstrapper.

The issue is that the ConfigureContainer() override never gets called and nothing gets initialized. Also, I am not sure how to load the ShellView using ViewModel first concept. Here is some code that I have come up till now.

The Bootstrapper

public class AutocadMefBootStrapper : Bootstrapper {

    private CompositionContainer container;
    private ElementHost host;

    public AutocadMefBootStrapper(ElementHost host) : base(false) {
        this.host = host;
    }

    protected override void Configure() { //Not getting invoked.
        ...
        var rootViewModel = container.GetExportedValue<IShell>();
        var rootView = ViewLocator.LocateForModel(rootViewModel, null, null);
        host.Child = rootView;
    }
}

I have a windows form which the AutoCAD loads when requested. In the Windows Form's loaded event, I create an instance cuztomized caliburn micro bootstrapper and expect the boot strapper to do all the magic and load the Shell. But the Shell does not load. I get the blank window displayed in the AutoCAD. Here is how the Windows Form is coded.

public partial class WinFormHost : Form {

    private void WinFormHost_Load(object sender, EventArgs e) {
        ElementHost host = new ElementHost();
        host.Dock = DockStyle.Fill;
        Controls.Add(host);
        AutocadMefBootStrapper bootStrapper = new AutocadMefBootStrapper(host);
    }
}

Here is my ShellView

<UserControl x:Class="RelayAnalysis_Autocad.Views.ShellView"
    ...
    <Grid>
        <TextBlock>Hello There</TextBlock>
    </Grid>
</UserControl>

and the ShellViewModel

[Export(typeof(IShell))]
public class ShellViewModel : Conductor<object>, IShell {
    protected override void OnActivate() {
        base.OnActivate();
    }
}

So in summary, I am trying use Caliburn Micro in an hosted environment which is not loaded using the Application object. I am unable to configure Caliburn Micro, as the ShellView never loads.


Solution

  • This issue has been resolved. The problem was that loading of supporting assemblies (dll's) in AutoCAD itself was giving error. Please See this thread. Once the assemblies were loaded correctly, I could use the Caliburn Micro and it works in non-WPF environment too.

    Edit: I will logically show the process. The wpf screen that I had developed in a pure wpf application was to be re-used in AutoCAD plugin, but since autocad plugin's are class library(dll), there is no Application object available. When AutoCAD is launched, the plugin code executes where I could initialize the caliburn micro bootstrapper. Here is the relevant plugin code.

    MyPlugin.cs

    public class MyPlugin : IExtensionApplication {
    
        //Called when plugin is loaded. This is where I load xaml resources, since there is no App.xaml available
    
        void IExtensionApplication.Initialize() { 
             if (System.Windows.Application.Current == null) {
                new System.Windows.Application { ShutdownMode = ShutdownMode.OnExplicitShutdown }; 
            }
            System.Windows.Application.Current.Resources.MergedDictionaries.Add(System.Windows.Application.LoadComponent(
                    new Uri("RelayAnalysis_Autocad;component/Content/Styles/CommonBrushes.xaml", UriKind.Relative)) as ResourceDictionary);
            System.Windows.Application.Current.Resources.MergedDictionaries.Add(System.Windows.Application.LoadComponent(
                    new Uri("RelayAnalysis_Autocad;component/Content/Styles/Button.xaml", UriKind.Relative)) as ResourceDictionary);
        ...
            //Load Other xaml resources
        ...
            //Initialize the Bootstrapper
            AutocadMefBootStrapper bootstrapper = new AutocadMefBootStrapper();
        }
    
        //Called when plugin is unloaded
        void IExtensionApplication.Terminate() {
            // Do plug-in clean up here
            System.Windows.Application.Current.Shutdown();
        }
    

    Note that the Application has a shutdown mode of Explicit. This is required, although I don't remember why!

    There is not much difference in the Bootstrapper, except that we pass false to the base constructor as mentioned in documentation. Here is what the Bootstrapper looks like

    AutocadMefBootStrapper.cs

    public class AutocadMefBootStrapper : Bootstrapper {
    
        public static CompositionContainer container;
    
        public AutocadMefBootStrapper()
            : base(false) {
        }
    
        protected override void Configure() {
    
            //Create and Add Catalogs.
            AssemblyCatalog currentAssemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    
            AssemblyCatalog domainAssemblyCatalog =
                new AssemblyCatalog(Assembly.GetAssembly(typeof(RelayAnalysis_Domain.Entity.Rack)));
        ...
        }
    

    This is the configuration part which happens only once when the plugin is loaded the calibur micro is configured. After this the code is a bit related to AutoCAD but for the sake of completeness, I will share.

    QueryRelay.cs This class accepts command input from AutoCAD user and then displays the requested View

    public class QueryRelay {
    
        //This command is used to display a particular View. This is entered from AutoCAD Command Window
        public void QueryRelayCommand() {
    
            //Get the ViewModel for the screen from Container
            AcadRelayListViewModel relayListViewModel = AutocadMefBootStrapper.container.GetExportedValue<AcadRelayListViewModel>();
            IWindowManager windowManager = AutocadMefBootStrapper.container.GetExportedValue<IWindowManager>();
            windowManager.ShowWindow(relayListViewModel);
            ...
        }
    }
    

    Since the windows in AutoCAD are shown using the AutoCAD's API, I had to customize the Caliburn Micro WindowManager slightly. Here is the code for the CustomWindowManager

    CustomWindowManager.cs

    public class CustomWindowManager : WindowManager {
    
        public override void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null) {
            Autodesk.AutoCAD.ApplicationServices.Application.ShowModalWindow(null, CreateWindow(rootModel, false, null, null), false);
        }
    }
    

    I ask the CaliburnMicro to create View from ViewModel(rootModel in above code), which then is loaded in AutoCAD using the AutoCAD API. How the View is shown will depend on the hosting application (AutoCAD in my case).

    Finally the CustomWindowManager has to be registered in the BootStrapper Configure

        protected override void Configure() {
            ...
            var batch = new CompositionBatch();
            batch.AddExportedValue<IWindowManager>(new CustomWindowManager());
            container.Compose(batch);
            ...
        }
    

    regards, Nirvan