Lifecycle participation of custom user control bound vms

Jul 28, 2011 at 4:52 AM

I have a WP7 project, I'm wondering about the best way to have custom user control views and their vms participate in the lifecycle of the containing view model associated with a PhoneApplicationPage. For example, here we have a pivot with conventional binding to user controls which act as view for each pivot item.

The problem is that OnActivate, OnDeactivate and OnViewReady do not get called for the VMs relating to these pivot item bound user controls;(

    <Grid x:Name="PagePivot" Grid.Row="1" Style="{StaticResource GridPagePivot}">
            <controls:Pivot Title="{Binding Path=AppResources.PageHeaderShare, Source={StaticResource AppResourcesInfo}}" 
                x:Name="SharePivot" SelectedIndex="{Binding SharePivotSelectedIndex}" Style="{StaticResource PivotDefault}"
                cm:Message.Attach="[Event LoadedPivotItem] = [Action SharePivotItemLoaded($eventargs)]">
                <controls:PivotItem Header="{Binding Path=AppResources.SharePivotHeaderEmail, Source={StaticResource AppResourcesInfo}}"
                    x:Name="EmailPivotContent" Style="{StaticResource PivotItemDefault}">
                </controls:PivotItem>
                <controls:PivotItem  Header="{Binding Path=AppResources.SharePivotHeaderTwitter, Source={StaticResource AppResourcesInfo}}"
                    x:Name="TwitterPivotContent" Style="{StaticResource PivotItemDefault}">
                </controls:PivotItem>
                <controls:PivotItem Header="{Binding Path=AppResources.SharePivotHeaderFacebook, Source={StaticResource AppResourcesInfo}}"
                    x:Name="FacebookPivotContent" Style="{StaticResource PivotItemDefault}">
                </controls:PivotItem>
            </controls:Pivot>
        </Grid>

The only overrides that seem to occur are OnViewAttached and OnViewLoaded, but these aren't always guaranteed to run (e.g. back button press).

What's the best way to have the custom user control linked VMs associate themselves with the lifecylce of the containing view model associated with a PhoneApplicationPage?

 

 

Coordinator
Jul 28, 2011 at 11:45 AM

If you bound your Pivot to a Conductor<T>.Collection.OneActive, where each conducted item corresponded to a PivotItem and each conducted item inherited from Screen, it should work. In the code above, you are using a view-first strategy which opts out of lifecycle since that can only work in a view-model first context (reliably at least).

Jul 28, 2011 at 11:42 PM

gotcha, finally got a scenario to move away from screen it seems;) i'll include this in my tech ed talk if i can get it working as it seems like the perfect wp7 demo to show the conductor stuff working. 

Jul 29, 2011 at 12:27 AM
Edited Jul 29, 2011 at 12:29 AM

ok, that's awesome. i made my phoneapplicationpage view model a Conductor<T>.Collection.OneActive like you said. I've already got my pivot item vms as screens.

My only remaining question is whether i'm wiring things up in the best way. Below is the ShareViewModel (the pivot xaml in my first post is from ShareView. My PageViewModelBase inherits from Conductor<ControlViewModelBase>.Collection.OneActive and the ControlViewModelBase is the base class for all my pivot items (and inherits from Screen). 

If i'm understanding things right, i need to add the pivot item view models to the page conductor, and i'm doing that below in the constructor. I'm then hooking off the pivot item changed event and calling ActivateItem and passing in the active Pivot Item vm?

You can also see the other code which is setting the pivot item vm to the content property of the pivot item only when it first comes into view (for performance, as i want to set the vm as late as possible). 

Does this look correct or is there some further magic i could be making use of here? :)

using System.Linq;
using Microsoft.Phone.Controls;

namespace Client.Project.Phone.ViewModels {

