1

Here is my situation.

ViewModelA {  
    ObservableCollection<Items> ItemsA  
    ObservableCollection<ViewModelB> ViewModelBs
}

View A with the datacontext set to ViewModel A ViewA has a panorama with a listbox, panels, textblocks etc with the items source for the listbox bound to ItemsA

I want to add other panorama items at runtime to the panorama control with a commond data template (listbox, textblock...etc).. Each panorama item will be bound at run time to each ViewModelB in the ViewModelBs collection.

I am not oppose to doing some codebehind stuff for this as I am not a strict mvvm purist.. but the solution could be elegant if I could specify control and data templates and make this work. I am kind of new to wpf/xaml and trying to break in to those technologies by writing a wp7 app.. using the mvvm light framework.. Eventually, i want my dynamically generated panorama items/list box content fire off relay commands on the viewmodel they are being bound to...

Here is some code snippet that i tried unsuccessfully to work. Hope it provides some idea..

<phone:PhoneApplicationPage.Resources>
    <Style x:Key="PanoramaItemStyle" TargetType="ContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Grid x:Name="ContentGrid">
                        <controls:PanoramaItem x:Name="ItemLocationPanoramaItem" Header="{Binding TagName}">
                            <StackPanel >
                                <ListBox  x:Name="ItemLocatorsList" ItemsSource="{Binding ItemLocators}" Height="496" SelectedItem="{Binding SelectedItemLocation, Mode=TwoWay}" >
                                    <Custom:Interaction.Triggers>
                                        <Custom:EventTrigger EventName="SelectionChanged">
                                            <GalaSoft_MvvmLight_Command:EventToCommand x:Name="SelectionChangedEvent" Command="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext.GoToEditItemLocatorCommand}" PassEventArgsToCommand="True"/>
                                        </Custom:EventTrigger>
                                    </Custom:Interaction.Triggers>
                                    <ListBox.ItemsPanel>
                                        <ItemsPanelTemplate >
                                            <StackPanel Orientation="Vertical"  ScrollViewer.VerticalScrollBarVisibility="Auto" />
                                        </ItemsPanelTemplate>
                                    </ListBox.ItemsPanel>
                                    <ListBox.ItemTemplate>
                                        <DataTemplate>
                                            <StackPanel> 
                                                    <StackPanel Orientation="Horizontal" Margin="0,0,0,17">
                                                        <StackPanel Width="311">
                                                            <TextBlock Text="{Binding Path=Item.Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
                                                            <TextBlock Text="{Binding Path=Location.Description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                                                        </StackPanel>
                                                    </StackPanel> 
                                            </StackPanel>
                                        </DataTemplate>
                                    </ListBox.ItemTemplate>
                                </ListBox>
                            </StackPanel>
                        </controls:PanoramaItem>
                        <ContentPresenter/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Foreground" Value="White"/>
    </Style> 
</phone:PhoneApplicationPage.Resources>


Codebehind: private LocationGroupsViewModel viewModel = null;

    public LocationGroups()
    {
        InitializeComponent(); 
        LocationGroupsPanaroma.DefaultItem = LocationGroupsPanaroma.Items[0];
         viewModel = this.DataContext as LocationGroupsViewModel;
         CreateDynamicPanaromaItems();
    }


    private void CreateDynamicPanaromaItems()
    {
        foreach (Model.LocationGroup group in viewModel.LocationGroups)
        {
            if (group.TotalItems > 0)
            {
                PanoramaItem pi = new PanoramaItem();
                pi.Header = group.Name;
                pi.Orientation = System.Windows.Controls.Orientation.Horizontal;
                ItemLocationListViewModel itemLocationViewModel = viewModel[group.LocationGroupId];
                pi.DataContext = itemLocationViewModel;
                pi.Style = Resources["PanoramaItemStyle"] as Style;
                LocationGroupsPanaroma.Items.Add(pi);

            }
        }

    }

Edit

ViewModel A has

Items collection

Collection of ViewModelBs

panaroma data context set to viewmodelA

