0

I have 3 ViewModels:

  • App.ViewModels.LoginViewModel
  • App.ViewModels.NavigationViewModel
  • App.ViewModels.AbcViewModel

and 3 Views:

  • App.Views.LoginView
  • App.Views.NavigationView
  • App.Views.AbcView

In my AppBootstrapper, LoginView is loaded like so:

protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
    var windowManager = IoC.Get<IWindowManager>();
    var loginModel = IoC.Get<ILogin>("Login");

    windowManager.ShowWindow(loginModel, "LoginView");
}

However, this returns that the view cannot be found for that ViewModel. Unless I change the namespace of the LoginView to App.Views.Login.LoginView and leave the VM namespace as it is. It then works fine.

After a succesfful login, I use the same process to load my NavigationViewModel. (After having changed the namespace to the App.Views.Navigation.NavigationViewModel so that it actually works)

Currently, this leaves me with the following namespaces for the views:

  • App.Views.Login.LoginView
  • App.Views.Navigation.NavigationView
  • App.Views.AbcView

NavigationViewModel is a conductor, it has a list of ViewModels and a TabControl on the view to display them.

Unfortunately I then have to manually bind my AbcViewModel to the view, otherwise nothing gets displayed. For example:

AbcView abcv= new AbcView ();
AbcViewModel abcvm= IoC.Get<AbcViewModel>("Abc");
ViewModelBinder.Bind(abcvm, abc, null);

I want everything to be done using the Caliburn ViewModel first approach, so that adding new ViewModels and Views I don't need to worry about binding the view manually. I've adhered to the structure and yet it isn't working. Where am I going wrong?

Basically, is there a way that caliburn can create and then bind my view when I create my ViewModel? Do I need to somehow call the ViewLocator for each of my models? If so, how is this any different to the manual bind that I'm doing at the moment?

Does anyone know of a full example (Whole project) of a view model first caliburn project that I can sneak a look at?

Any help appreciated, thanks in advance.

Matt

  • You shouldn't need to do anything except configuring `Bootstrapper`. Caliburn.Micro uses convention: `Views/xyzView` maps to `ViewModels/xyzViewModel`. Also make sure you remove your starting point from `App.xaml` and add `ResourceDictionary`. I created a [template](https://github.com/fcin/Caliburn.Micro-DI-Example) for starting with caliburn.micro with Dependency Injection – FCin Nov 17 '17 at 16:36
  • Cheers for the quick response. Had a quick look at your example, will have a proper look later, I can already see your bootstrapper is far simpler than mine. I'm also using a CompositionContainer and have coupled that up with a CompositionBatch. `container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType()));` Then I'm using: `batch.AddExportValue>(() => container.GetExportValue());` To add all of my ViewModels (Except Navigation and Login) to the container. – Matt Conway Nov 17 '17 at 16:49
  • Do you keep your ViewModels and Views in the same solution? You don't need `CompositionContainer`. `SimpleContainer` is enough to register types you want to inject if they are in the same project. – FCin Nov 17 '17 at 17:00
  • Yes the ViewModels and Views are in the same project. I'll change to a simple container! What will this actually change? – Matt Conway Nov 20 '17 at 09:09
  • The container type itself is not the problem. You just don't need `CompositionContainer`, because you don't need to add any dll's. Every type you need to find is in your project, so `SimpleContainer` is sufficient. The actual problem is that you try to run your application by `windowManager.ShowWindow(loginModel, "LoginView");`. This is not how `Caliburn.Micro` works. You need to override `OnStartup` and use `DisplayRootViewFor();`. Follow my project and [this](https://caliburnmicro.com/documentation/configuration). – FCin Nov 20 '17 at 09:24
  • I can post an answer later explaining what steps are required for configuring `Caliburn.Micro` so that you can close the question. – FCin Nov 20 '17 at 09:26
  • I've removed the call to WindowManager and have replaced with DisplayRootView for Login and Navigation. Both working now! Then, in the view models that I'm conducting between I have added a new method: `private void BindView() { var v = ViewLocator.LocateForModel(this, null, null); ViewModelBinder.Bind(this, v, null); }` Which works but still feels like it is incorrect. At least everything is working using the correct namespace structure now though! – Matt Conway Nov 20 '17 at 10:35
  • personal option is get away from MEF if you don't need it. Its such a pain in the ass to debug. Use SimpleContainer or some other container but get away from MEF – mvermef Nov 25 '17 at 01:10

1 Answers1

0

You don't need to bind any Views/ViewModels yourself, Caliburn.Micro takes care of it. You have to only tell Caliburn.Micro where your start class is by overriding OnStartup in your Bootstrapper class (see tutorial).

You also have to provide ResourceDictionary with your Bootstrapper class in App.xaml and remove Startup:

    <Application.Resources>
        <!--Declare your bootstrapper class-->
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:YourBootstrapperClass x:Key="bootstrapper" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Your Bootstrapper class must derive from BootstrapperBase and call Initialize() in constructor. That should be enough for Caliburn.Micro to bind views/viewmodels.


Additionally, if you want to use Dependency Injection in your project you can setup a SimpleContainer and register types you want to inject. You can do this in Bootstrapper:

public class Bootstrapper : BootstrapperBase
    {
        // Container for your registered types.
        private SimpleContainer _container = new SimpleContainer();

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
        {
            // Tells Caliburn.Micro where the starting point of your application is.
            DisplayRootViewFor<ShellViewModel>();
        }

        protected override void Configure()
        {
            // Register types you want to inject.
            _container.Singleton<IWindowManager, WindowManager>();
            _container.Singleton<IEventAggregator, EventAggregator>();
            base.Configure();
        }

        protected override object GetInstance(Type service, string key)
        {
            // This is called every time a dependency is requested. 
            // Caliburn.Micro checks if container contains dependency and if so, returns it.
            var instance = _container.GetInstance(service, key);
            if (instance != null)
                return instance;

            throw new InvalidOperationException("Could not locate any instances.");
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            // Get all registered classes...
            return _container.GetAllInstances(service);
        }

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

    }

After that you can just inject dependecies via constructor like this:

public class ShellViewModel 
{
    public ShellViewModel(IEventAggregator eventAggregator)
    {
          // You can use eventAggregator here. It is already injected.
    }
}

Caliburn.Micro uses naming convention so that it can discover Views/ViewModels automatically. You should have folders named: Views and ViewModels and your classes should be named YourClassView and YourClassViewModel. This way Caliburn.Micro can find them. So if you setup OnStartup like this:

protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
    DisplayRootViewFor<ShellViewModel>();
}

Then your viewmodel must sit in ViewModels/ShellViewModel and your view for this ViewModel must sit in Views/ShellView.

FCin
  • 3,804
  • 4
  • 20
  • 49