    public class ShareViewModel : 
        PageViewModelBase
    {

        #region Private Members

        private ShareEmailPivotViewModel _emailPivotContent;
        private ShareTwitterPivotViewModel _twitterPivotContent;
        private ShareFacebookPivotViewModel _facebookPivotContent;
        private ShareEmailPivotViewModel _emailPivotViewModel;
        private ShareTwitterPivotViewModel _twitterPivotViewModel;
        private ShareFacebookPivotViewModel _facebookPivotViewModel;

        #endregion

        #region Constructor

        public ShareViewModel(
            ShareEmailPivotViewModel emailPivotViewModel, ShareTwitterPivotViewModel twitterPivotViewModel,
            ShareFacebookPivotViewModel facebookPivotViewModel, ViewModelWorker viewModelWorker)
            : base(viewModelWorker)
        {
            _emailPivotViewModel = emailPivotViewModel;
            _twitterPivotViewModel = twitterPivotViewModel;
            _facebookPivotViewModel = facebookPivotViewModel;
            base.Items.Add(_emailPivotViewModel);
            base.Items.Add(_twitterPivotViewModel);
            base.Items.Add(_facebookPivotViewModel);
        }

        #endregion

      

        #region Pivot Bindings

        /// 
        /// Handler for pivot item change
        /// 
        public void SharePivotItemLoaded(PivotItemEventArgs e)
        {
            if (e.Item.Name == "EmailPivotContent")
            {
                if (this.EmailPivotContent == null)
                {
                    this.EmailPivotContent = _emailPivotViewModel;
                }
                ActivateItem(_emailPivotViewModel);
            }
            else if (e.Item.Name == "TwitterPivotContent")
            {
                if (this.TwitterPivotContent == null)
                {
                    this.TwitterPivotContent = _twitterPivotViewModel;
                }
                ActivateItem(_twitterPivotViewModel);
            }
            else if (e.Item.Name == "FacebookPivotContent")
            {
                if (this.FacebookPivotContent == null)
                {
                    this.FacebookPivotContent = _facebookPivotViewModel;
                }
                ActivateItem(_facebookPivotViewModel);
            }
        }

        public ShareEmailPivotViewModel EmailPivotContent
        {
            get { return _emailPivotContent; }
            set
            {
                _emailPivotContent = value;
                NotifyOfPropertyChange(() => EmailPivotContent);
            }
        }

        public ShareTwitterPivotViewModel TwitterPivotContent {

            get { return _twitterPivotContent; }
            set
            {
                _twitterPivotContent = value;
                NotifyOfPropertyChange(() => TwitterPivotContent);
            }
        }

        public ShareFacebookPivotViewModel FacebookPivotContent
        {
            get { return _facebookPivotContent; }
            set
            {
                _facebookPivotContent = value;
                NotifyOfPropertyChange(() => FacebookPivotContent);
            }
        }

        #endregion

    }

}

Jul 29, 2011 at 2:48 AM

Just to note i have looked 2 way binding on the activitem against a datatemplate as refereneced here, but this isn't as performant on device as i'd like. so hence the manual activation on the pivot item changed event.

http://blog.caraulean.com/page/3/

Aug 1, 2011 at 10:33 AM

when i say performance i'm talking about the first swipe in any direction on any pivot that has been set up as per the blog post i linked to, doesn't mattter what you put in the pivots, and it doesn't seem to reoccur. There is this brief but noticeable pause as the second pivot changes. I love the simplicity of that code, and would love to switch to it if i could just figure out why there was that little swipe delay;) 

If i take away x:Name="Items" and just do ItemsSource={Binding Items} (which of course is silly) i have the pivots bound and displaying the type name of the vms on each pivot as you would expect, without any delay. 

will keep digging... 

Aug 1, 2011 at 11:25 AM

just checked on my focus with the latest 1.2 hello wp7 sample. It's barely noticeable but there on the first swipe with the very simple pivot that's in there, but this does cause problems with real world controls, and i wouldn't personall find the UX acceptable to use this approach for a wp7 app at this time so am keen to get the pivot behaviour super smooth.

Coordinator
Aug 1, 2011 at 1:47 PM

Does it go away if you use explicit bindings instead of conventions?

Aug 6, 2011 at 5:32 AM

Hi,

The current approach i have used explicit bindings but i am declaring each pivot item and then binding the VM to those pivot items via x:Name convention as above. 

I tried to bind directly to the ItemsSource with the Items collection below which obviously doesn't work, as the corresponding views for each pivot item are not resolved. 

Can you provide a hint as to how i can most easily bind the conductors Items to the pivot without using the x:Name convention on the Pivot control? 

 

   <controls:Pivot Title="{Binding DisplayName}" ItemsSource="{Binding Items}"
                x:Name="ThingPivot" SelectedItem="{Binding ActiveItem}">
                <controls:Pivot.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding DisplayName}" />
                    </DataTemplate>
                </controls:Pivot.HeaderTemplate>
            </controls:Pivot>

Oct 3, 2011 at 8:50 AM

I am finding the same issue with Pivot and using the x:Name convention. But it's not so bad if I do all my loading with coroutines - swiping AWAY from the Pivot page while the data is loading is another story! I am seeing the same problem with the OnViewReady not being called, but I can do the loading in OnActivate (but this could be my problem)

Using (ISubScreen is IScreen and the Pivot pages are all of type Screen):

public class MenuViewModel : MultipageViewModel<IMenuSubScreen>
{
	public MenuViewModel(IEnumerable<IMenuSubScreen> screens)
		: base(screens)
	{ }
}
public class MultipageViewModel<T> : Conductor<T>.Collection.OneActive where T : ISubScreen
{
	public MultipageViewModel(IEnumerable<T> screens)
	{
		Items.AddRange(screens.OrderBy(s => s.Ordinal));
	}
}

I also have found that using x:Name="MyDataSource" on a ListBox tends to do funny things with Data Virtualisation - in that it requests all the indexes at once, rather than my defined cache of view models (to bind to views with a view locator - I am doing nothing clever). But of course, I may have done something wrong for this to be happening ;)