Use with a MVVM Framework

Mar 13, 2013 at 7:22 PM
Hi,

First of all, thanks for sharing this awesome piece of code.

There is a way to use a MVVM Framework? I would like to use Caliburn.

As I'm not proficient with MVVM and WPF, I can't discover by myself.


Thanks
Coordinator
Mar 13, 2013 at 10:26 PM
Yes, you should be able to use any MVVM framework you like. Time permitting, I'll create an MVVM sample soon.
Mar 14, 2013 at 11:35 AM
Edited Mar 14, 2013 at 11:37 AM
I made a Caliburn.Micro content loader which I specify on the window, the content loader binds a viewmodel to the view to allow Caliburns conventions and binding to work. The only issue I've noticed with this is that the content load doesn't use the ContentLoader, instead it bypasses it somehow.

Here's the code if it helps.
public class CaliburnContentLoader : DefaultContentLoader
{
    protected override object LoadContent(Uri uri)
    {
        var content = base.LoadContent(uri);
        if (content == null)
            return content;

        var vm = Caliburn.Micro.ViewModelLocator.LocateForView(content);
        if (vm == null)
            return content;

        if (content is DependencyObject)
        {
            Caliburn.Micro.ViewModelBinder.Bind(vm, content as DependencyObject, null);
        }
        return content;
    }
}
You can then use the ModernWindow.ContentLoader property to specify that loader.
Coordinator
Mar 14, 2013 at 1:54 PM
Very nice!

What do you mean with the content load doesn't use the ContentLoader?
Mar 14, 2013 at 2:27 PM
Yeah.. that doesn't make much sense..
What I meant is that when you specify ModernWindow.ContentSource and run the application the window loads the specified page but LoadContent() gets skipped.

My workaround is to specify ContentSource="Empty.xaml" and when the Empty page loads it finds the frame and navigates onto my apps first page which does make a call to LoadContent().

Ben
Coordinator
Mar 14, 2013 at 2:39 PM
That is most likely because the ContentSource property change handler is executed before the ContentLoader has been set. The DefaultContentLoader is then used to load the initial page.
Mar 14, 2013 at 5:27 PM
kozw wrote:
That is most likely because the ContentSource property change handler is executed before the ContentLoader has been set. The DefaultContentLoader is then used to load the initial page.
Yeah spot on, for some reason I was binding the ContentLoader property to an IContentLoader on my view model, switched that to a static resource method and problem is fixed. Thanks!
Mar 14, 2013 at 6:42 PM
First of all, thanks you both for every thing. I'll try this approach on this afternoon, and I give a feedback.

Maybe I have other questions about it.

Thanks
Mar 15, 2013 at 12:34 AM
@TheDuke2k How are setting the ContentLoader property? Using a XAML ResourceDictionary?
@kozw I'm not sure but we could change the DefaultContentLoader class to have an static property with the default IContentLoader instance, what about that?

I did it work with Caliburn.
Mar 15, 2013 at 12:39 AM
Yeah, StaticResource works well.

App.xaml
<Application.Resources>
    <ResourceDictionary>
        <local:CaliburnContentLoader x:Key="CaliburnContentLoader" />
    </ResourceDictionary>
</Application.Resources>
Window.xaml
<mui:ModernWindow
    ...
    ContentLoader="{StaticResource CaliburnContentLoader}">
Mar 16, 2013 at 5:26 PM
Edited Mar 16, 2013 at 5:39 PM
Thank you @TheDuck2k: Mission Accomplished.

I have a new question now, maybe someone can help with an experience.

In my project I have a ViewModel (ExportDataViewModel) that receives an instance of service (IExportData) to do some job, each implementation does the same job for different entities. Example: ExportDataViewModel receives a CompanyExportData, EmployeeExportData.

In my current Windows Form projects using Caliburn.Micro, I'm using "Action" triggering feature to create instances of ExportDataViewModel based on the button clicked.
Each "Action" creates a new ExportDataViewModel with the appropriated service and calls IConductor.ActivateItem to show the View based on the ViewModel instance.
    public void ExportCompany()
    {
        var viewModel = new ExportDataViewModel(() => new CompanySynchronizer());
        this.ActivateItem(this.viewModel);
    }
    public void ExportEmployee()
    {
        var viewModel = new ExportDataViewModel(() => new EmployeeSynchronizer());
        this.ActivateItem(this.viewModel);
    }
    
I'm now trying to migrate that to Modern UI.
I think I can't do that on Moder UI, because I must have a Source property with a URI to a View, and in this case I need to have a URI to the same View.
Do you think there a way to do that using Modern UI?

