Display different data templates on a list view in UWP - c#

I am working with visual studio 2017. And I wand to display two type of list view items in a list view. That means two different custom data templates.
And This is my xaml page
<Page
x:Class="InboxModule.ChatMessages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InboxModule"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="leftTemplate">
<StackPanel Background="Aqua" Orientation="Horizontal">
<TextBlock Text="left"/>
<TextBlock Text="{Binding LastMessage}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="rightTemplate">
<Grid Background="White">
<TextBlock Text="right"/>
</Grid>
</DataTemplate>
<local:MyDataTemplateSelector x:Key="myPremiumUserDataTemplateSelector" />
</Page.Resources>
<Grid>
<ListView x:Name="myListView" ItemTemplateSelector="{StaticResource myPremiumUserDataTemplateSelector}">
</ListView>
</Grid>
</Page>
And my xaml.cs code is this
public sealed partial class ChatMessages : Page
{
public ChatMessages()
{
this.InitializeComponent();
List<chat> users = new List<chat>();
for (int i = 0; i < 10; ++i)
{
var user = new chat { NewMessages = "Name is mj "};
if (i == 2 || i == 4)
{
user.Name = "Alex Doe";
}
users.Add(user);
}
myListView.ItemsSource = users;
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(InboxChat));
}
}
And my MyDataTemplateSelector class is this
public class MyDataTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
FrameworkElement elemnt = container as FrameworkElement;
chat user = item as chat;
if (user.Name == "Alex Doe")
{
return elemnt.FindName("leftTemplate") as DataTemplate;
}
else
{
return elemnt.FindName("rightTemplate") as DataTemplate;
}
}
}
I tried to use dummy values. And i have tried for hours.. but i could not find a solution. Please help me to solve this problem.
Thank you very much!!

