Dynamically change DataTemplate for a ListView at Runtime - xaml

I have 2 DataTemplates for displaying the contents of ClassA or ClassB inside a single ListView; which template to select will be based on a RadioButton selection by the user.
Is it possible to change the ItemTemplate of a ListView (in XAML) based on user input dynamically at runtime?
An example snippet of code:
XAML Page:
<Page...>
<Page.Resources>
<DataTemplate x:Key="ClassAListViewItemTemplate" x:DataType="vm:ClassA" ... />
<DataTemplate x:Key="ClassBListViewItemTemplate" x:DataType="vm:ClassB" ... />
</Page.Resources>
<RelativePanel>
<RadioButton Content="ClassA" ... />
<RadioButton Content="ClassB" ... />
<ListView DataContext="{Binding Path=MainViewModel}"
ItemsSource="{Binding ListOfClassAOrB, Mode=TwoWay}"
ItemTemplate="{StaticResource ClassAListViewItemTemplate}"/>
</RelativePanel>
</Page>
I have stripped the code down somewhat to the essentials, but I would like to be able to change the following at runtime:
ItemTemplate="{StaticResource ClassAListViewItemTemplate}"
I have seen solutions for Classic WPF applications that use Style.Triggers, but these aren't applicable for UWP
Marco Minerva's blog on Adaptive Triggers, RelativePanel and DataTemplate in the Universal Windows Platform talks of using UserControls within DataTemplates to modify the visual state using Adaptive Triggers, but this doesn't take into account switching out of templates based on user input
The closest answer I have found to my problem is another blog he wrote "Dynamically choose DataTemplate in WinRT" where there is an element of code-behind involved - but it only appears to be an if statement - but its the cleanest solution I have come across thus far, and what I'd like to replicate in XAML
Thanks

you need to use overwrite SelectTemplateCore of Data template. Change your view model like this.
Below code will helps you.
public class SampleViewModel : DataTemplateSelector
{
public DataTemplate ClassAListViewItemTemplate{ get; set; }
public DataTemplate ClassBListViewItemTemplate{ get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemsData = item as SampleClass; // add your Data class
if (itemsData.IsAddButton == false) // define any property to select the datatemplate
{
return ClassAListViewItemTemplate;
}
else
{
return ClassBListViewItemTemplate;
}
}
}
Add your two datatemplates to one key, and give the key to ItemTemplateSelector property in gridview.
<viewModels:SampleViewModel x:Key="FeedbackTempateSelector"
ClassAListViewItemTemplate="{StaticResource ClassAListViewItemTemplate}"
ClassBListViewItemTemplate="{StaticResource ClassBListViewItemTemplate}">
</viewModels:SampleViewModel>

Related

Localize strings in XAML UI in UWP

I have a resource entry named info_278 in the Resources.resw file on my UWP app. I have 3 scenarios where I need to use this resource but looks like I need to duplicate this to cater to different scenarios. Scenarios are as follows.
Error message content from code
var displayErrorOnPopup = ResourceHandler.Get("info_278");
TextBlock Text property from XAML (Looks like a new entry needed as info_278.Text)
<TextBlock x:Uid="info_278" Margin="10,0,0,0" />
Button Content property from XAML (Looks like a new entry needed as info_278.Content)
<Button x:Uid="info_278" Margin="10,0,0,0" />
How do I proceed without duplicating this resource in the .resw file?
The only way to avoid duplication is to set the string value in code-behind using ResourceLoader. Because you could direct access to the specific property of the target control. Like this:
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
this.TextBlock.Text = resourceLoader.GetString("info_278");
If you are not going to do it in the code behind, then I have to say there is no way to avoid the duplication of the resource string. You should add info_278.Text and info_278.Content for different XAML scenarios.
You could create a markup extension. I've used this in WinUI 3, but should work in UWP too.
using Microsoft.UI.Xaml.Markup;
using Windows.ApplicationModel.Resources;
namespace MyApp;
[MarkupExtensionReturnType(ReturnType = typeof(string))]
public class StringResourceExtension : MarkupExtension
{
private static readonly ResourceLoader _resourceLoader = new();
public StringResourceExtension() { }
public string Key { get; set; } = "";
protected override object ProvideValue()
{
return _resourceLoader.GetString(Key);
}
}
Then in the XAML:
...
local="using:MyApp"
...
<TextBlock Text="{local:StringResource Key=info_278}" />
<Button Content="{local:StringResource Key=info_278}" />
The Content of Button can be a TextBlock:
<Button>
<TextBlock x:Uid="MyTextId" Style="{StaticResource MyTextBlockStyle}" />
</Button>