panaroma item - Statitically created in xaml to some Items collection in ViewModelA

    This pan item has a list box



panaroma items --- to be bound to collection of viewmodelbs

    These pan items should each have a listbox which is selectable
                  and  bound to some collection in View Model B and fires commands on    selection         changed to viewModelB. Currently using the galasoft eventtocommand to hook the selection changed on the
    list box to a relay command. The problem is that this eventtommand should have the viewmodel as its data context and the not the collection (bound to the listbox) within viewmodel. 
siva
  • 53
  • 2
  • 7
  • I can get this to work by using a style but the event to commands doesn't seem to work within a style any more. It appears i need to change the data context in the binding to my parent view model.. tried a few things and could not get it to work Need this to fire > – siva Jun 28 '11 at 05:58
  • Please note that stack overflow is a site that helps you find the solution yourself by pointing you in the right direction. It is not a site that solves concrete programming tasks. I.e. it is a tool for learning! You already posted a nearly identical question and usually - if the answer does not quite fit your problem - you edit your original post, clearly marking the edit. The more information in the post the more likely you will get a suitable answer. On the subject I will have a look at how you can solve the problem with templates - but I – AxelEckenberger Jun 28 '11 at 16:27
  • @obalix I am not expecting concrete solutions.. I understand the purpose. Kind of new to stackoverflow and did not know.. i could edit the original post.... :-) – siva Jun 28 '11 at 19:10

1 Answers1

3

OK, finally got time to answer the question. The proposed solution does not require any code behind and only relys on MVVM concepts and data binding.

Conceptionally the Panorama control is an ItemPresenter (it inherits from ItemsPresenter), i.e. you can bind the ItemsSource to a list that contains items that repesent your PanoramaItems.

To render your PanoramaItem you will have to provide templates for Panorama.HeaderTemplate and Panorama.ItemTemplate. The DataContext inside the templates is the ViewModel that represents your PanoramaItem. If this ViewModel contains an item list you now can use it to generate the ListBoxes you were looking for.

And here is the sample ...

ViewModelLocator.cs

using GalaSoft.MvvmLight;

namespace WP7Test.ViewModel
{
    public class ViewModelLocator
    {
        private static MainViewModel _main;

        public ViewModelLocator()
        {
    if (ViewModelBase.IsInDesignModeStatic) {
        // Create design time services and viewmodels
    } else {
        // Create run time services and view models
    }
            _main = new MainViewModel();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public MainViewModel Main
        {
            get
            {
                return _main;
            }
        }
    }
}

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        this.Items = new ObservableCollection<ItemViewModel>();

        if (IsInDesignMode) {
            // Code runs in Blend --> create design time data.
        } else {
            // Code runs "for real"
        }
        this.LoadData();
    }

    #region [Items]

    public const string ItemsPropertyName = "Items";

    private ObservableCollection<ItemViewModel> _items = default(ObservableCollection<ItemViewModel>);

    public ObservableCollection<ItemViewModel> Items {
        get {
            return _items;
        }
        private set {
            if (_items == value) {
                return;
            }

            var oldValue = _items;
            _items = value;

            RaisePropertyChanged(ItemsPropertyName);
        }
    }

    #endregion

    private void LoadData() {
        this.Items.Add(new ItemViewModel() { LineOne = "runtime one", LineTwo = "Maecenas praesent accumsan bibendum", LineThree = "Facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu" });
        this.Items.Add(new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus", LineThree = "Suscipit torquent ultrices vehicula volutpat maecenas praesent accumsan bibendum dictumst eleifend facilisi faucibus" });
        this.Items.Add(new ItemViewModel() { LineOne = "runtime three", LineTwo = "Habitant inceptos interdum lobortis", LineThree = "Habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu suscipit torquent" });

        foreach (var item in Items) {
            for (int i = 0; i < 5; ++i)
                item.Items.Add(new ItemViewModel() { LineOne = "Item " + i, LineTwo = "Maecenas praesent accumsan bibendum" });
        }
    }
}

