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.
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 fromItemsPresenter
), i.e. you can bind theItemsSource
to a list that contains items that repesent yourPanoramaItems
.To render your
PanoramaItem
you will have to provide templates forPanorama.HeaderTemplate
andPanorama.ItemTemplate
. TheDataContext
inside the templates is theViewModel
that represents yourPanoramaItem
. If thisViewModel
contains an item list you now can use it to generate theListBoxes
you were looking for.And here is the sample ...
ViewModelLocator.cs
MainViewModel.cs
ItemViewModel.cs
MainPage.xaml
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:
Then I used Blend to get the template for the Panorama control and inserted it into the
controls:Panorama
element.There are two tricks here, first I inserted a StacPanel to have allow for more than one element underneath the
controlPrimitives:PanningLayer
with the nameItemsPanel
. Into thisStackPanel
I moved theItemsPresenter
and added anotherPanoramaItem
. One thing that is important, though, is to set theWidth
property of thePanoramaItem
, 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 theElementName
in the Binding.Hope this shows the power of MVVM and Templating!