MVVM and ScatterView

This article was originally posted on TechNet Wiki.

ScatterView provides a simple way to create applications that enhance the Microsoft PixelSense experience. It is a common way to visualize content that can be manipulated freely by user.

Figure 1 ScatterView with some different ScatterViewItems

All those who have tried to bind a ScatterView to a collection of objects ended up noticing that a ScatterViewItem will wrap every generated item.

Binding UI controls to collections exposed by the ViewModel is one of the principle at the base of the MVVM pattern and I like to apply it to Surface SDK apps too.

I would like to use the ScatterView in an MVVM context; this would mean bind it to a collection of different objects exposed by the ViewModel that would be represented in different way, just like in Figure 1.

There are two problems to overcome to get the result:

  • How to apply the appropriate template to each kind of object respecting the MVVM pattern

Once we solved this problem, we will deal with

  • Correctly sizing the generated ScatterViewItems

Let’s face the problems one by one.

How to apply the appropriate template to each kind of object respecting the MVVM pattern

I saw many apps where a common workaround for this problem is to expose (from ViewModel) a collection of ScatterViewItems (or derived from).

This is something acceptable but we are coupling the ViewModel with View because in this scenario the ViewModel would reference Surface SDK 2.0 assemblies.

A solution to this problem would be using a DataTeplateSelector: In this way, we will be able to apply different template depending to the type of item:

Imagine having an interface:

    public interface ISearchable
    {

    }

And some object implementing it:

    public class Item : ObservableObject, ISearchable
    {
        // concrete implementation..
    }

    public class SearchBarViewModel : ViewModelBase, ISearchable
    {
        // concrete implementation.
    }

    public class SearchResultViewModel : ViewModelBase, ISearchable
    {
        // concrete implementation..
    }

In this way, thaks to polymorphism,  we can expose a collection of ISearchable:

    public class SurfaceShellViewModel : ViewModelBase
    {
        public ObservableCollection<ISearchable> SearchableCollection
        {
            get { return _searchableCollection; }

            set

            {
                _searchableCollection = value;
                RaisePropertyChanged("SearchableCollection");
            }
        }
    }

And we can bind it to the ScatterView:

<s:SurfaceWindow xmlns declaration omitted>
    <s:SurfaceWindow.DataContext>
        <vm:SurfaceShellViewModel />
    </s:SurfaceWindow.DataContext>
    <l:DragDropScatterView ItemsSource="{Binding Path=SearchableCollection}" >
    </l:DragDropScatterView>
</s:SurfaceWindow>

Now what we need is a way to instruct the ScatterView to apply different template depending on the concrete type of object (in our case Item, SearchBarViewModel or SearchResultViewModel).

The simplest way to do this is to use a DataTemplateSelector. The DataTemplateSelector has a single method to override: SelectTemplate(object item, DependencyObject container). In this method we decide which DataTemplate the ScatterView have to choose based on the type of object passed.

The following sample demonstrate how to implement it:

public class ScatterViewDataTemplateSelector : DataTemplateSelector

    {
        public DataTemplate SearchBarTemplate { get; set; }
        public DataTemplate SearchResultTemplate { get; set; }
        public DataTemplate ItemDataTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            DataTemplate retVal = null;

                if (item is SearchBarViewModel)
                    retVal = SearchBarTemplate;
                else if (item is SearchResultViewModel)
                    retVal = SearchResultTemplate;
                else if (item is Item)
                    retVal = ItemDataTemplate;

                return retVal;
        }
    }
 
<s:SurfaceWindow

xmlns declaration omitted>

<s:SurfaceWindow.DataContext>

<vm:SurfaceShellViewModel />

</s:SurfaceWindow.DataContext>

<l:DragDropScatterView ItemsSource="{Binding Path=SearchableCollection}" >

<s:ScatterView.ItemTemplateSelector>

<ts:ScatterViewDataTemplateSelector>

<ts:ScatterViewDataTemplateSelector.ItemDataTemplate>

<DataTemplate>

<Image Source="{Binding Url}" />

</DataTemplate>

</ts:ScatterViewDataTemplateSelector.ItemDataTemplate>

<ts:ScatterViewDataTemplateSelector.SearchBarTemplate>

<DataTemplate>

<v:SearchBar />

</DataTemplate>

</ts:ScatterViewDataTemplateSelector.SearchBarTemplate>

<ts:ScatterViewDataTemplateSelector.SearchResultTemplate>

<DataTemplate>

<v:SearchResultView />

</DataTemplate>

</ts:ScatterViewDataTemplateSelector.SearchResultTemplate>

</ts:ScatterViewDataTemplateSelector>

</s:ScatterView.ItemTemplateSelector>

</l:DragDropScatterView>

</s:SurfaceWindow>

Thanks to the DataTemplateSelector we was able to bind a ScatterView with a common collection of objects, we was also able to instruct the ScatterView to apply different template depending on the object type and we have also have the ViewModel decoupled from the View.

You can still reuse your ViewModel, it’s completely decoupled you can even use it for a WinRT version of the app!

At this point if you try to start the app to see the result you will notice that that the size set for added ScatterViewItems could be wrong. Here we come to the second problem we will solve to achieve our result.

Correctly sizing the generated ScatterViewItems

When ScatterViewItems are added trough a data bind it could be hard to set the appropriate size end eventually to apply a style to the ScatterviewItem.

A workaround is to create a base view from witch will inherit every View used as DataTemplate for the Scatterview:

    public class BaseView : UserControl
    {
        public BaseView()
        {
            this.Loaded += new System.Windows.RoutedEventHandler(BaseView_Loaded);
            this.Unloaded += new System.Windows.RoutedEventHandler(BaseView_Unloaded); 
        }

        void BaseView_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            var svi = VisualHelper.GetVisualAncestor<ScatterViewItem>(this);
            if (svi != null)
            {
                svi.Height = this.ActualHeight;
                svi.Width = this.ActualWidth;

                ResourceDictionary LibrayStiles = new ResourceDictionary();
                LibrayStiles.Source = new Uri("/DataTemplateSelector;component/Resources/Theme.xaml", UriKind.RelativeOrAbsolute);

                if(this is SearchBar)
                    svi.Style = (Style)LibrayStiles["SearchInScatterViewItemStyle"];
                else if (this is SearchResultView)
                    svi.Style = (Style)LibrayStiles["LibraryContainerInScatterViewItemStyle"];
            }
        }

        void BaseView_Unloaded(object sender, System.Windows.RoutedEventArgs e)
        {

            this.Loaded -= new System.Windows.RoutedEventHandler(BaseView_Loaded);
            this.Unloaded -= new System.Windows.RoutedEventHandler(BaseView_Unloaded);
        }
    }

In this way we are setting the appropriate height and width to the ScatterViewItem and we are also applying the correct style.

You can download a full working implementation on the MSDN Code Gallery - MVVM and ScatterView

 

Ultimi Post

Discalmer

Articles and content of this blog aren't and shouldn't be interpreted as professional advice or opinions. Author writes on a personal basis and as part of their own research, experimentation and knowledge. Opinions expressed aren't in any way attributable to professional affiliations / institutional thereof or to opinions that the author express as part of their roles / positions that may be quite different from that reported here.