Main Caliburn Window -> How do I get a reference to it

Topics: Bootstrappers & IoC, UI Architecture
Sep 14, 2013 at 12:26 AM
I am working on a class library and I am using the BaseBootstrapper. Everything works fine except for when a library I am using tries to set a shortcut:
SetShortcut(Application.Current.MainWindow, gesture, handler);
There is no Application object since my code is running in a library. However there is a main window, caliburn is generating it and from all my searching it is IMPOSSIBLE to do something like the following:

SetShortcut(IoC.Get<MainWindow>(), gesture, handler);

Why is there no way to get the main window in a Caliburn project using the BaseBootstrapper?
Sep 16, 2013 at 12:26 PM
Even if your code is deployed as a library, if main application is a WPF application, the Application object should exist.
What kind of project is referencing your library?
Sep 19, 2013 at 2:41 AM
It is an AutoCAD addin. There is no Application object as the class library is being "hosted" in the AutoCAD application.

This is causing untold nightmares trying to get anything WPF working since most things seem to be tied to the Application object. I have yet to get themes working since loading the theme into a ResourceDictionary causes exceptions to be thrown with any custom controls (ToolBarEx is an extension of the ToolBar and when parsing the XAML in the theme it will say it failed to create/find this)

I tried searching but I could not find much on how to get WPF working with a class library that does not have an Application object.
Sep 20, 2013 at 9:31 AM
I see. AutoCAD probably works like Visual Studio: it hosts a .NET framework environment inside a native application. In this case, Application.Current can be null.

Now, issue about not having an Application object, can be resolved creating a singleton Application in your addin. See this SO thread for more information.