ItemViewModel.cs

public class ItemViewModel : ViewModelBase
{
    public ItemViewModel() {
        this.Items = new ObservableCollection<ItemViewModel>();

        if (IsInDesignMode) {
            // Code runs in Blend --> create design time data.
        } else {
            // Code runs "for real": Connect to service, etc...
        }
    }

    public override void Cleanup() {
        // Clean own resources if needed

        base.Cleanup();
    }

    #region [LineOne]

    public const string LineOnePropertyName = "LineOne";

    private string _lineOne = default(string);

    public string LineOne {
        get {
            return _lineOne;
        }

        set {
            if (_lineOne == value) {
                return;
            }

            var oldValue = _lineOne;
            _lineOne = value;
            RaisePropertyChanged(LineOnePropertyName);
        }
    }

    #endregion

    #region [LineTwo]

    public const string LineTwoPropertyName = "LineTwo";

    private string _lineTwo = default(string);

    public string LineTwo {
        get {
            return _lineTwo;
        }

        set {
            if (_lineTwo == value) {
                return;
            }

            var oldValue = _lineTwo;
            _lineTwo = value;

            RaisePropertyChanged(LineTwoPropertyName);
        }
    }

    #endregion

    #region [LineThree]

    public const string LineThreePropertyName = "LineThree";

    private string _lineThree = default(string);

    public string LineThree {
        get {
            return _lineThree;
        }

        set {
            if (_lineThree == value) {
                return;
            }

            var oldValue = _lineThree;
            _lineThree = value;
            RaisePropertyChanged(LineThreePropertyName);
        }
    }

    #endregion

    #region [Items]

    public const string ItemsPropertyName = "Items";

    private ObservableCollection<ItemViewModel> _items = default(ObservableCollection<ItemViewModel>);

    public ObservableCollection<ItemViewModel> Items {
        get {
            return _items;
        }
        private set {
            if (_items == value) {
                return;
            }

            var oldValue = _items;
            _items = value;
            RaisePropertyChanged(ItemsPropertyName);
        }
    }

    #endregion
}

MainPage.xaml

<phone:PhoneApplicationPage 
    x:Class="WP7Test.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:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800" 
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="False">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Main, Source={StaticResource Locator}}">
        <controls:Panorama Title="my application" ItemsSource="{Binding Items}">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>
            <controls:Panorama.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding LineOne}"/>
                </DataTemplate>
            </controls:Panorama.HeaderTemplate>
            <controls:Panorama.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Border BorderThickness="0,0,0,1" BorderBrush="White">
                            <TextBlock Text="{Binding LineTwo}" FontSize="28" TextWrapping="Wrap"/>
                        </Border>
                        <Border BorderThickness="0,0,0,1" Margin="0,20" BorderBrush="White">
                            <TextBlock Text="{Binding LineThree}" TextWrapping="Wrap"/>
                        </Border>
                        <ListBox ItemsSource="{Binding Items}">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel>
                                        <TextBlock Text="{Binding LineOne}" FontSize="24"/>
                                        <TextBlock Text="{Binding LineTwo}" FontSize="18" Margin="24,0,0,5"/>
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </StackPanel>
                </DataTemplate>
            </controls:Panorama.ItemTemplate>
        </controls:Panorama>
    </Grid>
    <!--Panorama-based applications should not show an ApplicationBar-->
</phone:PhoneApplicationPage>

Edit - adding additional first panel

Finally I understand what you trying to achive! However, you still need no code behind to do it! You just need a template ... for this Blend does help you as it lets you extract a template for a exiting control ... ok, here are the changes.

First I added a new property to the MainViewModel to show some data:

#region [MainPageProperty]

public const string MainPagePropertyPropertyName = "MainPageProperty";
private string _mainPageProperty = "Facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu";

public string MainPageProperty {
    get {
        return _mainPageProperty;
    }
    set {
        if (_mainPageProperty == value) {
            return;
        }

        _mainPageProperty = value;
        RaisePropertyChanged(MainPagePropertyPropertyName);
    }
}

