Mahapps.metro: TransitioningContentControl Support for MVVM

Created on 8 Jul 2016  路  11Comments  路  Source: MahApps/MahApps.Metro

What steps will reproduce this issue?

I have an application that uses Caliburn.Micro and MahApps, I am using a TransitioningContentControl in my ShellView

<controls:TransitioningContentControl Grid.Row="0" Grid.Column="1" Content="{Binding ActiveItem}" RestartTransitionOnContentChange="True" />`

I am setting the Content to the ActiveItem property of the ShellViewModel, which gets set when the ViewModel is activated

protected override void OnActivate()
{
    this._eventAggregator.Subscribe(this);
    this.ActivateItem(this.UpdateViewModel.Value);
    base.OnActivate();
}

UpdateViewModel is a property of the ViewModel

[Import(typeof(UpdateViewModel))]
 public Lazy<IScreen> UpdateViewModel { get; set; }

Expected outcome

What is expected is that the View content is loaded in the TransitioningContentControl, however all that gets displayed is the fully qualified type name of the ViewModel

Environment

  • MahApps.Metro v1.2.4.0
  • Windows 10
  • Visual Studio 2015
  • .NET Framework 4.6.1

Most helpful comment

I don't know how caliburn.micro handles the " x:Name="ActiveItem". The problem with
Content="{Binding ActiveItem}" is, that the ContentControl doesn't knwo how to render the viewmodel behind ActiveItem.

If you create a datatemplate in your ressources it works:

<Window.Resources> <DataTemplate DataType="{x:Type viewModels:TestViewModel }"> <views:TestView></views:TestView> </DataTemplate> </Window.Resources>

If you are interested in how caliburn.micro handles the x:Name on contentcontrol you should open an issue there, or study the code from caliburn.micro :)

@punker76 I think the issue should be closed. It's not a problem with mahapps.

All 11 comments

@stevensn99 Do you have a `ContentTemplate? for your ViewModel?

The ViewModel is a class that inherits from Screen (Caliburn), the View is a UserControl. Caliburn wires up the DataContext of the View to the ViewModel, why would my ViewModel have a ContentTemplate?

Looking at the source for the TransitioningContentControl the following line sets the content of currentContentPresentationSite

this.currentContentPresentationSite.Content = newContent;

If setting the Content to a ViewModel then i feel you would need to locate the View for that ViewModel using something like

ViewLocator.LocateForModel( newContent, null, null)

because newContent will be the ViewModel, for example:

public class MyViewModel : Screen, IMyViewModel { ... }

and the the View would be the MyView.xaml UserControl

I may be completely misunderstanding this control, or even how to use the control with MVVM, if so then please accept my apologies but i cant see how I can use this control without extending the functionality and i cant do that since StartTransition is private

I have no problems with the TransitioningContentControl. It works exactly like the standard control.

Newest caliburn.micro version + newest mahapps.metro version.
In ShellView.xaml:
<Controls:TransitioningContentControl x:Name="ActiveItem" RestartTransitionOnContentChange="True" />

And in my ShellViewModel I invoke
ChangeActiveItem(new MyViewModel(), true);

On a button click I invoke

ChangeActiveItem(new TestViewModel(), true);

and the content changes with transition.

Is your UpdateViewModel really instantiated?

@MoiraG I am using Caliburn.Micro v3.0.1 and MahApps v1.2.4

Yes my UpdateViewModel does get instantiated i have put a break point on the constructor to verify and the breakpoint gets hit. My UpdateViewModel is injected in my ShellViewModel as below since I am also using MEF as the IoC container.

[Import(typeof(UpdateViewModel))] public Lazy<IScreen> UpdateViewModel { get; set; }

I was using ActivateItem but have tried your suggestion as below:

this.ChangeActiveItem(this.UpdateViewModel.Value, true);

an i even tried

this.ChangeActiveItem(new UpdateViewModel(this._eventAggregator), true);

All it displays is the fully qualified typename of the ViewModel as per the screen grab below

image

From what you describe, asside from confirming the versioning my code should work in the same manner as yours. If it will make it easier my code is hosted on github bitbucket in a private repository that i can give access to if required

Maybe you havn't followed the view resolution conventions from caliburn.micro?
The folder structure respectively the namespace and the naming is important.

