Caliburn.Micro getting it to bind UserControls in a MainView to their ViewModels
Asked Answered
I

1

14

I have a MainView.xaml, binding to a MainViewModel just fine.

What I wanted to try out was splitting a lot of controls I have on my main form into UserControls.

Now I put the UserControls inside the Views folder along with the MainView and named them, LeftSideControlView.xaml and RightSideControlView.xaml. The corresponding ViewModels are in the ViewModels folder called LeftSideControlViewModel, etc.

I successfully added the usercontrols to the mainview:

 <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <UserControls:LeftSideControlView cal:Bind.Model="{Binding}" />
    <UserControls:RightSideControlView cal:Bind.Model="{Binding}"
                                  Grid.Column="1" />
</Grid>

they show up correctly in the designer. Here's one of them in xaml:

 <UserControl x:Class="TwitterCaliburnWPF.Library.Views.LeftSideControlView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <StackPanel>
        <Label x:Name="Text" FontSize="25" HorizontalAlignment="Center" Margin="5"/>
        <TextBox x:Name="TextBox1"
                 Width="200"
                 HorizontalAlignment="Center" FontSize="25" Margin="5" />
        <Button Width="200" Height="50" Content="Button!" Margin="20" />
    </StackPanel>
</Grid>

I added the viewmodels and their interfaces inside the AppBootstrapper for Caliburn using Castle.Windsor.

 public class ApplicationContainer : WindsorContainer
 {
  public ApplicationContainer()
  {
     // Register all dependencies here
     Register(
        Component.For<IWindowManager>().ImplementedBy<WindowManager>().LifeStyle.Is(LifestyleType.Singleton),
        Component.For<IEventAggregator>().ImplementedBy<EventAggregator>().LifeStyle.Is(LifestyleType.Singleton),
        Component.For<ILeftSideControlViewModel>().ImplementedBy<LeftSideControlViewModel>(),
        Component.For<IRightSideControlViewModel>().ImplementedBy<RightSideControlViewModel>()
        );

     RegisterViewModels();
  }

  private void RegisterViewModels()
  {
     Register(AllTypes.FromAssembly(GetType().Assembly)
                  .Where(x => x.Name.EndsWith("ViewModel"))
                  .Configure(x => x.LifeStyle.Is(LifestyleType.Transient)));
  }

Here's the LeftSideControlViewModel class:

 using Screen = Caliburn.Micro.Screen;

 namespace TwitterCaliburnWPF.Library.ViewModels
 {
    public class LeftSideControlViewModel : Screen, ILeftSideControlViewModel
    {
       private string _text = "Hello from the Left side!";
       private string _textBox1 = "Enter Text Here";

       public string Text
       {
          get { return _text; }
       }

       public string TextBox1
       {
          get { return _textBox1; }
       }
    }
 }

Here's the MainViewModel and I'm going off what I read in the Caliburn.Micro documention, as before I tried this nothing was specifically in the MainViewModel to tell it to load these 2 controls or show these 2 controls.

Still when the app is up and running the values are not binding to the usercontrols from their respective viewmodels.

namespace TwitterCaliburnWPF.Library.ViewModels
{
   public class MainViewModel : Conductor<IScreen>
   {
      public MainViewModel()
      {
         ShowLeftControl();
         ShowRightControl();
      }

      private void ShowRightControl()
      {
         ActivateItem(new RightSideControlViewModel());
      }

      private void ShowLeftControl()
      {
         ActivateItem(new LeftSideControlViewModel());
      }

      public string TextToDisplay
      {
         get { return "Coming from the ViewModel!"; }
      }
   }
}
Inductive answered 3/4, 2012 at 16:9 Comment(0)
U
25

You don't need to use a Conductor here. That's basically used for navigation scenarios. Just make two public properties on your MainViewModel one for RightSideControlViewModel called RightSide and one for LeftSideControlViewModel, called LeftSide. Then, instead of instantiating your UserControls directly in your MainView, create two ContentControls, one with x:Name="LeftSide" and the other with x:name="RightSide" This is a view-model-first way of accomplishing it. If you want to do it view-first, keep the user control definitions in your MainView, but change the Bind.Model so that it points to the new properties you created, like Bind.Model="{Binding LeftSide}"

Basically, the way you have things defines....the bindings just aren't pointing at the right objects, more or less. You've got the Conductor in there, which you don't need to accomplish this. You may want to keep it if you intend to have some sort of navigation architecture. Remember that when you call ActivateItem on a Conductor, you are basically changing its ActiveItem property; only one model with be active at a time. In the case above you activate both items, but only the second one remains active. Furthermore, in your view, nothing is bound to ActiveItem.

I hope this makes sense!

Unfetter answered 3/4, 2012 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.