I have a GridView as my zoomed out view in a SemanticZoom control. This GridView uses a custom DataTemplateSelector as the ItemTemplateSelector. The DataTemplateSelector returns a DataTemplate with different Foreground color, depending upon whether or not the Group has any items in it.
However, even though the DataTemplateSelector seems to work and returns the correct template, only one template is ever used by the GridView and the text is all the same color.
Here is the XAML of the GroupedListView:
<UserControl
x:Class="GroupList.GroupList.GroupedListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GroupList.GroupList"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:GroupList.Model"
xmlns:wuxdata="using:Windows.UI.Xaml.Data"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Use a collection view source for content that presents itself as a list of items that can be grouped or sorted. Otherwise, you can use x:Bind
directly on the ListView's item source to for further optimization. Please see the AppUIBasics sample for an example of how to do this. -->
<CollectionViewSource x:Name="ContactsCVS" IsSourceGrouped="True" />
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyle" BasedOn="{StaticResource ProximaNovaSemiBold}">
<Setter Property="FontSize" Value="54" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontWeight" Value="ExtraBold"/>
<Setter Property="FontStretch" Value="Expanded" />
</Style>
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyleGray" BasedOn="{StaticResource TileHeaderTextStyle}">
<Setter Property="Foreground" Value="Khaki" />
</Style>
<!-- When using x:Bind, you need to set x:DataType -->
<DataTemplate x:Name="ContactListViewTemplate" x:DataType="data:Contact">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="Ellipse"
Grid.RowSpan="2"
Width ="32"
Height="32"
Margin="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Fill="LightGray"/>
<TextBlock Grid.Column="1"
Text="{x:Bind Name}"
x:Phase="1"
Style="{ThemeResource BaseTextBlockStyle}"
Margin="12,6,0,0"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{x:Bind Position}"
x:Phase="2"
Style="{ThemeResource BodyTextBlockStyle}"
Margin="12,0,0,6"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="GrayZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyleGray}" />
</DataTemplate>
<DataTemplate x:Key="ZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyle}" />
</DataTemplate>
<local:GroupEmptyOrFullSelector x:Key="GroupEmptyOrFullSelector" Empty="{StaticResource GrayZoomedOutTemplate}" Full="{StaticResource ZoomedOutTemplate}" />
</UserControl.Resources>
<!--#region Navigation Panel -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Margin="15,0,0,0" Text="Paula's SemanticZoom Sandbox" Grid.Row="0"
VerticalAlignment="Center"
Style="{ThemeResource TitleTextBlockStyle}" />
<Button x:Name="ZoomInOutBtn" Content="ABC↕" Click="ZoomInOutBtn_Click" Width="60" HorizontalAlignment="Center" BorderThickness="0" />
</StackPanel>
<!--#endregion-->
<SemanticZoom x:Name="ZoomControl" Grid.Row="1">
<SemanticZoom.ZoomedInView>
<GridView ItemsSource="{x:Bind ContactsCVS.View}"
ItemTemplate="{StaticResource ContactListViewTemplate}"
SelectionMode="Single"
ShowsScrollingPlaceholders="True">
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="data:GroupInfoList">
<TextBlock Text="{x:Bind Key}"
Style="{ThemeResource TitleTextBlockStyle}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView ItemTemplateSelector="{StaticResource GroupEmptyOrFullSelector}" ScrollViewer.VerticalScrollBarVisibility="Disabled" Margin="0, 200" Width="475" ItemsSource="{x:Bind ContactsCVS.View.CollectionGroups}" SelectionMode="None" >
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</Grid>
</UserControl>
And here is the DataTemplateSelector:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.Foundation.Collections;
using GroupList.Model;
namespace GroupList.GroupList
{
/// <summary>
/// This determines whether or not the Group passed during binding is empty or not and allows selection
/// of the proper DataTemplate based on this value.
/// </summary>
class GroupEmptyOrFullSelector : DataTemplateSelector
{
private DataTemplate _full;
private DataTemplate _empty;
public DataTemplate Full
{
set { _full = value; }
get { return _full; }
}
public DataTemplate Empty
{
set { _empty = value; }
get { return _empty; }
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
}
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
}
}
So, here was the problem:
In the DataTemplateSelector listing, even if a passed "object item" was not a group, it fell through to the template selection logic. Sometimes, the "object" passed to your function is not a Group object, but rather a generic DependencyObject. In such a case, your template selection logic needs to return a default template, returning one of the specific templates only if the "object item" is a Group object. Thus, the new function looks like this:
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
return Full;
}
Related
I'm making an application using Caliburn.Micro(for easy data binding and stuff) and MahApps.Metro(for designing).
I've created a View name 'MainView' which has HamburgerMenu of MahApps.
My issue is data binding is working fine under HamburgerMenu.ContentTemplate tag
Here is my HamburgerMenu.ContentTemplate xaml.
<Page x:Class="Sample.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:utils="clr-namespace:Omni.WindowsClient.Utils"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Omni.WindowsClient.Views"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="600">
<Page.Resources>
<DataTemplate x:Key="HamburgerMenuItem"
DataType="{x:Type mah:HamburgerMenuItem}">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{Binding Glyph}"
Stretch="UniformToFill" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Label}" />
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<mah:HamburgerMenu x:Name="HamburgerMenuControl"
SelectedIndex="0"
ItemTemplate="{StaticResource HamburgerMenuItem}"
OptionsItemTemplate="{StaticResource HamburgerMenuItem}"
IsPaneOpen="True"
DisplayMode="CompactInline"
cal:Message.Attach="[Event ItemClick] = [Action ShowDetails(HamburgerMenuControl.SelectedItem)]"
DataContext="{Binding RelativeSource={RelativeSource self}}">
<mah:HamburgerMenu.ItemsSource>
<mah:HamburgerMenuItemCollection>
<mah:HamburgerMenuItem Label="System Status">
<mah:HamburgerMenuItem.Tag>
<iconPacks:PackIconFontAwesome Width="22"
Height="22"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Kind="Tasks" />
</mah:HamburgerMenuItem.Tag>
</mah:HamburgerMenuItem>
<mah:HamburgerMenuItem Label="Inbox">
<mah:HamburgerMenuItem.Tag>
<iconPacks:PackIconFontAwesome Width="22"
Height="22"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Kind="Inbox" />
</mah:HamburgerMenuItem.Tag>
</mah:HamburgerMenuItem>
<mah:HamburgerMenuItem.Tag>
<iconPacks:PackIconFontAwesome Width="22"
Height="22"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Kind="Certificate" />
</mah:HamburgerMenuItem.Tag>
</mah:HamburgerMenuItem>
</mah:HamburgerMenuItemCollection>
</mah:HamburgerMenu.ItemsSource>
<mah:HamburgerMenu.ContentTemplate>
<DataTemplate DataType="{x:Type mah:HamburgerMenuItem}">
<Grid utils:GridUtils.RowDefinitions="48,*">
<!--cal:Action.TargetWithoutContext="{Binding ElementName=HamburgerMenuControl, Path=DataContext}"-->
<Border Grid.Row="0"
Background="{DynamicResource MahApps.Metro.HamburgerMenu.PaneBackgroundBrush}">
<TextBlock x:Name="Header"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Foreground="White" />
<!--Text="{Binding Path=Header, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"-->
</Border>
<Frame Grid.Row="1"
cal:Message.Attach="RegisterFrame($source)"
DataContext="{x:Null}"
NavigationUIVisibility="Hidden" />
</Grid>
</DataTemplate>
</mah:HamburgerMenu.ContentTemplate>
</mah:HamburgerMenu>
</Grid>
</Page>
and respective view model code is:
using Caliburn.Micro;
using MahApps.Metro.Controls;
using System.Windows.Controls;
namespace Sample.ViewModels
{
public class MainViewModel : Screen
{
private readonly SimpleContainer _container;
private INavigationService _navigationService;
private string _header;
public string HeaderTitle
{
get { return _header; }
set
{
_header = value;
NotifyOfPropertyChange();
}
}
public MainViewModel(SimpleContainer container)
{
this._container = container;
DisplayName = "Main";
}
public void RegisterFrame(Frame frame)
{
_navigationService = new FrameAdapter(frame);
_container.Instance(_navigationService);
_navigationService.NavigateToViewModel(typeof(SystemStatusViewModel));
HeaderTitle = "System Status";
}
public void ShowDetails(HamburgerMenuItem menuItem)
{
switch (menuItem.Label)
{
case "System Status":
_navigationService.NavigateToViewModel(typeof(SystemStatusViewModel));
HeaderTitle = "System Status";
break;
case "Inbox":
_navigationService.NavigateToViewModel(typeof(InboxViewModel));
HeaderTitle = "Inbox";
break;
default:
break;
}
}
}
}
I want to change View in frame under HamburgerMenu.ContentTemplate when I click on menu item.
Like System Status view is SystemStatusView
and Inbox view is InboxView.
My code is working fine (it changes the view in frame and change the Header label too) if I don't use HamburgerMenu.ContentTemplate. But I want to use HamburgerMenu.ContentTemplate to work with HamburgerMenu.
Thanks!
If it's working fine if you don't use HamburgerMenu.ContentTemplate, but stops working when you do, the problem is probably with you overwriting the default template in a way that doesn't support all functionalities of a control.
I'd suggest you to use Blend to get the default HamburgerMenu.ContentTemplate, then just edit it to your needs, without changing too much (keep in mind that names of controls used as a template may have a crucial meaning, so be careful what you are editing).
If you don't know how to use Blend to get your control's template, here is a simple tutorial described in a documentation of Telerik controls (don't worry, it works the same for all controls). You just need to create copy of a HamburgerMenu.ContentTemplate, paste it to your application and you are good to go (editing).
Last year I made an app for Windows Phone 8, containing a LongListSelector with a unique first and last item template, as described here:
LongListSelector different item template for first and last item
I recently updated the app to Windows Phone 8.1 Store and this functionality broke. Here's my class (the only difference from the post being, that I'm using a ListView instead of a LongListSelector for Windows Phone 8.1 Store, since LongListSelector is not a part of the Windows Phone 8.1 Store framework):
public abstract class TemplateSelector : ContentControl
{
public abstract DataTemplate SelectTemplate(object item, int index, int totalCount, DependencyObject container);
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
var parent = GetParentByType<ListView>(this);
var index = (parent.ItemsSource as IList).IndexOf(newContent);
var totalCount = (parent.ItemsSource as IList).Count;
ContentTemplate = SelectTemplate(newContent, index, totalCount, this);
}
private static T GetParentByType<T>(DependencyObject element) where T : FrameworkElement
{
T result = null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
result = parent as T;
if (result != null)
{
return result;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
The problem is that this:
DependencyObject parent = VisualTreeHelper.GetParent(element);
of the GetParentByType function returns null for some reason. Anyone knows why or have an alternative solution?
Below is my XAML code(with a few xmlns's removed). The DataTrigger is there because sometimes a unique first item template is enough (controlled by the LoadMore property of the ViewModel, which is a bool).
<controls:WP81Page
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<controls:WP81Page.Resources>
<DataTemplate x:Key="first">
<Grid HorizontalAlignment="Left" Width="Auto" Height="200">
<Border BorderThickness="1" BorderBrush="Black" Visibility="{Binding NoLargeImage, Converter={StaticResource BoolToVisibilityConverter}}" >
<Image Source="/Images/default-image.png" Stretch="UniformToFill" />
</Border>
<Border BorderThickness="1" BorderBrush="Black" Visibility="{Binding NoLargeImage, Converter={StaticResource BoolToVisibilityConverterReverse}}" >
<Image Source="{Binding LargeImageUrl}" Stretch="UniformToFill" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="#7F000000" >
<TextBlock Text="{Binding Header}" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="White" FontWeight="Bold" Margin="15,0,15,15"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="default">
<StackPanel Orientation="Horizontal" Margin="0,10,0,0" Height="105" Width="Auto">
<Border BorderThickness="1" BorderBrush="Black" Margin="0,2" Visibility="{Binding NoSmallImage, Converter={StaticResource BoolToVisibilityConverter}}" >
<Image Source="/Images/default-image.png" Width="130" Height="100" Stretch="UniformToFill" />
</Border>
<Border BorderThickness="1" BorderBrush="Black" Margin="0,2" Visibility="{Binding NoSmallImage, Converter={StaticResource BoolToVisibilityConverterReverse}}">
<Image Source="{Binding SmallImageUrl}" Width="130" Height="100" Stretch="UniformToFill"/>
</Border>
<StackPanel Orientation="Vertical" Width="300" Margin="8,0,0,0">
<TextBlock Text="{Binding Header}" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Margin="0,3,0,0" Text="{Binding DisplayDate}" TextWrapping="Wrap" Foreground="#FFB9B9B9" FontSize="16" />
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="last">
<TextBlock Text="hent flere nyheder" FontSize="25" Margin="0,20" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Height="75" />
</DataTemplate>
<DataTemplate x:Key="UniqueFirstTemplateSelector">
<common:UniqueFirstTemplateSelector Content="{Binding}" First="{StaticResource first}" Default="{StaticResource default}" HorizontalAlignment="Stretch"/>
</DataTemplate>
<DataTemplate x:Key="UniqueFirstAndLastTemplateSelector">
<common:UniqueFirstAndLastTemplateSelector Content="{Binding}" First="{StaticResource first}" Default="{StaticResource default}" Last="{StaticResource last}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</controls:WP81Page.Resources>
<controls:WP81Page.DataContext>
<viewModels:NewsViewModel/>
</controls:WP81Page.DataContext>
<Grid x:Name="LayoutRoot" Style="{Binding Source={StaticResource Background}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ProgressBar Grid.Row="0" VerticalAlignment="Top" IsIndeterminate="True" Background="Transparent" Foreground="White" Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibilityConverter}}" />
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="NYHEDER" FontSize="35" FontWeight="Bold" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1">
<controls:WP81ListView x:Name="listSelector" Margin="22,0" Grid.Row="2" ItemsSource="{Binding News}" Command="{Binding NewsEntrySelectedCommand}" >
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding LoadMore}" Value="True">
<core:ChangePropertyAction TargetObject="{Binding ElementName=listSelector}"
Value="{StaticResource UniqueFirstAndLastTemplateSelector}"
PropertyName="ItemTemplate" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding LoadMore}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=listSelector}"
Value="{StaticResource UniqueFirstTemplateSelector}"
PropertyName="ItemTemplate" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</controls:WP81ListView>
</Grid>
</Grid>
</controls:WP81Page>
Thank you in advance.
EDIT
Alright, so what I want is not for the first and last item to be unique all the time. I'm making a news feed where the last item is only unique when there are more news entries to load (when the last item are clicked, more news entries are added to the ListView). If however, the end of the news entries are reached, the last item must not be unique (hence the combination with interactivity).
ListView has a property called ItemTemplateSelector which accepts objects based on DataTemplateSelector. So you need to change a couple of things to get it to work.
First of all, the definition of your template selector. It needs to be based on DataTemplateSelector and override method called SelectTemplateCore. It takes an object which is a ListViewItem. At that point you can go up the VisualTree to get the ListView and choose the DataTemplate based on index.
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate First { get; set; }
public DataTemplate Default { get; set; }
public DataTemplate Last { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var listViewItem = container as ListViewItem;
var listView = GetParentByType<ListView>(listViewItem);
var index = (listView.ItemsSource as IList).IndexOf(item);
var totalCount = (listView.ItemsSource as IList).Count;
if (index == 0)
return First;
else if (index == totalCount - 1)
return Last;
else
return Default;
}
private T GetParentByType<T>(DependencyObject element) where T : FrameworkElement
{
T result = null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
result = parent as T;
if (result != null)
{
return result;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
Then, you need to create an instance of it in static resources
<local:MyTemplateSelector x:Key="SelectingTemplate"
First="{StaticResource first}"
Default="{StaticResource default}"
Last="{StaticResource last}" />
with the First, Default and Last DataTemplates, just like you did before.
The last step is to apply it to the ListView you're using.
<ListView ItemsSource="{Binding SomeItemsSource}"
ItemTemplateSelector="{StaticResource SelectingTemplate}" />
And that's it!
The standard GridApp template is as follows:
Variable Sized Grouped GridView template is as follows:
How to make a template for your application, so that it looks like this:
For example, this design in all applications Bing for windows 8:
Code for Variable Sized Grouped GridView template:
<UserControl.Resources>
<!-- Collection of grouped items displayed by this page -->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="Items"
d:Source="{Binding ItemGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
<DataTemplate x:Key="CustomTileItem">
<Grid >
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding Image}" Stretch="UniformToFill"/>
</Border>
<StackPanel VerticalAlignment="Bottom" >
<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="30" Margin="15,0,15,0"/>
<TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Grid.Column="1" Style="{StaticResource PageHeaderTextStyle}"/>
</Grid>
<!-- Horizontal scrolling grid used in most view states -->
<ScrollViewer
x:Name="itemGridScrollViewer"
AutomationProperties.AutomationId="ItemGridScrollViewer"
Grid.Row="1"
Margin="0,-3,0,0"
Style="{StaticResource HorizontalScrollViewerStyle}">
<local:MyGridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Margin="116,0,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource CustomTileItem}"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
<local:MyGridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</local:MyGridView.ItemsPanel>
<local:MyGridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Content="{Binding Title}"
Click="Header_Click"
Style="{StaticResource TextButtonStyle}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="75" ItemHeight="150" Orientation="Vertical" Margin="0,0,80,0" MaximumRowsOrColumns="3"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</local:MyGridView.GroupStyle>
</local:MyGridView>
</ScrollViewer>
and:
public class MyGridView : GridView
{
private int rowVal;
private int colVal;
private Random _rand;
private List<Size> _sequence;
public MyGridView()
{
_rand = new Random();
_sequence = new List<Size> {
LayoutSizes.PrimaryItem,
LayoutSizes.SecondarySmallItem, LayoutSizes.SecondarySmallItem,
LayoutSizes.SecondarySmallItem,
LayoutSizes.SecondaryTallItem,
LayoutSizes.OtherSmallItem, LayoutSizes.OtherSmallItem, LayoutSizes.OtherSmallItem
};
}
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
SampleDataItem dataItem = item as SampleDataItem;
int index = -1;
if (dataItem != null)
{
index = dataItem.Group.Items.IndexOf(dataItem);
}
if (index >= 0 && index < _sequence.Count)
{
colVal = (int)_sequence[index].Width;
rowVal = (int)_sequence[index].Height;
}
else
{
colVal = (int)LayoutSizes.OtherSmallItem.Width;
rowVal = (int)LayoutSizes.OtherSmallItem.Height;
}
VariableSizedWrapGrid.SetRowSpan(element as UIElement, rowVal);
VariableSizedWrapGrid.SetColumnSpan(element as UIElement, colVal);
}
}
public static class LayoutSizes
{
public static Size PrimaryItem = new Size(6, 2);
public static Size SecondarySmallItem = new Size(3, 1);
public static Size SecondaryTallItem = new Size(3, 2);
public static Size OtherSmallItem = new Size(2, 1);
}
For example with "Variable Sized Grouped GridView template", we can combine the rows or columns, and how to set the first element height = "auto", and all other elements that have different widths and heights, but grouped as "Variable Sized Grouped GridView template"?
View this post:
http://blogs.msdn.com/b/synergist/archive/2012/09/25/windows-store-app-xaml-gridview-with-variable-templates.aspx
I think this is what you need.
In my Windows Phone application I have a listBox with ContentItems binding :
private ObservableCollection<ContentItemViewModel> _contentItems;
public ObservableCollection<ContentItemViewModel> ContentItems
{
get { return _contentItems; }
}
<ListBox x:Name="ContentListBox" Margin="0,117,12,0" VirtualizingStackPanel.VirtualizationMode="Standard" Logic1:TiltEffect.IsTiltEnabled="True" ItemsSource="{Binding ContentItems}" Tap="ContentListBox_Tap" MinHeight="656" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical" >
<Grid Height="{Binding ItemHeight}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" x:Name="itemIco1" Width="Auto" Height="Auto" HorizontalAlignment="Left" Source="{Binding IconURL}" Stretch="Fill" CacheMode="BitmapCache" VerticalAlignment="Top" Margin="5,5,5,5" Visibility="Visible"/>
<ListBox IsHitTestVisible="False" Grid.Column="1" VerticalAlignment="Center" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<TextBlock x:Name="ContentCategoryTitle" Height="70" Text="{Binding ContentTitle}" Width="460" Margin="5,34,0,0" TextTrimming="WordEllipsis" TextWrapping="NoWrap" FontSize="28" FontFamily="Segoe WP Semibold" Foreground="#FFF7F7F7" VerticalAlignment="Bottom" />
</ListBox>
</Grid>
<Rectangle Fill="#FF585858" Height="1" Margin="0,0,0,0" Width="460" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Is it possible to binding not an ObservableCollection<ContentItemViewModel>, but - ObservableCollection<List<ContentItemViewModel>> ?
Yes that is possible, if you have a collection of collections. Or why not this?
ObservableCollection<ObservableCollection<ContentItemViewModel>>
If you want your UI to be notified of collection changes to your sub collections.
Update
For example:
View Model
public ObservableCollection<ObservableCollection<ContentItemViewModel>> ContentItems
{
get { return _contentItems; }
set { _contentItems = value; // Notify of property change here, this allows you to change the ContentItems reference after view model construction }
}
public MyViewModel()
{
// Populate content items
this.ContentItems = new ObservableCollection
{
new ObservableCollection { new ContentItemViewModel() },
new ObservableCollection { new ContentItemViewModel(), new ContentItemViewModel() }
};
}
View
<ListBox ItemsSource="{Binding ContentItems}" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyContentItemViewModelProperty}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
I have the following code:
<Window.Resources>
<DataTemplate x:Key="SectionTemplate" >
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</Window.Resources>
<Grid >
<Border>
<HeaderedItemsControl Header="Top1"
ItemsSource="{Binding Path=List1}"
ItemTemplate="{StaticResource SectionTemplate}"/>
</Border>
</Grid>
public class MainWindow
{
public List<Item> List1
{
get { return list1; }
set { list1 = value; }
}
public MainWindow()
{
list1.Add(new Item { Name = "abc" });
list1.Add(new Item { Name = "xxx" });
this.DataContext = this;
InitializeComponent();
}
}
public class Item
{
public string Name { get; set; }
}
For some reason the Items are rendered, but without the header.
As the documentation points out:
A HeaderedItemsControl has a limited default style. To create a HeaderedItemsControl with a custom appearance, create a new ControlTemplate.
So when you create that template make sure to include some ContentPresenter which is bound to the Header (e.g. using ContentSource)
e.g.
<HeaderedItemsControl.Template>
<ControlTemplate TargetType="{x:Type HeaderedItemsControl}">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ContentPresenter ContentSource="Header" />
<Separator Grid.Row="1" />
<ItemsPresenter Grid.Row="2" />
</Grid>
</Border>
</ControlTemplate>
</HeaderedItemsControl.Template>
(All the default bindings (Margin, Background, etc.) are omitted.)
You can make a DataTemplate for the header, just as you did for the items (which is surely less intrusive than redoing the entire control template).
e.g.
<Window.Resources>
<DataTemplate x:Key="HeaderTemplate">
</DataTemplate>
</Window.Resources>
<HeaderedItemsControl Header="Top1"
HeaderTemplate="{StaticResource HeaderTemplate}"
ItemsSource="{Binding Path=List1}"
ItemTemplate="{StaticResource SectionTemplate}"
/>
Instead of using e.g. "Top1" as here, you can bind to an object and then use binding relative to that object in the DataTemplate.
There is one gotcha with this approach, which is that the necessary approach to getting styles pulled in for non-control elements (notably TextBlock) is a little convoluted; also see WPF Some styles not applied on DataTemplate controls. (You might also run into this with the ControlTemplate approach.)