UPDATE: I'm figuring out that I can use a URI with a "#HashTag" and then on the ContentLoader.LoadContent method put some logic to load the appropriated ViewModel instance, is this approach correct?

Thanks you Guys.
Coordinator
Mar 16, 2013 at 10:59 PM
Edited Mar 16, 2013 at 11:00 PM
You can use uri fragments (value after #) to have different links refer to the same page. Keep in mind that at this point in time this also implies that a new page instance is created for each uri. I'm currently working on improving the navigation stack and adding support for fragment navigation.
Mar 17, 2013 at 12:30 AM
Thanks for the answer.
If I use URI Fragments, I will need to put some logic on my CustomContentLoader to instantiate different ViewModels by fragment, is this approach I should use?
Coordinator
Mar 17, 2013 at 8:06 PM
With the updated navigation stack (available soon), you do not need to put the logic in an IContentLoader implementation; the content itself can receive navigation events.
Mar 18, 2013 at 11:57 PM
Very nice... May I help you with something to build new navigation stack?
Coordinator
Mar 19, 2013 at 12:17 AM
The new navigation stack has been checked in moments ago. Just get the latest and compile. A new release is pending.
Mar 19, 2013 at 2:05 PM
Very nice, I did a test local and every goes fine using the ModernFrame control.
There is any way to put the same events of ModernFrame on the ModernTab? Or I missed something.
Coordinator
Mar 19, 2013 at 9:13 PM
Why would you need the navigation events on the ModernTab?
Mar 19, 2013 at 10:26 PM
Maybe I missed something.

I'm using the ModernTab on my view and I have 4 links on my list to the same view using a Fragment, to make each one "different".
What I want is intercept the FramegmentNavigation event to instanciate an appropriated ViewModel to the Fragment and after that change the DataContext.

I just did the same of above using a ModernFrame and 3 buttons with Command=NavigationCommands.GoToPage and a Fragment on the URI.

Maybe I should doing this another way.
Coordinator
Mar 19, 2013 at 10:30 PM
Edited Mar 19, 2013 at 10:31 PM
You should implement the new IContent interface for your views that are loaded in a ModernFrame. The IContent interface is optional. Once frame content implements the IContent interface, the appropiate navigation methods are invoked by the ModernFrame, this includes a FragmentNavigation method.

This IContent interface is part of the updated navigation framework and is available in the latest source code (not released yet). I'm aware that documentation is lacking, will update docs asap. In the meantime take a look at the new ModernFrame sample page in the sample app in the latest source.
Mar 19, 2013 at 10:45 PM
Edited Mar 19, 2013 at 10:51 PM
This is my first try using buttons and ModernFrame, and it works beautiful.
<mui:ModernFrame x:Name="Frame" Source="/Views/ExportDataView.xaml" 
                            Width="480" Height="200" Padding="4"
                            ContentLoader="{StaticResource CaliburnContentLoader}"
                            cal:Message.Attach="[Event FragmentNavigation] = [Action Frame_FragmentNavigation($source, $eventArgs)]">
</mui:ModernFrame>           
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
    <Button Content="companies" Command="NavigationCommands.GoToPage" CommandParameter="/Views/ExportDataView.xaml#companies" CommandTarget="{Binding ElementName=Frame}" Margin="8,0,0,0"/>
    <Button Content="customers" Command="NavigationCommands.GoToPage" CommandParameter="/Views/ExportDataView.xaml#customers" CommandTarget="{Binding ElementName=Frame}" Margin="8,0,0,0"/>
</StackPanel>
But what I want is
<mui:ModernTab Layout="List" ContentLoader="{StaticResource CaliburnContentLoader}">
    <mui:ModernTab.Links>
            <mui:Link DisplayName="companies" Source="/Views/ExportDataView.xaml#companies" />
            <mui:Link DisplayName="customers" Source="/Views/ExportDataView.xaml#customers" />
    </mui:ModernTab.Links>
</mui:ModernTab>
You're saying that I should implement IContent on the ExportDataView?
And I should put my logic on the ExportDataView to instantiate the CustomerExportDataViewModel for "#customer" and CompanyExportDataViewModel for "#company"?

Am I correct to understand your answer?

P.S: All my attempts are being done using Caliburn.Micro, but I'm new to WPF and MVVM world.
Coordinator
Mar 19, 2013 at 10:53 PM
That is correct. ExportDataView should implement IContent and handle the OnFragmentNavigation method to instantiate the various viewmodels based on the fragment value.