ViewModel resolution / attachement Problem: Can't get started with WP8

Topics: Getting Started
Jan 23, 2013 at 1:32 AM

Hey,

I'm totally new to Caliburn and MVVM and IoC architectures. I'm trying to set up my first WP8 app project, the MainPage gets displayed, but the MainPageViewModel constructor is never called and trying to bind an action results in a detail-less exception..

My Code:

Bootstrapper:

namespace MensaOnWP8
{
    public class MensaOnBootstrapper : PhoneBootstrapper
    {
        PhoneContainer container;

        protected override void Configure()
        {
            LogManager.GetLog = type => new DebugLogger(type);

            container = new PhoneContainer(RootFrame);

            container.RegisterInstance(typeof(INavigationService), null, new FrameAdapter(RootFrame));
            //container.RegisterPerRequest(typeof(TabViewModel), null, typeof(TabViewModel));
            container.RegisterSingleton(typeof(NearestMensaViewModel), "MainPageViewModel", typeof(NearestMensaViewModel));
            container.RegisterSingleton(typeof(MainPageViewModel), "MainPageViewModel", typeof(MainPageViewModel));
            
            //container.RegisterInstance(typeof(IPhoneService), null, new PhoneApplicationServiceAdapter(PhoneService));
            ConfigureConventions();            
        }


        protected void ConfigureConventions()
        {
            // Phone Controls (from the Caliburn Sample

            ConventionManager.AddElementConvention<Pivot>(ItemsControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
                (viewModelType, path, property, element, convention) =>
                {
                    if (ConventionManager.GetElementConvention(typeof(ItemsControl)).ApplyBinding(viewModelType, path, property, element, convention))
                    {
                        ConventionManager.ConfigureSelectedItem(element, Pivot.SelectedItemProperty, viewModelType, path);
                        ConventionManager.ApplyHeaderTemplate(element, Pivot.HeaderTemplateProperty, null, viewModelType);  //null added for HeaderTemplateSelector, maybe incorrect!
                        return true;
                    }
                    return false;
                };            

            ConventionManager.AddElementConvention<Panorama>(ItemsControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
                (viewModelType, path, property, element, convention) =>
                {
                    if (ConventionManager.GetElementConvention(typeof(ItemsControl)).ApplyBinding(viewModelType, path, property, element, convention))
                    {
                        ConventionManager.ConfigureSelectedItem(element, Panorama.SelectedItemProperty, viewModelType, path);
                        ConventionManager.ApplyHeaderTemplate(element, Panorama.HeaderTemplateProperty, null, viewModelType);//null added for HeaderTemplateSelector, maybe incorrect!
                        return true;
                    }
                    return false;
                };
        }

        protected override object GetInstance(Type service, string key)
        {
            return container.GetInstance(service, key);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return container.GetAllInstances(service);
        }

        protected override void BuildUp(object instance)
        {
            container.BuildUp(instance);
        }
    }
}

 

MainPageViewModel:

namespace MensaOnWP8
{
    sealed class MainPageViewModel : Conductor<IScreen>.Collection.OneActive
    {
        INavigationService navigationService;

        private readonly NearestMensaViewModel nearestMensaViewModel;

        public MainPageViewModel(INavigationService navigationService, NearestMensaViewModel nearestMensaViewModel)
        {
            this.navigationService = navigationService;
            this.nearestMensaViewModel = nearestMensaViewModel;
        }

        protected override void OnActivate()
        {
            base.OnActivate();
        }

        public void ItemsLoaded(object sender, RoutedEventArgs e)
        {
            Items.AddRange(new Screen[] { nearestMensaViewModel });
        }
    }
}

 MainPage

<phone:PhoneApplicationPage
    x:Class="MensaOnWP8.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="True"><!-- cal:Bind.Model="MainPageViewModel" doesnt help here -->

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">

        <!--Pivot Control-->
        <phone:Pivot Title="Happy Fuetterung!" cal:Message.Attach="[Event Loaded] = [Action ItemsLoaded( $source, $eventArgs )];" >
                       
        </phone:Pivot>

    </Grid>

</phone:PhoneApplicationPage>


Where am I wrong?

 

Jan 23, 2013 at 8:34 PM
Edited Jan 23, 2013 at 8:40 PM
public class MensaOnBootstrapper : PhoneBootstrapper
    {
        PhoneContainer container;