Xamarin.Forms can you make type related datatemplates?

In WPF you can create a DataTemplate, put it in a ResourceDictionary, assign it a Type and then bind data of that type to a ContentControl and the DataTemplate will be used to render. as in this example:
How do I use the DataType property on a WPF DataTemplate?
the Xamarin.Forms enterprise apps ebook hints at such an ability but does not show any example: https://developer.xamarin.com/guides/xamarin-forms/enterprise-application-patterns/mvvm/#Creating_a_View_Defined_as_a_Data_Template
Can this be done in Xamarin.Forms?
Unfortunately, x:DataType does not working with Xamarin Forms.
(I tried many different ways, but fail.)
You should implement DataTemplateSelector
ResourceDictionary.xaml
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:Solution.Forms"
x:Class="Mango.Forms.LayerDataTemplate">
<!--#region RectLayerView -->
<DataTemplate x:Key="RectLayerDataTemplate">
<forms:RectLayerView forms:ValueX="{Binding ValueX}"
forms:ValueY="{Binding ValueY}"
forms:ValueWidth="{Binding ValueWidth}"
forms:ValueHeight="{Binding ValueHeight}"
forms:MangoColor="{Binding Color}" />
</DataTemplate>
<!--#endregion-->
<forms:LayerDataTemplateSelector x:Key="LayerDataTemplateSelector"
RectLayerTemplate="{StaticResource RectLayerDataTemplate}"/>
</ResourceDictionary>
DataTemplateSelector.cs
public class LayerDataTemplateSelector : DataTemplateSelector
{
public DataTemplate RectLayerTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is RectLayerViewModel)
return RectLayerTemplate;
return null;
}
}
Here is Microsoft document

How to mix dynamic and static items in UWP XAML NavigationView MenuItems?

