0

I try to use Caliburn Micro in WPF application in combination with modern UI. I find some solutions which uses caliburn content loader however it doesn't work at all, here is the code:

public class CaliburnContentLoader : DefaultContentLoader
{
   protected override object LoadContent(Uri uri)
   {
      var content = base.LoadContent(uri);
      if (content == null)
         return content;

      var vm = Caliburn.Micro.ViewModelLocator.LocateForView(content);
      if (vm == null)
         return content;

      if (content is DependencyObject)
      {
         Caliburn.Micro.ViewModelBinder.Bind(vm, content as DependencyObject, null);
      }
      return content;
   }
}

For any view model the LocateForView method every time returns null. I think that problem here is the IoC container which from some reasons is empty in my CaliburnContentLoader.

For big picture here is my App XALM:

<Application x:Class="Astrea.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Astrea">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:Bootstrapper x:Key="bootstrapper" />
                    <local:CaliburnContentLoader x:Key="CaliburnContentLoader" />
                </ResourceDictionary>
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Dark.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

and MainWindow xaml

<mui:ModernWindow x:Class="Astrea.Views.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mui="http://firstfloorsoftware.com/ModernUI"
        Title="mui" IsTitleVisible="True"         
        ContentLoader="{StaticResource CaliburnContentLoader}"
        ContentSource="/Views/ChildView.xaml">

    <mui:ModernWindow.MenuLinkGroups>
        <mui:LinkGroup DisplayName="nodes explorer">
            <mui:LinkGroup.Links>
                <mui:Link DisplayName="home" Source="/Views/ChildView.xaml" />
            </mui:LinkGroup.Links>
        </mui:LinkGroup>

Maybe something is wrong in my bootstrapper? Here is code:

namespace Astrea
{
    class Bootstrapper : BootstrapperBase
    {
        private CompositionContainer container;

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            // Add New ViewLocator Rule
            ViewLocator.NameTransformer.AddRule(
            @"(?<nsbefore>([A-Za-z_]\w*\.)*)?(?<nsvm>ViewModels\.)(?<nsafter>([A-Za-z_]\w*\.)*)(?<basename>[A-Za-z_]\w*)(?<suffix>ViewModel$)",
            @"${nsbefore}Views.${nsafter}${basename}View",
            @"(([A-Za-z_]\w*\.)*)?ViewModels\.([A-Za-z_]\w*\.)*[A-Za-z_]\w*ViewModel$"
            );

            container = new CompositionContainer(
                    new AggregateCatalog(
                    new AssemblyCatalog(typeof(IShellViewModel).Assembly),
                    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>().FirstOrDefault()
                )
            );

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

        public T GetInstance<T>()
        {
            string contract = AttributedModelServices.GetContractName(typeof(T));

            var sexports = container.GetExportedValues<object>(contract);
            if (sexports.Count() > 0)
                return sexports.OfType<T>().First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;

            var exports = container.GetExportedValues<object>(contract);
            return exports.FirstOrDefault();
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            //return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
            var ret = Enumerable.Empty<object>();

            string contract = AttributedModelServices.GetContractName(serviceType);
            return container.GetExportedValues<object>(contract);
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);
        }

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return new[] {
            Assembly.GetExecutingAssembly()
        };
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<MainWindowViewModel>();
        }
    }
}
PaulWebbster
  • 1,480
  • 2
  • 14
  • 27
  • ContentLoader is a MUI object you can probably get more assistance from them but also what I found is this http://mui.codeplex.com/discussions/436518 – mvermef Apr 28 '15 at 18:24
  • Yes I see that. The problem here is the IoC container I think. [Here](http://stackoverflow.com/questions/16421582/caliburn-micro-and-modernui-examples-tutorials) Mathias has the same problem. I don't know if that is the container or even something with namespace. I try even write something less generic with hardcoded view model name but `IoC.getInstance` is empty and I really don't know what I'm doing wrong. I debug the bootstrapper and indeed the view models are added to IoC container but they disappears in content loader. – PaulWebbster Apr 29 '15 at 07:25
  • Of course thanks for suggestions, I will add also this tag. – PaulWebbster Apr 29 '15 at 07:33

2 Answers2

0

I find the solution. The problem here was the MEF container which wouldn't work with the content loader or simply I don't know how to force it to work with.

Instead of MEF container I tried to use Simple IoC container delivered with Caliburn Micro and it works like a harm.

Here is my new bootstrapper:

public class HelloBootstrapper : BootstrapperBase
{
    private SimpleContainer container;

    public HelloBootstrapper()
    {
        Initialize();
    }

    protected override void Configure()
    {
        container = new SimpleContainer();
        container.Singleton<IWindowManager, WindowManager>();
        container.Singleton<IEventAggregator, EventAggregator>();
        container.PerRequest<ShellViewModel>();
        container.PerRequest<ModernWindowViewModel>();
    }

    protected override IEnumerable<Assembly> SelectAssemblies()
    {
        return new[] {
        Assembly.GetExecutingAssembly()
    };
    }

    protected override object GetInstance(Type serviceType, string key)
    {
        return container.GetInstance(serviceType, key);
    }

    protected override IEnumerable<object> GetAllInstances(Type serviceType)
    {
        return container.GetAllInstances(serviceType);
    }

    protected override void BuildUp(object instance)
    {
        container.BuildUp(instance);
    }

    protected override void OnStartup(object sender, StartupEventArgs e)
    {
        DisplayRootViewFor<ModernWindowViewModel>();
    }
}

I don't only know how to add all View Models to the container. Maybe someone can help with that and modify this code section.

PaulWebbster
  • 1,480
  • 2
  • 14
  • 27
0

I use this code to add the ViewModels:

 foreach(var assembly in SelectAssemblies())
   {
   assembly.GetTypes()
     .Where(type => type.IsClass)
     .Where(type => type.Name.EndsWith("ViewModel"))
     .ToList()
     .ForEach(viewModelType => _container.RegisterPerRequest(viewModelType, viewModelType.ToString(), viewModelType));
   }

The code is slightly modified from code created by Tim Corey for the TimcoRetail course. I cycle along all assemblies. for each assemb"ly you need to get all types, but then apply two filters. One to include classes only and then to include only classes whose name ends with "ViewModel" These classes all are then added to the container as PerRequest. In SelectAssemblies you can add other assemblies, like libraries.

RudolfJan
  • 91
  • 1
  • 11