        protected override void Configure()
        {
            LogManager.GetLog = type => new DebugLogger(type);

            container = new PhoneContainer(RootFrame);

            //change
            container.RegisterPhoneServices();
           
            // not necessarily needed as singleton fyi. Could be a PerRequest<T>();
            container.Singleton<NearestMensaViewModel>(); 
            container.Singleton<MainPageViewModel>();



            ConfigureConventions();            
        }

...

 

cleaned, appears you are basing your attempt off some really old code,  I suggest refreshing and checking out the samples in the source.   

If this does help what is your folder structure and how are the files associated.  Views in folder Views and ViewModels in folder ViewModels etc..

hope this helps

Morgan.

Jan 23, 2013 at 9:20 PM

Hey,

I find it really a pity the docs are behind, coz I like CM's ideas, but using a not well documented framework in production (I thought of using it on WPF with a project I do at work, while refactoring it to a MVVM pattern) requres reconsideration.

I included your change, it did change something. Now the thing crashes with uncommented "InvalidOperationException" once  cal:Bind.Model="MainPageViewModel" is in MainPage's header. It seems to happen after "GetInstance()" call with type=null and key="MainPageViewModel" (!). If I remove Bind.Model from MainPage's header, GetInstance is never colled and VM never initialized.

Jan 24, 2013 at 3:31 AM

I encountered some issues when I was doing this as well, but I was also trying to add in MEF. :) I've got a sample for Win8 and WinPhone8 you can take a look at here:

http://dl.dropbox.com/u/99625565/Win8MEFCaliburn.zip

If you're not using MEF, then you should be able to revert to the in-box IOC fairly easily.

One thing that tripped me up was to make sure that I had Navigation Page: Views/MainPage.xaml as the correct location in WMAppManifest.xml when I moved MainPage under the Views folder to follow the standard conventions.

If you can provide your code or an example project, then it'd be easier to determine where the issue is.

 

I'd also like to mention that the docs may be a bit behind on some things, but remember someone isn't being paid to provide this and that usually happens in those cases. The docs do give a very good overview and in a lot of cases it just takes some syntax tweaks to match with the current code. I find myself opening up the CM source files while I'm debugging and stepping through them to find out more about how the framework actually works and in doing that, I have felt confident enough that CM has been in about 4 production apps I've worked on.(One of which is still using Caliburn(full) built to support .Net3.5 WPF).

Jan 24, 2013 at 3:56 AM
Edited Jan 24, 2013 at 4:03 AM
Maverick89 wrote:

Hey,

I find it really a pity the docs are behind, coz I like CM's ideas, but using a not well documented framework in production (I thought of using it on WPF with a project I do at work, while refactoring it to a MVVM pattern) requres reconsideration.

I included your change, it did change something. Now the thing crashes with uncommented "InvalidOperationException" once  cal:Bind.Model="MainPageViewModel" is in MainPage's header. It seems to happen after "GetInstance()" call with type=null and key="MainPageViewModel" (!). If I remove Bind.Model from MainPage's header, GetInstance is never colled and VM never initialized.


you won't need cal:Bind.Model in the xaml unless you want to do View First coding... your choice there.

What is the structure of your project, its vital information.  Not that it should make a difference but make the class for MainPageViewModel public and not sealed.

when bootstrapper starts it is assuming that the "mainpage" is in the root of the project (because that is how wp7/8 are setup with the manifest file), so then it looks for the associated viewmodel. The starting view MainPage is located in any other folder and the manifest file for the application isn't changed in Properties folder then it would not work as you expect.

Jan 26, 2013 at 2:11 AM
iamdragonwolf wrote:

I'd also like to mention that the docs may be a bit behind on some things, but remember someone isn't being paid to provide this and that usually happens in those cases. The docs do give a very good overview and in a lot of cases it just takes some syntax tweaks to match with the current code. I find myself opening up the CM source files while I'm debugging and stepping through them to find out more about how the framework actually works and in doing that, I have felt confident enough that CM has been in about 4 production apps I've worked on.(One of which is still using Caliburn(full) built to support .Net3.5 WPF).

 

I didnt mean to criticize, it's open source software and I find it really great people are investing so much time and effort to share it for free. I'd love to contribute if I ever had enough time (not exactly something a tech student usually has, huh? ;) ) Maybe someday.

 

I didn't find any striking differences between your test project and mine. I've moved my VMs and Views into the according Views- and ViewModels namespaces and folders, and I also recompiled and inserted my PDBs for CM so I can debug inside lib.

What I get now is a TargetInvocationException somewhere after "InitializeComponents" of the MainPage. Here is the callstack:

===

> System.Windows.ni.dll!System.Windows.Threading.DispatcherOperation.Invoke() Unknown
System.Windows.ni.dll!System.Windows.Threading.Dispatcher.Dispatch(System.Windows.Threading.DispatcherPriority priority) Unknown
System.Windows.ni.dll!System.Windows.Threading.Dispatcher.OnInvoke(object context) Unknown
System.Windows.ni.dll!System.Windows.Hosting.CallbackCookie.Invoke(object[] args) Unknown
System.Windows.RuntimeHost.ni.dll!System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(System.IntPtr pHandle, int nParamCount, System.Windows.Hosting.NativeMethods.ScriptParam* pParams, System.Windows.Hosting.NativeMethods.ScriptParam* pResult) Unknown
 ==

any my sources: https://dl.dropbox.com/u/20486059/MensaOnWP8.7z

Jan 26, 2013 at 9:10 AM
Edited Jan 26, 2013 at 9:11 AM

That exception is caused by not having the Navigation Page correct in the Properties->WMAppManifest.xml.

Change it to Views/MainPage.xaml

That's what I mentioned tripped me up as This is my first time coding with Windows Phone.

Jan 26, 2013 at 9:39 AM

So a couple things I found after making the fix above.

Your ViewModels classes are not public.

You try registering the INavigationService twice.

container.RegisterPhoneServices() in your bootstrapper does it already, so you can remove the line below that call that tries doing it again.

 

My changes are here: http://dl.dropbox.com/u/99625565/MensaOnWP8.7z

Feb 11, 2013 at 3:51 PM
Thank you very much! I forgot Caliburn is an external assembly and needs Vmodels to be public. How dumb of me :)