5

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);
    }
}
Community
  • 1
  • 1
Joe
  • 6,773
  • 2
  • 47
  • 81
  • Probably you should add more details: are you using a `conductor` to display your dialog? What issue do you have with `TryClose`? Do you see the same message of the other question? – Il Vic Jun 01 '16 at 13:06
  • This is viewmodel first since you aren't using a user control to specify the view and you are loading a view into the content control based on the view model. It's only view first if you are providing a user control that you want to bind/create a view model to/for. Does your child viewmodel have a candeactivate hook? – Charleh Jun 01 '16 at 14:03
  • The child (TimeSpanViewModel) inherits PropertyChangedBase and the dialog ViewModel that hosts it (view code above) implements Screen. I've not touched deactivation hooks in caliburn. – Joe Jun 01 '16 at 15:58
  • @IlVic I'm getting the same log message from caliburn: "TryClose requires a parent IConductor or a view with a Close method or IsOpen property." I'm not displaying the ViewModel with a conductor, but in a window using the WindowManager (I've added that code into the question if it's any help) – Joe Jun 01 '16 at 16:37

2 Answers2

3

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?

Robin Krom
  • 180
  • 8
  • I think you might be right with the window manager being the problem, I'll try using the default implementation. I have tried with x:Name="TimeSpanViewModel" and get the same results. The TimeSpanViewModel is indeed a property on the DialogViewModel. I'm not sure what you mean by [Import]. I've got the ViewModel and View files (if I misname the View file Caliburn can't wire it up and reports "View not found for TimeSeriesViewModel") – Joe Jun 01 '16 at 19:53
  • The TimeSpaceViewModel has to come from somewhere, that is what I meant with the import... (You have export on your window manager, so I assume you use MEF). – Robin Krom Jun 01 '16 at 21:13
  • I am working on a project to hopefully make it easy to use: translations, configuration, Caliburn.Micro, MahApps, NotifyIcon WPF and MEF. This can be found here: https://github.com/dapplo/Dapplo.CaliburnMicro and has a demo project where I added a dialog. – Robin Krom Jun 01 '16 at 21:14
  • The dialog "CredentialsViewModel" is behin the login button top right of the settings, it opens and closes without issues. The demo might looks horrible but it's just a demo to show how "easy" it is to use the features (remembers your language settings) and besides it's work in progress... The projects were written for a new version of Greenshot. – Robin Krom Jun 01 '16 at 21:31
  • I'm not using MEF (Yet! I may, so that's a very interesting project to look at). That Import is probably from the project I took the AppWindowManager (from Caliburn.Metro) implementation from. I reconfigured the project to use the standard WindowManager, and the standard ShowDialog and still have the same error. It's odd, all my other dialogs work fine and if I remove the ContentControl the dialog functions fine. I'll have a look at your implementation. – Joe Jun 02 '16 at 08:52
  • Can you copy only the essentials parts to a simple project, so we can reproduce it and see what's wrong? Mixing MEF and non MEF is not a good idea. MEF is not easy to use, this is why I wrote the Dapplo.Addons bootstrapping code (supports extensions in different DLLs). My projects are available as NuGet, you should have a look at the Demo. That reminds me, maybe there is an error with your Caliburn.Metro bootstrapper... Maybe also have a look at my CaliburnMicroBootstrapper, although this uses MEF... – Robin Krom Jun 02 '16 at 17:54
  • `[Export(typeof(IWindowManager))]` that line says you are using MEF, as for viewmodel or view-first, that is entirely up to how you feel when working with it. CM is primarily a viewmodel-first design, pretty much always has been. Can you show us your bootstrapper, so the community can better guide your DialogView usage. – mvermef Jun 07 '16 at 18:58
1

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.

Kyle
  • 575
  • 1
  • 4
  • 9