#endregion

Then I used Blend to get the template for the Panorama control and inserted it into the controls:Panorama element.

<controls:Panorama.Template>
    <ControlTemplate TargetType="controls:Panorama">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <controlsPrimitives:PanningBackgroundLayer x:Name="BackgroundLayer" HorizontalAlignment="Left" Grid.RowSpan="2">
                <Border x:Name="background" Background="{TemplateBinding Background}" CacheMode="BitmapCache"/>
            </controlsPrimitives:PanningBackgroundLayer>
            <controlsPrimitives:PanningTitleLayer x:Name="TitleLayer" CacheMode="BitmapCache" ContentTemplate="{TemplateBinding TitleTemplate}" Content="{TemplateBinding Title}" FontSize="187" FontFamily="{StaticResource PhoneFontFamilyLight}" HorizontalAlignment="Left" Margin="10,-76,0,9" Grid.Row="0"/>
            <controlsPrimitives:PanningLayer x:Name="ItemsLayer" HorizontalAlignment="Left" Grid.Row="1">
                <StackPanel Orientation="Horizontal">
                    <controls:PanoramaItem Header="Main panel" Width="432">
                        <TextBlock Text="{Binding ElementName=LayoutRoot, Path=DataContext.MainPageProperty}" TextWrapping="Wrap"/>
                    </controls:PanoramaItem>
                    <ItemsPresenter x:Name="items"/>
                </StackPanel>
            </controlsPrimitives:PanningLayer>
        </Grid>
    </ControlTemplate>
</controls:Panorama.Template>

There are two tricks here, first I inserted a StacPanel to have allow for more than one element underneath the controlPrimitives:PanningLayer with the name ItemsPanel. Into this StackPanel I moved the ItemsPresenter and added another PanoramaItem. One thing that is important, though, is to set the Width property of the PanoramaItem, as otherwise the panel will extend to the room that is needed.

The other trick is that in order to get access to the DataContext I had to use the ElementName in the Binding.

Hope this shows the power of MVVM and Templating!

Sascha
  • 10,231
  • 4
  • 41
  • 65
AxelEckenberger
  • 16,628
  • 3
  • 48
  • 70
  • The problem is that i want one panaroma item take data from ItemsA in ViewModelA and for other panaroma items, i am setting the datacontext to viewmodelb. The solution you have suggested works only if the panaroma is bound to a single view model. I want to have one pan item bound (statically bound to viewmodelA) and other pan items bound dynamically to observablecollection of viewmodelBs. I used a style & control template and with some code behind, I am able to get it work. The problem is that I have galasoft eventtocommand not working within control template. – siva Jul 02 '11 at 18:33
  • I edited the main post with my new code. The problem is that i want the change the data context to parent for the code below. I tried a few things and doesn't work. – siva Jul 02 '11 at 18:38
  • See edit, this adds another PanoramaItem befor the list of PanoramaItems is built. – AxelEckenberger Jul 02 '11 at 19:46
  • Can you post the xaml? I am diving into xaml through wp7 app. I tried a few ways to get the parent context for the galasoft event to command within the list box in the pan item. The element name binding doesn't seem to work – siva Jul 04 '11 at 04:01
  • ViewModel A has Items collection Collection of ViewModelBs panaroma data context set to viewmodelA panaroma item - Statitically created in xaml to some Items collection in ViewModelA This pan item has a list box panaroma items --- to be bound to collection of viewmodelbs These pan items should each have a listbox which is selectable and fires commands on selection changed to viewModelB. Currently they are using the galasoft eventtocommand to hook the selection changed on the list box to a relay command – siva Jul 04 '11 at 04:43
  • I don't like the way my comments end up on stackoverflow.. so i edited the main post with my scenario.. – siva Jul 04 '11 at 04:47
  • XAML was there but formatted wrongly, sorry. Fixed now. And comments are for commenting and answers for answering. Putting comments in the answer section might earn you bad reputation (minus-points). – AxelEckenberger Jul 04 '11 at 05:11