Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caliburn Micro, How to use ContentControl (or display 'sub' ViewModel) using ViewModel First

I'm using MVVM framework Caliburn Micro throughout my application with ViewModel first (or so I thought). However, when I had problems with a dialog using TryClose(true) failing to close it's parent window and stumbled upon this question that perfectly outlined my problem, I'm also getting the "TryClose requires a parent IConductor or a view with a Close method or IsOpen property.":

Caliburn.Micro - ShowDialog() how to close the dialog?

However, I'm not exactly sure how to implement the solution. The answer states:

Remove the cal:Bind.Model and cal:View.Model bindings...

Turns out using these bindings is a View-First approach, which I wasn't aware i was doing. Here's a sample of my offending dialog:

<UserControl ... Height="206" Width="415">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="AUTO" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Button x:Name="Okay" Content="Okay" Width="100" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" />
            <Button x:Name="Cancel" Content="Cancel" Width="100" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
        <ContentControl cal:View.Model="{Binding TimeSpanViewModel}"/>
    </Grid>
</UserControl>

It's just a wrapper with an okay and cancel button for an already existing ViewModel (who's view is resolved by caliburn, hence me thinking I'm doing ViewModel first). If I remove this cal:View.Model binding I do indeed regain the ability to close my dialog box, but I loose all the actual content. I'm using the ContentControl to display things all over my application (in ItemsControls, dialog boxes, pop-ups, etc).

My question is, how should I be displaying a ViewModel in a ViewModel first Caliburn?

Edit: I'm displaying the DialogViewModel (which inherits screen) using the WindowManager like so:

[Export(typeof(IWindowManager))]
public class AppWindowManager : MetroWindowManager, IDialogManager
{
    AppViewModel Content { get; set; }

    public AppWindowManager()
    {

    }

    public override MetroWindow CreateCustomWindow(object view, bool windowIsView)
    {
        if (windowIsView)
        {
            return view as MainWindowContainer;
        }

        MainWindowContainer window = new MainWindowContainer();
        //{
        window.Content = view;
        //};

        return window;
    }

    public override bool? ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null)
    {
        Window window = CreateWindow(rootModel, true, context, settings);

        return window.ShowDialog();
    }

    public object ShowCustomDialog(object rootModel, string title, bool showWindowsOptions = true)
    {
        dynamic settings = new ExpandoObject();
        settings.Title = title;
        settings.ShowCloseButton = showWindowsOptions;
        settings.ShowMaxRestoreButton = showWindowsOptions;
        settings.ShowMinButton = showWindowsOptions;
        settings.SizeToContent = SizeToContent.WidthAndHeight;
        return ShowDialog(rootModel, null, settings);
    }

    public ILoadingDialogViewModel CreateLoadingDialogManager()
    {
        return new LoadingDialogViewModel(this);
    }
}
like image 254
Joe Avatar asked Jun 01 '16 08:06

Joe


2 Answers

To answer the main question

how should I be displaying a ViewModel in a ViewModel first Caliburn?

I assume TimeSpanViewModel is a property you have on your ViewModel which has an [Import] (and the ViewModel is exporting itself)? I think that you should change cal:View.Model="{Binding TimeSpanViewModel}" to x:Name="TimeSpanViewModel". Even if this might not solve the issue, it is the right thing to do and Caliburn will make sure it's bound correctly.

I tried to reproduce your issue, but even using your way it worked for me. So why it doesn't work the way you are currently doing it, is a good (second) question.

The biggest problem might be your AppWindowManager, if the window you create in there doesn't go through the correct Caliburn code it will not be correctly bound. As there is a lot of code missing, I'm not even sure what AppViewModel Content { get; set; } is doing there, mostly I can just speculate. Did you try to use the default WindowManager implementation, just to see if it works with that?

like image 188
Robin Krom Avatar answered Nov 11 '22 18:11

Robin Krom


For those new to Caliburn.Micro and relevant to this thread, if you use Caliburn.Micro's SimpleContainer in your AppBootStrapper for an IoC dependency injection container, then you do not use MEF or any other IoC container implementation.

A lot of old StackOverflow discussions and code floating around the Internet use MEF with Caliburn.Micro, but the SimpleContainer provided by Caliburn.Micro may be sufficient for your project (if so, don't let the MEF code confuse you when looking at examples).

Calburn.Micro Wiki entry for SimpleContainer.

like image 1
Kyle Avatar answered Nov 11 '22 18:11

Kyle