I'm trying to make a NavigationViewMenu and I need a menu layed out as follows
static Home item
static Header
dynamic elements from DB as items
static Header
static set of items
This is what I tried:
<NavigationView.MenuItems>
<NavigationViewItem Icon="Home" Content="Home" Tag="home" />
<NavigationViewItemSeparator />
<NavigationViewItemHeader Content="My Stuff"/>
<NavigationViewList ItemsSource="{x:Bind MyStuff}">
<NavigationViewList.ItemTemplate>
<DataTemplate x:DataType="local:MyModel">
<NavigationViewItem Icon="Pictures" Content="{x:Bind Name}" Tag="{x:Bind Tag}" />
</DataTemplate>
</NavigationViewList.ItemTemplate>
</NavigationViewList>
<!-- Static equivalent to the above:
<NavigationViewItem Icon="Pictures" Content="Woop" Tag="foos"/>
<NavigationViewItem Icon="Pictures" Content="Doop" Tag="foos"/>
<NavigationViewItem Icon="Pictures" Content="Loop" Tag="foos"/>
-->
<NavigationViewItemHeader Content="Other Stuff"/>
<NavigationViewItem Icon="Pictures" Content="Foos" Tag="foos"/>
<NavigationViewItem Icon="ContactInfo" Content="Bars" Tag="bars"/>
<NavigationViewItem Icon="SwitchApps" Content="Bazes" Tag="bazes"/>
</NavigationView.MenuItems>
This is what I've got:
This is what I wanted:
Is there anything as good and practical as Angular's *ngFor in XAML for UWP?
I ran into the same behavior, and managed to find a work around. In my case, I had two lists of menu items (dynamically data-bound items), and I wanted to use NavigationViewItemHeader on top of both (static items). I tried using a NavigationViewList and ran into your problem.
TL;DR:
Create a list of menu items in C# code. The elements of this list can be a mix of your viewmodels, and any static Navigation Items (headers, separators, etc). Then use a DataTemplateSelector to either databind to your viewmodel or pass-through the navigation items unchanged.
More detailed
In your C# code-behind, create an enumerable (or observable collection) of your menu items. In my case SomeCollection and AnotherCollection represent my data sources that I wanted to bind to my NavigationView. I have to type it as object because it's a mix of my viewmodels and the built-in UWP navigation item types.
private IEnumerable<object> MenuItems()
{
yield return new NavigationViewItemHeader { Content = "Some List" };
foreach (var some in SomeCollection)
{
yield return some;
}
yield return new NavigationViewItemHeader { Content = "Another List" };
foreach (var another in AnotherCollection)
{
yield return another;
}
}
// somewhere else, like in your Page constructor or a CollectionChanged handler
this.NavigationList = MenuItems().ToList();
Second, create a Data Template Selector to switch between your template and the navigation items:
class NavigationItemTemplateSelector : DataTemplateSelector
{
public DataTemplate ViewModelTemplate{ get; set; }
public DataTemplate NavigationItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
return item is MyViewModel
? ViewModelTemplate
: NavigationItemTemplate;
}
}
Finally, change your NavigationView to reference the template selector and menu item source. The NavigationItemTemplate is just a pass-through, and your ViewModelTemplate would have the normal viewmodel item binding logic.
<Page.Resources>
<DataTemplate x:Key="ViewModelTemplate" x:DataType="local:MyViewModel">
<TextBlock Text="{x:Bind SomeProperty}" />
</DataTemplate>
<DataTemplate x:Key="NavigationItemTemplate">
</DataTemplate>
<local:NavigationItemTemplateSelector x:Key="NavigationItemTemplateSelector"
ViewModelTemplate="{StaticResource ViewModelTemplate}"
NavigationItemTemplate="{StaticResource NavigationItemTemplate}" />
</Page.Resources>
<NavigationView
MenuItemsSource="{x:Bind NavigationList, Mode=OneWay}"
MenuItemTemplateSelector="{StaticResource NavigationItemTemplateSelector}">
<Frame x:Name="ContentFrame"></Frame>
</NavigationView>
I can reproduce it. It looks like NavigationViewList only take the space of one item when putting itself in NavigationView.MenuItem. Which is the same like putting a ListView in a ListViewItem. To change this behavior we need to change the item's behaviour ourselves. However after some investigating it seems currently customization of NavigationViewList is blackbox for us. So the only way I could think is to build our own NavigationView with the help of splitview and acrylic.
I didn't find it necessary to use different templates as in the accepted answer, maybe because there were some changes in the underlying Windows code in the meantime. As I needed a stable part of the menu and then a dynamic part depending on the actual page, I created an interface:
interface IMenuProvider {
IEnumerable<NavigationViewItemBase> GetMenuItems();
}
and made sure all my pages implement it. My MainPage returns the fixed part:
public IEnumerable<NavigationViewItemBase> GetMenuItems() {
yield return new NavigationViewItem {
Tag = "home",
Icon = new SymbolIcon(Symbol.Home),
Content = "Home",
};
yield return new NavigationViewItemSeparator();
yield return new NavigationViewItem {
Tag = "xxx",
Icon = new SymbolIcon(Symbol.XXX),
Content = "XXX",
};
}
the other pages, similary, provide their own menu headers and items.
When I navigate the pages, I change the menu as well, concatenating the fixed and variable parts:
ContentFrame.Navigate(PageType, null, transitionInfo);
if (ContentFrame.Content is IMenuProvider menuProvider)
= GetMenuItems().Concat(menuProvider.GetMenuItems()).ToList();
(Or, you might place the menu change into the Navigated handler of the Frame.)
While it's still a nuisance that these menus, at least the fixed part, cannot be declared in XAML, this approach works.

How to use template selector within a ContentPresenter in Windows 8.1

