Localize strings in XAML UI in UWP - xaml

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>

Related

How to use Xamarin to call an UI Element with a bound name

I'm trying to use a custom variable to name UI Elements in XAML in order to use them in my Xamarin code. How would I do that?
I know that I can use the tags
<Label x:Name="CallVariable" Text="This will appear"/>
and I can call the label from Xamarin using
Label foo = FindByName("CallVariable") as Label;
and mess with the text with the following
foo.Text == "This will appear"
which should return true.
I learned about binding, and so I tried to use this in my variables.
<Label x:Name={Binding Name}/>
Label bar = FindByName(emp.training[i] as Label);
Unfortunately, every time I run it, I'm receiving the error:
System.NullReferenceException: Object reference not set to an instance of an object.
I remembered to set the BindingContext. And in case it's important, this is all happening within a list view.
Is x:Name a bindable object? Or is there another method I should be using? Maybe a way to call an object based on its label, or something?
My current task is as follows:
I have a list of people's information. Name, Age, Gender, Email, Location, and a few other pieces of information. I'm trying to get someone to search for certain types of people, have it return a list of everyone in a ListView, and have the person be able to select as many as they want. I did this using a button that adds the person to a list (or removes them from the list if they're already on). If the user chooses to, they should be able to also click "Select All" to add everyone. I've got the individual adding down, I just need to somehow select all of them.
You can't access the label with a listView by x:Name in code behind. Instead of access the label in the listView, you can bind the property of label in code behind. For example:
<ListView x:Name="testListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" />
<Label Text="{Binding Desc}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And in code behind, it should be:
public partial class MainPage : ContentPage
{
ObservableCollection<testViewModel> myModels = new ObservableCollection<testViewModel>();
testViewModel model;
public MainPage()
{
InitializeComponent();
testListView.ItemsSource = myModels;
myModels.Add(new testViewModel { Name = "age" });
myModels.Add(new testViewModel { Name = "gender" });
myModels.Add(new testViewModel { Name = "name" });
}
}
class testViewModel
{
public string Name { get; set; }
public string Desc { get; set; }
}
Next time you want to update the text of label:
public void test() {
//Get the model you want to change
model.Desc = "This will appear";
}
I've got the individual adding down, I just need to somehow select
all of them.
I don't know how you implement the individual adding down, share the code and maybe I can give you some suggestions.

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.

Dynamically change DataTemplate for a ListView at Runtime

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>

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.

How to have an enum value in CommandParam in XAML

I have the following enum that represent a state of UI (I use it to enable and disable UI elements):
enum Mode
{
EDIT, RUN, REVIEW
}
I would like to pass Mode.EDIT to command in CommandParam:
<Button Grid.Column="6" VerticalAlignment="Top Command="{Binding Path=ChangeMode}"
CommandParameter="{StaticResource local:Mode.RUN}" />
But I have no idea how to declare it. As you see in the button declaration, I try to use StaticResource but it fails. I am quite new to SL4 and C# so I suppose that I missed something.
I have found a solution. I have created in my MyViewModel (my DataContext) 3 public attributes (of type Mode) and initialize them in the constructor (with values EDIT, RUN, REVIEW). Next, I have bound them in XAML as a normal property of a DataContext:
CommandParameter="{Binding Path=EDIT}
class MyViewModel
{
public Mode EDIT {set; get;}
public Mode RUN {set; get;}
public Mode REVIEW {set; get;}
MyViewModel()
{
EDIT = Mode.EDIT;
...
}
}
In WPF we can do something like this (might not work in SL) -
<Button Grid.Column="6" Command="{Binding Path=ChangeMode}"
CommandParameter="{x:Static local:Mode.RUN}" />
check this question for more details -
Passing an enum value as command parameter from XAML
in silverlight x:Static doesn't work so we can do sth like this:
<Button Command="{Binding Path=ChangeMode}">
<Button.CommandParameter>
<Mode>RUN</Mode>
</Button.CommandParameter>
</Button>