To retrieve the main window, since I am not sure if the created Application object will be able to reference the actual process windows correctly, I suppose you can use P/Invoke directly (have a look to this list of functions), or retrieve the current Process and transform the MainWindow handle into an actual window, as explained in this SO thread (see answer #2).

On a final note: I supose AutoCAD addins live in the same AppDomain as the main application, otherwise it can be at least trickier...
Sep 21, 2013 at 6:23 PM
I was trying to create a singleton Application object but I wanted to assign the Application.MainWindow to the Caliburn created MainWIndow. I can create an application object and a new Window and assign this to the Application.MainWindow but this of course is not the Caliburn window with my addin running in it. I can even show this window but it is blank.

I was looking for a way to "retrieve" the Caliburn generated window so I can attach this, but this is where I ran into trouble, I could not find any way to do this.

So currently I chopped out all the code that deals with the Application object but I would really like to add in some code that deals with themes.

The exception that I get is:
{"Type reference cannot find type named '{clr-namespace:Gemini.Modules.ToolBars.Controls}ToolBarEx'."}

The Xaml File:
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:mainmenu="clr-namespace:Gemini.Modules.MainMenu.Controls"
    xmlns:toolbars="clr-namespace:Gemini.Modules.ToolBars.Controls"
    xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Brushes.xaml" />
        <ResourceDictionary Source="StatusBar.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style TargetType="{x:Type ContextMenu}">
        <!--  BasedOn="{StaticResource {x:Type ContextMenu}} -->
        <Setter Property="Background" Value="{StaticResource ContextMenuBackground}" />
        <Setter Property="BorderBrush" Value="{StaticResource ContextMenuBorderBrush}" />
        <Setter Property="BorderThickness" Value="1" />
    </Style>

    <Style TargetType="{x:Type ToolBar}">
        <!-- BasedOn="{StaticResource {x:Type ToolBar}}" -->
        <Setter Property="Background" Value="{StaticResource ToolBarBackground}" />
    </Style>
    <Style TargetType="{x:Type toolbars:ToolBarEx}">
        <!-- BasedOn="{StaticResource {x:Type ToolBar}}" -->
        <Setter Property="Background" Value="{StaticResource ToolBarBackground}" />
    </Style>
The code that tries to use this:
        protected override void OnViewLoaded(object view)
        {
            foreach (var module in _modules)
                module.PreInitialize();
            foreach (var module in _modules)
                module.Initialize();

            AppDomain currentDomain = AppDomain.CurrentDomain;
            currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
            Gemini.Modules.ToolBars.Controls.ToolBarEx tbex = new Modules.ToolBars.Controls.ToolBarEx();

            string src = "C:\\ProjectFolder\\Solution\\Gemini\\Themes\\VS2010\\Theme.xaml";

            if (_currentTheme == null)
                CurrentTheme = new ResourceDictionary
                {

                    Source = new Uri(src, UriKind.Absolute)
                };
So it works on the .NET components but will fail when trying to find the custom extended toolbar control. This is strange since I can create this control just before the code that generates the exception:

Gemini.Modules.ToolBars.Controls.ToolBarEx tbex = new Modules.ToolBars.Controls.ToolBarEx();

I debug inspect it and it is created and is an actual object.

Part of the problem as well is the add-in is not in the same directory as the AutoCAD application. The standard operating procedure is to put this in a ApplicationPlugin folder. Even if I copy the Dll's and theme files into the AutoCAD directory it still throws the same exception.

In the AppBootstrapper I use the following to get around the add-in not being in the AutoCAD applications directory:
var directoryCatalog = new DirectoryCatalog(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));

I was trying to find a way to create a wrapper or class that I can add into projects that will solve these problems.

I also tried to intercept the resolution of the assembly that it throws an exception on:

currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

but this is never called even though the exception is thrown....
Sep 25, 2013 at 9:33 AM
So, your addin spawns its own window, which is not the AutoCAD main window, is it correct?
If so, you can retrieve the window through the IViewAware interface, given that the view has been displayed and is currently part of the visual tree.

Using System.Windows.Media.VisualTreeHelper you can navigate up the visual tree from the CM view, and find the first Window object. Then, you can set the singleton Application.Main.MainWindow property, and that should be enough (at least for the WindowManager).

Now, regarding the issue with the control. This kind of declaration
 xmlns:mainmenu="clr-namespace:Gemini.Modules.MainMenu.Controls"
 xmlns:toolbars="clr-namespace:Gemini.Modules.ToolBars.Controls"
is not suited for plugins or in cases where there is an high chance that standard assembly resolution will fail. Have a look at this.
It's better to use the full definition:
xmlns:prefix="clr-namespace:Your.Namespace;assembly=Your.Assembly"
Regarding themes, adding the XAML file as a merged dictionary to the Application.Current.Resources should be enough, but note that all resources (and eventually referenced dictionaries), should be fully qualified URIs (see pack URIs). As an example, if you have a resource dictionary called Dictionary1.xaml in the Folder directory, the Application.xaml should look like this:
<Application x:Class="MyAssembly.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MyAssembly;component/Folder/Dictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
You don't need and actual Application.xaml to make it work, it is enough to setup everything from code:
Application.Current.Resources = new ResourceDictionary
                                            {
                                                MergedDictionaries =
                                                    {
                                                        new ResourceDictionary
                                                        {
                                                                Source = new Uri("pack://application:,,,/WpfApplication2;component/Folder/Dictionary1.xaml")
                                                        }
                                                    }
                                            };
Sep 28, 2013 at 9:23 PM
Thanks BladeWise, this looks promising. I am going to try out these ideas. I am wondering if there is a way to work with this stuff with no application object at all? I have been searching and it looks like there is a way to use custom themes by applying them to window objects. It is unclear though if this must be done through each windows XAML or if they can be applied by separate code at runtime. I could not find anything yet but I think there should be a way for CM to apply a theme to any window/view etc. that it creates.
Sep 29, 2013 at 2:24 PM
Edited Sep 29, 2013 at 2:24 PM
I suppose that merging the actual theme xaml file (maybe the Generic.xaml) into the Application.Resources ResourceDictionary could be enough.
Note that the standard resources lookup strategy is well known as you can find here.
The relevant part is:
The search for resources starts from the calling element, then continues to successive parent elements in the logical tree. The lookup continues onwards into application resources, themes, and system resources if necessary.
Even if there is an high chance that everything works fine without an Application object, I would still check-and-create a single Application, since it is more or less a given in a WPF environment.
Sep 29, 2013 at 5:04 PM
I think as far as what I am trying to do what you have suggested should work for my scenario. I am looking into making add-ons for existing software that supports it. In the case of AutoCAD and other software there is no Application object so creating my own to handle any WPF specifics is fine as you have outlined above.

I have been reading up more on the links that you sent me as well as other sites and articles on WPF theme and resource handling. All of the information I have read makes the assumption that you are making a stand alone application.

With Roslyn up-and-coming and alternatives such as NRefactory MONO etc. already in use there seems to be a way to incorporate scripting into the applications you make for customization. This is where all of this mess is really going to be brought up since if I create a WPF application and then add scripting capability, or even MEF loadable add-ins then anyone trying to create an add-in with a WPF interface is going to have these same issues. However in this case the Application object will exist and it will be a part of my Application. So if I try to use the methods above that means the add-in will start changing the theme on my base application?

I am looking for a way to manage the resources and themes for a WPF add-in to a WPF application, but have a clean separation so the add-in has its own "sandbox". From what I have read it looks like a reasonable compromise between complexity and functionality might be for the add-in developer to apply themes and resources on a window-by-window basis and avoid the Application mechanisms all together since they are tied to the original WPF application.
Sep 30, 2013 at 8:38 AM
The check-and-create of the singleton Application should be fine, given that other people writing plugins know what they are doing! :D If you can just avoid the Application object, you're staying on the safe side, I guess!

The only way you can truly sandbox' your plugin is using a different AppDomain... with all the issues you have to overcome. Otherwise, you can simply use a per-Window approach, taking advantage of the resources resolution lookup I stated above (local resources takes priority over global resources).

That said, I don't dislike plugins that just integrate nicely with the main application, versus plugins that do not comply with the main application layout/style. So, I would simply avoid major styling for my plugins, letting the main application (if any) manage them, or providing theming options if needed... but it's just personal tastes...
Oct 1, 2013 at 2:07 AM
Edited Oct 1, 2013 at 2:10 AM
Thank you BladeWise for all the insight you provided. From your explanation I agree that given an application with themes etc. I would just inherit those in my plugin and stick with that layout/style, it makes sense. I have one more question though after I looked into my scenario a bit more.

For the AutoCAD plugin I cannot use a different AppDomain due to the way the API is structured. The plugin will not run correctly and will cause stability issues, this much I am pretty sure about from past experiences. Currently when I run my plugin there is no style or theme at all and this is what prompted me to go down this road.

I am OK with the check-and-create of the singleton Application and using this to handle themes/styles.

The question that I ran into now is that what if there is another plugin that is trying to achieve the same thing I am? All is good if they check for the singleton Application but if they don't then this will cause exceptions to be thrown. If during the plugin Autoload process their plugin is loaded first and I rely on the singleton Application (and check for it) then I am at the mercy of their theme / style choices. Unless I clear and change the Application.Current.Resources, but then I would be changing the look of their window.

It looks to me from the discussion here that the best way to proceed where I can maintain control of the look/feel of my plugin without having to worry about breaking/inheriting other plugins is to use the per-Window approach.

Is there a way to do the following (from what you wrote above):
Application.Current.Resources = new ResourceDictionary
                                            {
                                                MergedDictionaries =
                                                    {
                                                        new ResourceDictionary
                                                        {
                                                                Source = new Uri("pack://application:,,,/WpfApplication2;component/Folder/Dictionary1.xaml")
                                                        }
                                                    }
                                            };
in code to apply to windows? For CM I was thinking of applying my themes in the viewmodel or module.cs file. I was starting to look along the lines of MEF importing a theme handler class that applies defaults to all windows that CM creates. I wonder if I can MEF import a resource dictionary that each window uses. I don't think that when I tried I could use something like Window.Resources = new ResourceDictionary {}

I think you got me going in the right direction here (I think), just trying to go about figuring out how to wire up the last mile.
Oct 1, 2013 at 12:31 PM
Since you are using CM to locate views (I assume you are using a view-model first approach), you can override the behaviour of either the WindowManager or the ViewLocator.GetOrCreateViewType extension point.

Let's say you already have exported a delegate used to provide the resource dictionaries to merge:
public delegate IEnumerable<ResourceDictionary> GetThemeResources();
in case you want to apply the custom behaviour to the WindowManager, you just need to override the CreateWindow function:
protected override Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings) {
            var view = base.CreateWindow(rootModel, isDialog, context, settings);
            if (view.Resources == null)
                        view.Resource = new ResourceDictionary();   //Probably not needed... I'm coding without IDE...
            foreach(var resource in IoC.Get<GetThemeResources>()())
                        view.Resources.MergedDictionaries.Add(resource);
            return view;
        }
