Add multiple views inside a view using WPF and Caliburn.Micro

让人想犯罪 __ 提交于 2019-11-29 23:14:09

EDIT - New (more complete) Answer Below:

Ok, C.M is doing a lot of stuff for you, it's all about getting your classes and xaml prepared for C.M to be able to find it. As said above, I prefer to be write code explicit, rather than rely in implicit code assumptions by the framework.

So, the Bootstrapper, from the default C.M project is just fine.

public class AppBootstrapper : Bootstrapper<MainViewModel>
{
    // ... You shouldn't need to change much, if anything
}

The section `Bootstrapper' is very important, is it indicates which ViewModel is your first, or main screen, when the app starts up.

[Export(Typeof(MainViewModel))]
public class MainViewModel : Screen,  IShell
{
    [ImportingConstructor]
    public MainViewModel(YourFirstViewModel firstViewModel, YourSecondViewModel secondviewModel) // etc, for each child ViewModel
    {
    }
}

In the [ImportingConstructor] you don't need to do anything other than specify that the MainViewModel requires the presence of the other ViewModels. In my particular case, I like my MainViewModel to be a container, and container only, the event logic is handled elsewhere. But you could just as easily have your Handle logic here - but that's a while other discussion.

Now each child View Model also needs to export themselves so C.M knows where to find them.

[Export(Typeof(YourFirstViewModel))]
public class YourFirstViewModel : IShell
{
    // VM properties and events here
}

No need to specify an Importing Constructor if you are just using a default constructor.

Now, each of your Views for these will look something like:

<UserControl x:Class="Your.Namespace.MainView"
             xmlns:views="clr-namespace:Your.Namespace.Views"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.ViewModels.MainViewModel"
             MinWidth="800" MinHeight="600">
    <StackPanel x:Name="RootVisual">
        <views:YourFirstView />
        <views:YourSecondView />
        <!-- other controls as needed -->
    </StackPanel>
</UserControl>

XAMl or one of the child-views

<UserControl x:Class="Your.Namespace.Views.YourFirstView"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.ViewModels.YourFirstViewModel"
             MinWidth="800" MinHeight="600">
    <Grid x:Name="RootVisual">
        <!-- A bunch of controls here -->
    </Grid>
</UserControl>

What the heck is actually going on here?

Well, C.M sees in the bootstrapper, that MainViewModel is the starting point because of the line specifying public class AppBootstrapper : Bootstrapper<MainViewModel>. MainViewModel requires that a YourFirstViewModel and YourSecondViewModel (and other ViewModels) are required in it's constructor, so C.M constructs each one. All of these ViewModels end up in the IoC (making your life much easier later - again, a whole other discussion).

C.M handles assigning the datacontext, on your behalf, to each of the views because you specify which VM to bind to with the line like cal:Bind.Model="Your.Namespace.ViewModels.YourFirstViewModel"

With any luck, that should get you started. Also refer to the C.M example project Caliburn.Micro.HelloEventAggregator as it does exactly what you are looking for (Although, it's described as an Event Aggregator demo, which is also very useful - but again, another discussion)

(Original Answer for reverence, below)

You need to do this:

<UserControl x:Class="Your.Namespace.Here.YourView"
             xmlns:cal="http://www.caliburnproject.org"
             cal:Bind.Model="Your.Namespace.Here.YourViewModel"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="1024">
  <YourControlLayout />
</UserControl>

Notice the line cal:Bind.Model="Your.Namespace.Here.YourViewModel" which specifies the exact View Model to bind this View to.

Don't forget to export your class type, or c.m can't find it.

[Export(typeof(YourViewModel))]
public class YourViewModel : IShell
{
    ...
}

Then you can nest your User Controls as you see fit. It's a very good way to make use of C.M, and you will find it highly scalable. The only weakness is that the View and ViewModel must be in the same project (as far as I can tell). But the strength of this approach is you can separate the View and View Model classes into different Namespaces (within the same project) if you wish, to keep things organized.

As a commentary on c.m I prefer this method, actually, even if I don't have to nest View UserControls and such. I would rather explicitly declare witch VM a View is bound to (and still let C.M handle all the heavy lifting in IoC) than let c.m "figure it out" from implied code.

Even with a good framework: explicit code is more maintainable than implied code. Specifying the bound View Model has the benefit of clearly stating what your data context is expected to be, so you won't need to guess later.

A better approach is to use ContentControl on your main view, and give it the same name as a public property on your MainViewModel which is of type MyControlViewModel. E.g.

MainView.xaml

<ContentControl x:Name="MyControlViewModel" />

MainViewModel.cs

// Constructor
public MainViewModel()
{
  // It would be better to use dependency injection here
  this.MyControlViewModel = new MyControlViewModel();     
}

public MyControlViewModel MyControlViewModel
{
  get { return this.myControlViewModel; }
  set { this.myControlViewModel = value; this.NotifyOfPropertyChanged(...); }
}

in file App.xaml.cs, in method GetInstance add the following lines

protected override object GetInstance(Type service, string key)
{
    if (service == null && !string.IsNullOrWhiteSpace(key))
    {
        service = Type.GetType(key);
        key = null;
    }
    // the rest of method
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!