I think you need to pass the data templates to the selector when it is initialized in the view XAML. Here is how to do it.
You selector will look like this (basically accepting the data templates as properties in the class):
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate1 { get; set; }
public DataTemplate DataTemplate2 { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if ([Condition 1] == true)
return DataTemplate1;
if ([Condition 2] == true)
return DataTemplate2;
return base.SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
Then to use this selector, declare it like this in your view's XAML:
<Page.Resources>
<MyDataTemplateSelector x:Key="MySelector">
<MyDataTemplateSelector.DataTemplate1>
<DataTemplate .... />
<MyDataTemplateSelector.DataTemplate1>
<MyDataTemplateSelector.DataTemplate2>
<DataTemplate .... />
<MyDataTemplateSelector.DataTemplate2>
</MyDataTemplateSelector>
</Page.Resources>
you will basically be declaring those data templates inside the xaml code initializing your selector.
Edit: As for the reason your code wasn't working: I suspect this is because it can't find the element you are looking for with FindName and is probably returning a null data template back to the list view using this selector.
Hope this helps you.

Related

ItemsControl displaying class name

This app is displaying the class name of a collection instead of a text-box as desired. I've read other issues with this, but cannot figure out what I'm missing. I have a datacontext, I'm bound to the collection as an itemsource, and I've added a single item. All I want is to bind the collection 'Boxes' in my view model 'DrawBoxViewModel' to an item source, and have it display a single item as a text box. All help is appreciated.
First my XAML:
<Page
x:Class="BoxMaker2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BoxMaker2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:BoxMaker2.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<vm:DrawBoxViewModel x:Key="DrawBoxViewModel"/>
</Page.Resources>
<Canvas DataContext="{Binding Source={StaticResource DrawBoxViewModel}}">
<ItemsControl ItemsSource="{Binding Boxes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="350" Height="600" Background="AliceBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate x:DataType="vm:Box" x:Key="test">
<VariableSizedWrapGrid>
<TextBox Background="White"
Text="{x:Bind Data}"
Width="100"
Height="100"/>
<VariableSizedWrapGrid.RenderTransform>
<TranslateTransform X="{Binding LeftCanvas}" Y="{Binding TopCanvas}"/>
</VariableSizedWrapGrid.RenderTransform>
</VariableSizedWrapGrid>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Canvas>
And now my viewmodel:
namespace BoxMaker2.ViewModels
{
public class DrawBoxViewModel
{
#region fields
private ObservableCollection<Box> _boxes;
#endregion
#region properties
public ObservableCollection<Box> Boxes { get { return this._boxes; } }
#endregion
#region constructors
public DrawBoxViewModel()
{
this._boxes = new ObservableCollection<Box>();
_boxes.Add(new Box() { Data = "hello!", LeftCanvas = 200, TopCanvas = 200 });
}
#endregion
}
public class Box : INotifyPropertyChanged
{
private int _generation;
public int Generation
{
get { return _generation; }
set { _generation = value; OnPropertyChanged("Generation"); }
}
private int _childNo;
public int ChildNo
{
get { return _childNo; }
set { _childNo = value; OnPropertyChanged("ChildNo"); }
}
private Box _parentBox;
public Box ParentBox
{
get { return _parentBox; }
set { _parentBox = value; OnPropertyChanged("ParentBox"); }
}
private List<Box> _childrenBox;
public List<Box> ChildrenBox
{
get { return _childrenBox; }
set { _childrenBox = value; OnPropertyChanged("ChildrenBox"); }
}
private string _data;
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
private double _topCanvas;
public double TopCanvas
{
get { return _topCanvas; }
set
{
_topCanvas = value;
OnPropertyChanged("TopCanvas");
}
}
private double _leftCanvas;
public double LeftCanvas
{
get { return _leftCanvas; }
set
{
_leftCanvas = value;
OnPropertyChanged("LeftCanvas");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am not exactly sure what you are trying to achieve but here's a few issues I have found in your code.
You should assign your VM to the DataContext of the Page
directly.
<Page.DataContext>
<vm:DrawBoxViewModel />
</Page.DataContext>
After doing so, you can now remove DataContext="{Binding
Source={StaticResource DrawBoxViewModel}}" from your Canvas.
Replace <ItemsControl.Resource> with
<ItemsControl.ItemTemplate> and remove x:Key="test", assuming you want to show multiple
TextBoxes on the UI. The DataTemplate within the Resource you
defined won't do anything until you reference it by its key. I don't
think you really want that here though.
You should use x:Bind for your X & Y binding
<TranslateTransform X="{x:Bind LeftCanvas}"
Y="{x:Bind TopCanvas}" />
Your Boxes collection can be simplified as following
#region properties
public ObservableCollection<Box> Boxes { get; } = new ObservableCollection<Box>();
#endregion
#region constructors
public DrawBoxViewModel()
{
Boxes.Add(new Box() { Data = "hello!", LeftCanvas = 0, TopCanvas = 200 });
}
#endregion
Hope this helps!
Your Items control doesn't know which data template to use. Currently your view model has a template associated to it via the x:DataType="vm:Box" which is defined as a resource in the items control.
The problem is that Universal Windows Platform doesn't recognize templates associated to data types. So even though there is a template, the control doesn't know how to find it when it is rendering the collection of view models.
Automatic resolving of templates based on bound types was a function of WPF which is not available in UWP.
What that means is that in WPF you could associate a data template to a class/object via the x:DataType="Object Type" attribute of the data template (which is what you did). When the collection is bound, the rendering engine would auto-magically match the the individual items in the collection to their respective templates.
This was very powerful because if your collection had many different types of boxes for example (or things inheriting from DrawBoxViewModel) you could render each item type differently by simply defining a template. Well this is no more. Microsoft destroyed that feature in UWP.
So long story short - move the template to the page resource collection. Give it a key such as:
<Page.Resources>
<vm:DrawBoxViewModel x:Key="DrawBoxViewModel"/>
<DataTemplate x:Key="test">
<VariableSizedWrapGrid>
<TextBox Background="White"
Text="{x:Bind Data}"
Width="100"
Height="100"/>
<VariableSizedWrapGrid.RenderTransform>
<TranslateTransform X="{Binding LeftCanvas}" Y="{Binding TopCanvas}"/>
</VariableSizedWrapGrid.RenderTransform>
</VariableSizedWrapGrid>
</DataTemplate>
</Page.Resources>
Reference the template in your items control as follows:
<ItemsControl ItemsSource="{Binding Boxes} ItemTemplate={StaticResource test} ">

binding to nested object from the parent user control wpf

I am writing a new user control. It needs to be able to display an ObservableCollection of items. Those items will have a property that is also an observable collection, so it is similar to a 2-d jagged array. The control is similar to a text editor so the outer collection would be the lines, the inner collection would be the words. I want the consumer of the control to be able to specify not only the binding for the lines, but also the binding for the words. The approach I have so far is as follows:
The user control inherits from ItemsControl. Inside this control it has a nested ItemsControl. I would like to be able to specify the binding path of this nested ItemsControl from the parent user control. The XAML for the UserControl is
<ItemsControl x:Class="IntelliDoc.Client.Controls.TextDocumentEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:IntelliDoc.Client"
xmlns:con="clr-namespace:IntelliDoc.Client.Controls"
xmlns:data="clr-namespace:IntelliDoc.Data;assembly=IntelliDoc.Data"
xmlns:util="clr-namespace:IntelliDoc.Client.Utility"
xmlns:vm="clr-namespace:IntelliDoc.Client.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300"
>
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<ItemsPresenter Name="PART_Presenter" />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<ItemsControl Name="PART_InnerItemsControl" ItemsSource="{Binding NestedBinding, ElementName=root}" >
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Name="InnerStackPanel" Orientation="Horizontal" >
<TextBox Text="" BorderThickness="0" TextChanged="TextBox_TextChanged" />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<ContentControl Content="{Binding Path=Data, Mode=TwoWay}" />
<TextBox BorderThickness="0" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
The code behind has this property declared
public partial class TextDocumentEditor : ItemsControl
{
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(BindingBase), typeof(TextDocumentEditor),
new PropertyMetadata((BindingBase)null));
public BindingBase NestedItems
{
get { return (BindingBase)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
...
}
The expected bound object will be as follows:
public class ExampleClass
{
ObservableCollection<InnerClass> InnerItems {get; private set;}
}
public class InnerClass : BaseModel //declares OnPropertyChanged
{
private string _name;
public string Name //this is provided as an example property and is not required
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
....
}
public class ViewModel
{
public ObservableCollection<ExampleClass> Items {get; private set;}
}
The XAML declaration would be as follows:
<Window x:Class="IntelliDoc.Client.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="TestWindow" Height="300" Width="300">
<DockPanel>
<TextDocumentEditor ItemsSource="{Binding Path=Items}" NestedItems={Binding Path=InnerItems} >
<DataTemplate>
<!-- I would like this to be the user defined datatemplate for the nested items. Currently I am just declaring the templates in the resources of the user control by DataType which also works -->
</DataTemplate>
</TextDocumentEditor>
</DockPanel>
In the end, I want the user control I created to provide the ItemsControl template at the outer items level, but I want the user to be able to provide the datatemplate at the inner items control level. I want the consumer of the control to be able to provide the bindings for both the Outer items and the nested items.
I was able to come up with a solution that works for me. There may be a better approach, but here is what I did.
First, on the outer ItemsControl, I subscribed to the StatusChanged of the ItemContainerGenerator. Inside that function, I apply the template of the ContentPresenter and then search for the Inner ItemsControl. Once found, I use the property NestedItems to bind to the ItemsSource property. One of the problems I was having originally was I was binding incorrectly. I fixed that and I changed the NestedItems to be a string. Also, I added a new property called NestedDataTemplate that is of type DataTemplate so that a user can specify the DataTemplate of the inner items control. It was suggested that I not use a UserControl since I don't inherit from a UserControl, so I will change it to a CustomControl. The code changes are below
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(string), typeof(TextDocumentEditor),
new PropertyMetadata((string)null));
public static readonly DependencyProperty NestedDataTemplateProperty = DependencyProperty.Register("NestedDataTemplate", typeof(DataTemplate), typeof(TextDocumentEditor),
new PropertyMetadata((DataTemplate)null));
public DataTemplate NestedDataTemplate
{
get { return (DataTemplate)GetValue(NestedDataTemplateProperty); }
set
{
SetValue(NestedDataTemplateProperty, value);
}
}
public string NestedItems
{
get { return (string)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (((ItemContainerGenerator)sender).Status != GeneratorStatus.ContainersGenerated)
return;
ContentPresenter value;
ItemsControl itemsControl;
for (int x=0;x<ItemContainerGenerator.Items.Count; x++)
{
value = ItemContainerGenerator.ContainerFromIndex(x) as ContentPresenter;
if (value == null)
continue;
value.ApplyTemplate();
itemsControl = value.GetChildren<ItemsControl>().FirstOrDefault();
if (itemsControl != null)
{
if (NestedDataTemplate != null)
itemsControl.ItemTemplate = NestedDataTemplate;
Binding binding = new Binding(NestedItems);
BindingOperations.SetBinding(itemsControl, ItemsSourceProperty, binding);
}
}
}

How to set different dataTemplate to treeViewItems in code behind

I have TreeView (named: treeViewDiagram):
< TreeView x:Name = "treeViewDiagram" Grid.Row="2"
FontFamily="/logicalTree;component/Resources/Fonts/#Oxygen"
BorderThickness="7,7,0,7" BorderBrush="#FFE9E9E9" Padding="86,0,0,0" ItemTemplateSelector="{Binding Source={StaticResource dataTemplateSelector}}" />
And here the three dataTamplate I have:
<local:CostomDataTemplateSelector x:Key="dataTemplateSelector"/>
< DataTemplate x:Key = "BasicDataTemplate">
< Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top" >
<...>
< /Grid >
< /DataTemplate >
<DataTemplate x:Key="ComplexDataTemplate" >
<Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top">
<...>
</Grid>
</DataTemplate>
<DataTemplate x:Key="RootDataTemplate" >
<Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top" >
<...>
</Grid>
</DataTemplate>
Now, I want to add treeViewItems in the code and set for each treeViewItem
different dataTemplate according my code.
(if (basic) => so: BasicDataTemplate)
(if (complex) => so: ComplexDataTemplate)
(if (root) => so: RootDataTemplate)
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (item is MyBasicData)
{
return element.FindResource("BasicDataTemplate") as DataTemplate;
}
else if (item is MyComplexData)
{
return element.FindResource("ComplexDataTemplate") as DataTemplate;
}
else if (item is MyRootData)
{
return element.FindResource("RootDataTemplate") as DataTemplate;
}
return null;
}
I have 3 ObservableCollection in MainWindow:
private ObservableCollection< MyBasicData > _myBasicDataCollection = new ObservableCollection< MyBasicData >();
private ObservableCollection< MyComplexData > _myComplexDataCollection = new ObservableCollection< MyComplexData >();
private ObservableCollection< MyRootData > _myRootDataCollection = new ObservableCollection< MyRootData >();
How can I do that?
Create DataTemplateSelector which iterates through every item and, based on condition, sets its DataTemplate.
class TreeViewTemplateSelector : DataTemplateSelector
{
public DataTemplate BasicTemplate { get; set; }
public DataTemplate ComplexTemplate { get; set; }
public DataTemplate RootTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MyBasicData) return BasicTemplate;
if (item is MyComplexData) return ComplexTemplate;
if (item is MyRootData) return RootTemplate;
return null;
}
}
XAML:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication6">
<Window.Resources>
<DataTemplate x:Key="BasicDataTemplate">
</DataTemplate>
<DataTemplate x:Key="ComplexDataTemplate">
</DataTemplate>
<DataTemplate x:Key="RootDataTemplate">
</DataTemplate>
<local:TreeViewTemplateSelector x:Key="TreeViewDataTemplateSelector" BasicTemplate="{StaticResource BasicDataTemplate}"
ComplexTemplate="{StaticResource ComplexDataTemplate}"
RootTemplate="{StaticResource RootDataTemplate}"/>
</Window.Resources>
<TreeView ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"/>

Cant bind enum to combobox wpf mvvm

A have read a lot of method about the ways of binding enum to combobox. So now in .Net 4.5 it should be pretty ease. But my code dont work.
Dont really understand why.
xaml:
<Window x:Class="SmartTrader.Windows.SyncOfflineDataWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SyncOfflineDataWindow" Height="300" Width="300">
<Grid>
<StackPanel>
<ComboBox ItemsSource="{Binding StrategyTypes}" SelectedItem="{Binding StrategyType}" />
<Button Width="150" Margin="5" Padding="5" Click="Button_Click">Save</Button>
</StackPanel>
</Grid>
xaml.cs backend
namespace SmartTrader.Windows
{
/// <summary>
/// Interaction logic for SyncOfflineDataWindow.xaml
/// </summary>
public partial class SyncOfflineDataWindow : Window
{
public SyncOfflineDataWindow(IPosition position, ContractType type)
{
DataContext = new ObservablePosition(position);
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
}
View Model:
namespace SmartTrader.Entity
{
public class ObservablePosition : NotifyPropertyChanged, IPosition
{
public IEnumerable<StrategyType> StrategyTypes =
Enum.GetValues(typeof (StrategyType)).Cast<StrategyType>();
public ObservablePosition(IPosition position)
{
Strategy = position.Strategy;
}
private StrategyType _strategyType = StrategyType.None;
public StrategyType Strategy
{
get { return _strategyType; }
set
{
_strategyType = value;
OnPropertyChanged();
}
}
}
}
StrategyType is enum.
All i have got it is empty dropdown list
You are trying to bind to a private variable, instead, your enum should be exposed as a Property.
public IEnumerable<StrategyTypes> StrategyTypes
{
get
{
return Enum.GetValues(typeof(StrategyType)).Cast<StrategyType>();
}
}
Also, Discosultan has already solved another problem for you.
Simplest way to bind any enum data to combobox in wpf XAML:
Add data provider in window or user control resource
xmlns:pro="clr-namespace:TestProject">
<UserControl.Resources>
<ObjectDataProvider x:Key="getDataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="pro:YourEnumName"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
<!--ComboBox xaml:-->
<ComboBox ItemsSource="{Binding Source={StaticResource getDataFromEnum}}"/>
I have found a solution on youtube. Check the link below:
How to Bind an Enum to a ComboBox in WPF: https://youtu.be/Bp5LFXjwtQ0
This solution creates a new class derived from MarkupExtension class and uses this class as a source in the XAML code.
EnumBindingSourceExtention.cs file:
namespace YourProject.Helper
{
public class EnumBindingSourceExtention : MarkupExtension
{
public Type EnumType { get; private set; }
public EnumBindingSourceExtention(Type enumType)
{
if (enumType == null || !enumType.IsEnum)
{
throw new Exception("EnumType is null or not EnumType");
}
this.EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Enum.GetValues(EnumType);
}
}
}
View.Xaml file:
<Window
xmlns:helper="clr-namespace:YourProject.Helper"/>
<ComboBox
ItemsSource="{Binding Source={helper:EnumBindingSourceExtention {x:Type local:TheEnumClass}}}" />
local:TheEnumClass: TheEumClass should be located where you specify the namespace (in this case, it is on local)

Dynamically displaying Items using FlipView and DataTemplateSelector in WinRT

I'm using Flipview and a DataTemplateSelector to determine at runtime which DataTemplate to apply to show items in my control.
I have two DataTemplate's, one is static and the second can be used by a undetermined number of items.
Currently
My first view displays:
- "This is a test - Content"
Followed by 18 other views that look like this:
- "http://www.google.com/ 0"
- "http://www.google.com/ 1"
- "http://www.google.com/ 2"
- and so on until 17
I want
The items "http://www.google.com/ " to be grouped as 3 on a view.
For example the second view will display:
"http://www.google.com/ 0, http://www.google.com/ 1, http://www.google.com/ 2"
The third view will display:
"http://www.google.com/ 3, http://www.google.com/ 4, http://www.google.com/ 5"
And so on..
Bellow is my code:
FlipViewDemo.xaml
<Page.Resources>
<DataTemplate x:Key="FirstDataTemplate">
<Grid>
<TextBlock Text="{Binding Content}" Margin="10,0,18,18"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SecondDataTemplate">
<TextBox Text="{Binding Url}"></TextBox>
</DataTemplate>
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"
FirstTextTemplate="{StaticResource FirstDataTemplate}"
SecondTextTemplate="{StaticResource SecondDataTemplate}">
</local:MyDataTemplateSelector>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<FlipView x:Name="itemGridView" ItemTemplateSelector="{StaticResource MyDataTemplateSelector}"
Margin="265,220,284,162">
</FlipView>
</Grid>
FlipViewDemo.xaml.cs
public sealed partial class FlipViewDemo : Page
{
public FlipViewDemo()
{
this.InitializeComponent();
var items = new List<BaseClass>();
items.Add(new FirstItem
{
Content="This is a test - Content"
});
for (int i = 0; i < 18; i++)
{
items.Add(new SecondItem
{
Url = "http://www.google.com/ " + i.ToString()
});
}
itemGridView.ItemsSource = items;
}
}
public class BaseClass
{
}
public class FirstItem : BaseClass
{
public string Content { get; set; }
}
public class SecondItem : BaseClass
{
public string Url { get; set; }
}
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate FirstTextTemplate { get; set; }
public DataTemplate SecondTextTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item,
DependencyObject container)
{
if (item is FirstItem)
return FirstTextTemplate;
if (item is SecondItem)
return SecondTextTemplate;
return base.SelectTemplateCore(item, container);
}
}
I'm thinking that maybe this can be achieved with groups and list view. But I'm not sure how this can be done.
Probably it is a stupid question but, using Google, I can't find an answer. Also english is not my native language; please excuse typing errors.
I think the way to achieve what you are looking for is to expose the data in a way that better represents what you want to display. Then, you can use nested controls to display it. I just threw this together (using my own test data). It is probably not exactly what you want, but it should help you figure things out.
ViewModel
Here I made a helper method to build the collection with sub-collections that each have 3 items.
class FlipViewDemo
{
private List<object> mData;
public IEnumerable<object> Data
{
get { return mData; }
}
public FlipViewDemo()
{
mData = new List<object>();
mData.Add("Test String");
for (int i = 0; i < 18; ++i)
{
AddData("Test Data " + i.ToString());
}
}
private void AddData(object data)
{
List<object> current = mData.LastOrDefault() as List<object>;
if (current == null || current.Count == 3)
{
current = new List<object>();
mData.Add(current);
}
current.Add(data);
}
}
class TemplateSelector : DataTemplateSelector
{
public DataTemplate ListTemplate { get; set; }
public DataTemplate ObjectTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is List<object>) return ListTemplate;
return ObjectTemplate;
}
}
Xaml
Here I use an ItemsControl to vertically stack the items in the data. Each item is either a list of three objects or a single object. I use a FlipView for each of the lists of three objects and a simple ContentPresenter for the single objects.
<Page.Resources>
<DataTemplate x:Key="ListTemplate">
<FlipView
ItemsSource="{Binding}">
<FlipView.ItemTemplate>
<DataTemplate>
<ContentPresenter
Margin="0 0 10 0"
Content="{Binding}" />
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</DataTemplate>
<DataTemplate x:Key="ObjectTemplate">
<ContentPresenter
Margin="0 0 10 0"
Content="{Binding}" />
</DataTemplate>
<local:TemplateSelector
x:Key="TemplateSelector"
ListTemplate="{StaticResource ListTemplate}"
ObjectTemplate="{StaticResource ObjectTemplate}" />
</Page.Resources>
<ItemsControl
ItemsSource="{Binding Data}"
ItemTemplateSelector="{StaticResource TemplateSelector}" />
Note: You usually would not need a template selector for something like this, but since you need to select between a List<T> and an Object, there is no way I know of to recognize the difference using only the DataTemplate.TargetType property from Xaml due to List<t> being a generic type. (I tried {x:Type collections:List`1} and it did not work.)
You need to group items in viewmodel, and databind ItemsSource to the groups. In flipview's itemtemplate you display items in group.
public class PageGroup : PageBase {
public ObservableColection<BaseClass> Items { get; set; }
}
public ObservableCollection<PageBase> Pages { get; set; }
<FlipView ItemsSource="{Binding Pages}">
<FlipView.ItemTemplate>
<DataTemplate DataType="local:PageGroup">
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}" />
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
In order to display first page differently from others:
public class FirstPage : PageBase {
public string Title { get; }
}
Pages.Insert(0, new FirstPage());
and you need to use another datatemplaeselector or impicit datatemplates in FlipView to differentiate between FirstPage and PageGroup
<FlipView ItemsSource="{Binding Pages}"
ItemTemplateSelector="{StaticResource PageTemplateSelector}" />
You don't need to worry about selecting the appropriate template based on the class type, you can simply define the class in the DataTemplate itself.
<DataTemplate TargetType="{x:Type myNamespace:FirstItem}">
...
</DataTemplate>
You'll need to specify where the class is by adding the namespace at the top of your page:
xmlns:myNamespace="clr-namespace:MyApp.MyNamespace"

Categories