Question

I use Caliburn.Micro. Well, to tell the truth, here is the whole issue I have faced:

I have set up the binding at the design time. See the code below:

<Window x:Class="Microtech.TPM.Views.DestinationChoiceView"       
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
    xmlns:vmns="clr-namespace:Microtech.TPM.ViewModels"
    mc:Ignorable="d"
    d:DataContext="{d:DesignInstance Type=vmns:DestinationChoiceViewModel, IsDesignTimeCreatable=True}"
    cal:Bind.AtDesignTime="True" Width="1280" Height="1024">
<Window.Resources>
    <vmns:DestinationChoiceViewModel x:Key="ViewModelKey" />
</Window.Resources>

I need to subscribe for the events of the ViewModel. How to accomplish this if I have defined the reference to the ViewModel in Window.Resources and use it for the binding further in xaml? I quite don't understand how to use the same references. Besides, I quite don't understand how many ViewModel instances I will already have with this code. As I understand, I'll have at least 2 instances, am I right? So, how to avoid this? And this is the part of the question to.

Was it helpful?

Solution

If you are performing ViewModel-First binding you should have already used the Bootstrapper class to create the root viewmodel for your application hierarchy.

In this case, you either need to bind to a property which contains a ViewModel which is located on your root VM, or you need to make the VM a Conductor and Activate one or more items within it.

Example - binding other VMs as properties

Your VM:

public class RootViewModel : PropertyChangedBase
{
    private SomeOtherViewModel _someOtherView;
    public SomeOtherViewModel SomeOtherView
    {
        get { return _someOtherView; }
        set 
        {
            if(_someOtherView != value)
            {
                 _someOtherView = value;
                 NotifyOfPropertyChange(() => SomeOtherView);
            }
        }
    }
}

And the associated View:

<Window x:Class="SomeProject.SomeView">
    <... some view related code - buttons etc ...>
    <!-- Render the other ViewModel using CMs conventions -->
    <ContentControl x:Name="SomeOtherView">
</Window>

Example - using a conductor

The VM:

public class RootViewModel : Conductor<IScreen>
{
     public RootViewModel(SomeViewModel someViewModel)
     {
         ActivateItem(someViewModel);
     }        
}

And the associated View:

<Window x:Class="SomeProject.SomeView">
    <... some view related code - buttons etc ...>
    <!-- Render the other ViewModel using CMs conventions -->
    <ContentControl x:Name="ActiveItem">
</Window>

In addition to the above, my advice is not to declare a viewmodel as a resource as you are creating a dependency on that VM within your view. You are also scattering the concern of reference locating and handling and spreading it amongst your views, which can lead to maintenance headaches and spaghetti code.

Remember that you want to keep class complexity down to a minimum so your classes should follow Single Responsibility Principle - i.e. they should be concerned with one area of functionality and should not be responsible for jobs outside of that single scope. This is why we have IoC containers, they are there to manage your references (as a component that's their single responsibility!)

The IoC container SimpleContainer that comes with Caliburn Micro is fine for most projects; adding one of the popular IoC containers such as Castle Windsor/Ninject etc is easy and there are plenty of tutorials to get this running

This way you can specify your required dependencies in your VM but be agnostic to the resolution and control of these dependencies

In order to send events and messages between your VMs there are a couple of mechanisms CM has:

  • Using action messages - you attach an action message to your view to handle events, and this will call methods on the attached viewmodel

e.g.

<Window x:Class="SomeProject.SomeView">
    <Button cal:Message.Attach="[Event Click] = [Action ButtonClicked()]">click me</Button>
</Window>`

(this will call ButtonClicked() method on your ViewModel)

  • Using the event aggregator - you subscribe to the aggregator and listen for messages of a certain type by implementing IHandle<T> where T is the message type you are interested in

I will point out that at no point does the view or VM reference each other - though it's possible, it should be avoided unless really necessary. The viewmodel should be blissfully unaware of the view, with no coupling between the objects. Caliburn Micro is there to glue the VM to the view, and it does a good job of doing so.

The more decoupled your objects are the easier it is to make (inevitable) changes, refactorings and additions.

If you are struggling with using CM I'd suggest running through all the tutorials on the CodePlex site

http://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top