Note however that this way only Window objects managed directly from the WindowManager are affected (moreover, you need to export your custom WindowManager). This is not o bad, given that you have no other way to spawn windows.

Another approach would be modifying the ViewLocator.GetOrCreateViewType method like this (bear with me for the missing class declaration and so on):
private static readonly _isThemeInitialized = DependencyProperty.RegisterAttached("_isThemeInitialized",
                                                                                                             typeof(bool),
                                                                                                             typeof(MyManager),
                                                                                                             new PropertyMetadata(false))

var baseGetOrCreateViewType = ViewLocator.GetOrCreateViewType;
public static Func<Type, UIElement> GetOrCreateViewType = viewType => {
            var view = baseGetOrCreateViewType(viewType);
            var window = view as Window;
            if (window != null && !((bool)window.GetValue(_isThemeInitialized)))
            {
                        if (window.Resources == null)
                                    window.Resource = new ResourceDictionary();
                        foreach(var resource in IoC.Get<GetThemeResources>()())
                                    window.Resources.MergedDictionaries.Add(resource);
                        window.SetValue(_isThemeInitialized, true);
            }
            return view;
        };
This way every Window view located by CM will have its resources initialized once. Unfortunally, just this piece of code is not enough since it covers just cases where the Window is either exported or explicitly defined as a view. Fortunally, the only case where a Window is generated is inside the WindowManager (unless some custom code gets in the way), so implementing both solutions should be enough (note: if both methods are used, you need to share the attached dependency property between the ViewLocator and the WindowManager, to avoid to merge resources multiple times).

On a final note: you can go beyond simple resource merging (still taking adavatage on it) and dive into structural skinning. You can incorporate those concepts in CM and define skins that both provide new resources and new views, depending on the selected skin (hint: extend ViewLocator with the concept of Skin). ;)
As you can see, I am quite fond of this subject... :)
Oct 12, 2013 at 6:42 AM
Thanks for the help BladeWise! You got me going in the right direction and I know how I want to approach this now based on your feedback and suggestions.
Jan 20, 2015 at 9:15 PM