I have a Windows 8.1 application. I have a requirement of selecting different templates based on a certain value. For this purpose I'm using ContentPresenter in the xaml with a Static Resource TemplateSelector.
Here's my datatemplates and templateselector in xaml resources
<DataTemplate x:Key="template1">
<TextBox Text="Temp 1" />
</DataTemplate>
<DataTemplate x:Key="template2">
<TextBox Text="Temp 2" />
</DataTemplate>
<DataTemplate x:Key="template3">
<TextBox Text="Temp 3" />
</DataTemplate>
<template:BalanceTypesTemplateSelector x:Key="MySelector"
Template1="{StaticResource template1}"
Template2="{StaticResource template2}"
Template3="{StaticResource template3}" />
Here's my ContentPresenter XAML
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}"
Content="{Binding MyData}" />
Here's my Template Selector Code
public class BalanceTypesTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public DataTemplate Template3 { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
var type = item.ToString();
switch (type)
{
case "t1":
return Template1;
case "t2":
return Template1;
case "t3":
return Template3;
default:
throw new NotSupportedException();
}
}
return null;
}
}
But it's not hitting the templateselector code at all. The string that is bound is directly displayed on the display when I run the app.
I would be glad if some one point me in the right direction. Thanks in Advance.
Basically, you're only overriding one of the SelectTemplateCore overloads.
From the DataTemplateSelector docs:
To define an effective DataTemplateSelector subclass, provide implementations for SelectTemplateCore(Object) and SelectTemplateCore(Object, DependencyObject)
Once you provide an implementation for SelectTemplateCore(Object, DependencyObject), it will get invoked.
I tried to do it, but there was another problem I encountered - the object is always null (and not the Content/DataContext of the ContentPresenter).
I asked Google why is that and found this discussion. From it:
The ContentControl and ContentPresenter appear to be broken in Windows RT when used with a ContentTemplateSelector property bound to a view model. The 'object' parameter to the template selector is always null.
There's also a workaround for this problem at the end of that discussion.
Hope this helps. :)
Using ContentControl instead of ContentPresenter is working for me. Thanks #KaiBrummund for his comment on my question.

Change font colour inside combo box, data coming from SQL

Hard to put into words for title. I have a normal WPF combo box and the data (list of names) is getting pulled from SQL and I want to change the text colour and
Foreground ="Black"
only seems to be working when I actually select the user. Any suggestions how else I can change this?
EDIT: I haven't tried any other things as of yet as I know that way to actually change the text colour.
EDIT2:
<ComboBox x:Name="cmbDepartment" HorizontalAlignment="Left" Height="25" Margin="92,580,0,0" VerticalAlignment="Top" Width="400" Foreground="#FFA2A2A2" FontSize="13"/>
This is my XAML code for the combo box. I have figured out that my theme is making it blue but when I change the font colour on my theme everything then turns that colour in my application. Is there a piece of code that I can write in my XAML which will set the colour of everything in the combo box grey, without changing the colours in my application.
I assume that initially the ComboBox shows no selected value and once you click on it it shows the list of names with the proper color (being whatever color you assigned through the Foreground property).
If so, may it be the case that you haven't selected an item? Once you have set the items, you must select an item (e.g. SelectedIndex, SelectedValue) if you don't want the ComboBox selection to appear empty.
Excuse me if this is not the case, but the question was pretty vague..
Here is a example using MVVM
XAML
<Window x:Class="SelfBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox ItemsSource="{Binding MyItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Foreground="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
codebehind
using System.Collections.Generic;
using System.Windows;
namespace SelfBinding
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
public class MyViewModel
{
public List<MyItem> MyItems { get; set; }
public MyViewModel()
{
MyItems = new List<MyItem>();
MyItems.Add(new MyItem { Name = "Black" });
MyItems.Add(new MyItem { Name = "Red" });
MyItems.Add(new MyItem { Name = "Orange" });
MyItems.Add(new MyItem { Name = "Green" });
}
}
public class MyItem
{
public string Name { get; set; }
}
}
to test it on your on create a new WPFproject an copy & past the code
Maybe you can override the theme colors in the combobox resources. This is an exaple for doing so.
I just don't know what exactly is the key that you need to override. I guess you can google that.
good luck.