Do you have a usercontrol with the name UpdateView in the namespace Fusion.Windows.Ui.Updater.Views.UpdateView?

I will have a look on you repository but I can't make it tonight.

Wait a minute. I will give you a sample application. Maybe it helps.

@MoiraG Yes I have a Views folder with an UpdateView.xaml and a ViewModels folder with the UpdateViewModel as per the Caliburn conventions

image

here is a very small sample, which works for me

TestSample.zip

Last idea: How does your usercontrol looks like? I think caliburn finds the view but the binding shows only ToString of your Viewmodel

The UpdateView.xaml is:

<UserControl x:Class="Fusion.Windows.Ui.Updater.Views.UpdateView"
         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>
        <TextBlock Text="Hello World"></TextBlock>
    </Grid>
</UserControl>

and the UpdateView.xaml.cs is:

public partial class UpdateView : UserControl
{
    #region Constructors and Destructors

    /// <summary>
    /// Initializes a new instance of the <see cref="UpdateView"/> class.
    /// </summary>
    public UpdateView()
    {
        this.InitializeComponent();
    }

    #endregion
}

For completeness the ShellView.xaml is:

<controls:MetroWindow x:Class="Fusion.Windows.Ui.Updater.Views.ShellView"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                  xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
                  mc:Ignorable="d" Width="350"
                  WindowStartupLocation="CenterScreen"
                  ResizeMode="NoResize"
                  ShowCloseButton="False"
                  IsMaxRestoreButtonEnabled="False"
                  GlowBrush="{DynamicResource AccentColorBrush}"
                  BorderThickness="1"
                  SizeToContent="WidthAndHeight"
                  Style="{DynamicResource CleanWindowStyleKey}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="10" />
        </Grid.ColumnDefinitions>

        <controls:TransitioningContentControl Grid.Row="0" Grid.Column="1" Content="{Binding ActiveItem}" RestartTransitionOnContentChange="True" />
    </Grid>
</controls:MetroWindow>

I have had a look at your example which does indeed work, the only thing I am doing differently is i am changeing the active item in the OnActivate method, and I am bootstrapping in a slightly different way to you. I am using Main(string[]) because I wanted to wire up the AppDomain UnhandledException, and Application DispatcherUnhandledException before firing up WPF using App.Main(). The code below is from the OnStartup method of App.xaml.cs:

        var startupTask = new Task(
            () =>
                {
                    var bootstrapper = new AppBootstrapper();
                    Current.Dispatcher.Invoke(() => bootstrapper.Initialize());
                });

        startupTask.ContinueWith(
            t =>
                {
                    var shellViewModel = IoC.Get<ShellViewModel>();
                    IWindowManager windowManager;

                    try
                    {
                        windowManager = IoC.Get<IWindowManager>();
                    }
                    catch
                    {
                        windowManager = new WindowManager();
                    }

                    windowManager.ShowWindow(shellViewModel);
                },
            TaskScheduler.FromCurrentSynchronizationContext());

        startupTask.Start();

@MoiraG So i noticed that the only other thing that was different in the above was that I was using a {Binding ActiveItem}, I changed mine to use x:Name="ActiveItem" and that did the trick and the view loaded correctly.

I understand that Caliburn does the binding using x:Name but shouldn't that be the same whichever way you do it?

I don't know how caliburn.micro handles the " x:Name="ActiveItem". The problem with
Content="{Binding ActiveItem}" is, that the ContentControl doesn't knwo how to render the viewmodel behind ActiveItem.

If you create a datatemplate in your ressources it works:

<Window.Resources> <DataTemplate DataType="{x:Type viewModels:TestViewModel }"> <views:TestView></views:TestView> </DataTemplate> </Window.Resources>

If you are interested in how caliburn.micro handles the x:Name on contentcontrol you should open an issue there, or study the code from caliburn.micro :)

@punker76 I think the issue should be closed. It's not a problem with mahapps.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kshkrao3 picture kshkrao3  路  12Comments

webprofusion-chrisc picture webprofusion-chrisc  路  11Comments

AndrzejKl picture AndrzejKl  路  12Comments

benjamin275 picture benjamin275  路  17Comments

ghost picture ghost  路  18Comments