MarkupExtension not working with Label? - xaml

I don't understand why my MarkupExtension StringFormat does not work with labels but works perfectly with TextBlocks.
Here is a simplified version of my code:
[MarkupExtensionReturnType(typeof (BindingExpression))]
public class Format : MarkupExtension
{
#region Public Properties
public BindingBase Binding { get; set; }
public IValueConverter Converter { get; set; }
public string StringFormat { get; set; }
#endregion
#region Public Methods and Operators
public override object ProvideValue(IServiceProvider serviceProvider)
{
this.Binding.StringFormat = this.StringFormat;
return this.Binding.ProvideValue(serviceProvider);
}
#endregion
}
And the XAML:
<TextBlock Text="{wpfApplication31:Format Binding={Binding Name}, StringFormat=X_{0}}" /> <!-- StringFormat WORKING -->
<Label Content="{wpfApplication31:Format Binding={Binding Name}, StringFormat=X_{0}}" /> <!-- StringFormat NOT WORKING -->
The property 'Name' is a simple string.
I think it's related to the fact that Label is a more complex object than TextBlock but I still don't understand why the StringFormat is not applied.
If anybody can help.
Thx

From this answer :
The reason this doesn't work is that the Label.Content property is of type Object, and Binding.StringFormat is only used when binding to a property of type String.
And to make it work you'd need to adapt the behaviour of your MarkupExtension to make it set the Label.ContentStringFormat when it's used for the Label.Content property. You can use the IServiceProvider that you recieve as the first parameter of the ProvideValue method.

Related

Why will a child property not update when databinding in XAML

Can someone explain to me why the first property below (Name) updates fine from the UI, but the second one (End) does not? Both properties display correctly, so it IS bound. It just won't update the child property.
Period.Period (not my choice in the naming) is defined as a datetimeoffset.
<custom:FieldControl TargetObject="{Binding Path=Period}" TargetProperty="Name" IsReadOnly="False" />
<custom:FieldControl TargetObject="{Binding Path=Period.Period}" TargetProperty="End" IsReadOnly="False" />
I'm very new to XAML, so, if I haven't included enough detail, let me know and I'll edit the question.
You probably need to implement a INotifyPropertyChanged.
Here is an example with your two properties:
public class PeriodSample : INotifyPropertyChanged
{
private string name;
private DateTimeOffset period;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public DateTimeOffset Period
{
get
{
return period;
}
set
{
period = value;
OnPropertyChanged(nameof(Period));
}
}
public PeriodSample()
{
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
This concept allow to notify the Control when the binded properties changed.
If that doesn't solve the problem, could you provide a more complete example please?

Microsoft.AspNetCore.Components.Forms.InputRadioGroup` does not support the type xxx

I want to use radio group in blazor so after implementing edit form and select one of the radio button I got this error :
Microsoft.AspNetCore.Components.Forms.InputRadioGroup`1[EGameCafe.SPA.Models.GameModel] does not support the type 'EGameCafe.SPA.Models.GameModel'.
here is my edit form :
<EditForm Model="ViewModel" OnValidSubmit="HandleCreateGroup">
#if (ViewModel.Games.List.Any())
{
<InputRadioGroup Name="GameSelect" #bind-Value="Gamemodelsample">
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game" />
#game.GameName
<br />
}
</InputRadioGroup>
}
</EditForm>
#code{
public GameModel GameModelSample { get; set; } = new();
}
and GameModel is :
public class GameModel
{
public string GameId { get; set; }
public string GameName { get; set; }
}
The InputRadioGroup, like other Blazor components, supports only a limited amount of types like String or Int32. You had the right idea, but unfortunately, you run into a kind of limitation of Blazor.
You could try to create a wrapper field.
private String _selectedGameId = "<Your Default Id>";
public String SelectedGameId
{
get => _selectedGameId;
set
{
_selectedGameId = value;
// Set the property of the ViewModel used in your Model Property of the EditContext or any other property/field
ViewModel.SelectedGame = ViewModel.Games.List?.FirstOrDefault(x => x.GameId == value);
}
}
Use the property SelectedGameId as the bind value of the InputRadioGroup component.
<InputRadioGroup Name="GameSelect" #bind-Value="SelectedGameId" >
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game.GameId" />
#game.GameName
<br />
}
</InputRadioGroup>
As an alternative, you can create a custom component that inheriting from InputRadioGroup to create a kind of GameBasedInputRadioGroup. If you are interested I can post a sample.
Because in your code #bind-Value="Gamemodelsample",you are trying to bind GameName(string) to Gamemodelsaple(object), which will cause type mismatch problems.
You only need to modify your code to:
#bind-Value="GameModelSample.GameName"

Xamarin.Forms change UI language at runtime (XAML)

I am using Strings.resx, Strings.de.resx, etc. to localize Xamarin.Forms app.
I need to be able to change interface language at run time, and it (allmost) works.
Xamarin generates static class Strings in namespace MyProject.Resources from resource files, and I use those values to display strings on UI.
When doing it from code, it works flawlessly:
await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));
Problem is - not all attributes defined this way from XAML are updated when I change UI culture during runtime.
Buttons, Labels, Entry properties (Placeholder etc.) change as they should, but PageTitle, Toolbaritems, and some other properties remain in previous language.
I presume that some of these are populated when Page is first created, and are not updated on culture (and UI culture) change.
So, basically, I need a way to combine {DynamicResource ...} with values from resources.
I know that DynamicResource is ment to be used with Resource dictionary, but that is not a good way to store language translations for localization.
I tried
Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"
also not working.
Is there a way of refreshing page dynamicaly?
I also tried calling
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));
from Appearing event for that page, but that also does not work.
Any ideas?
Part of XAML file
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject.View"
xmlns:rs="clr-namespace:MMPI"
x:Class="MyProject.MainListPage"
xmlns:lr="clr-namespace:MyProject.Resources"
Title="{x:Static lr:Strings.appName}"
>
<ContentPage.ToolbarItems>
<ToolbarItem
Name="New"
Order="Primary"
Priority="0"
Text="{x:Static lr:Strings.lblAddNew}"
Clicked="New_Clicked"
>
When i encountered that challenge in a project I resolved it by using a simple class ResourceLoader and making use of INotifyPropertyChanged.
You can access the Instanceproperty from anywhere and change the culture. All String that are bound to the index would update.
The ResourceManager instance injected into the constructor must be set up appropriately.
public class ResourceLoader : INotifyPropertyChanged
{
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
public static ResourceLoader Instance { get; private set; }
public string GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
return stringRes;
}
public string this[string key] => this.GetString(key);
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
To display the localized strings in your application you need to bind through the indexer like so:
<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />
Since it is now bound it should update when you call ResourceLoader.SetCultureInfo because the Item[] 'PropertyName' is causing bound controls to re-fetch the values to their bound keys.
Update
I just tested it if i was talking bogus and for some reason the property changed didn't work. I've added a different approach below, which is close to what i'm using in production i urge you to add some kind of weak reference 'caching' instead of the simple list holding all the string resources (otherwise they will be kept forever)
I'm keeping above for reference.
public class ResourceLoader
{
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
private readonly List<StringResource> resources = new List<StringResource>();
public static ResourceLoader Instance { get; private set; }
public StringResource this[string key] {
get { return this.GetString(key); }
}
public StringResource GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
var stringResource = new StringResource(resourceName, stringRes);
this.resources.Add(stringResource);
return stringResource;
}
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
foreach (StringResource stringResource in this.resources) {
stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
}
}
}
StringResource:
public class StringResource : INotifyPropertyChanged
{
public StringResource(string key, string value)
{
this.Key = key;
this.Value = value;
}
private string value;
public string Key { get; }
public string Value {
get { return this.value; }
set {
this.value = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Binding
<Label Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}"
/>
Update 2
Came across this link where they implemented it similarly to my first approach. Maybe you can give it a try.
Update 3
Fixed the first approach. Both are working now. What was needed was this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); instead of this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));
I solved it very similar to #woelliJ . I just wanted to have key as strongly types from static class and binding should be in code behind.
ITranslationService is singleton from static variable. It is very close like #woelliJ .
[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
private readonly ITranslationService? _translationService;
public TranslateExtension()
{
_translationService = Mobile.App.TranslationService;
}
public string? Text { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var translationItem = _translationService[Text];
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"Value",
Source = translationItem,
};
return binding;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
}
[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
public string? Key { get; set; }
public string? Value { get; set; }
}
Then label would be like this
<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />

Changing part of view at runtime

I show several movie items in an ObservableCollection using a typical listbox+datatemplate view.
However, I want, in the same page, to be able to quickly change the view to what I define a posterview (i.e. only the posterimages in a wrappanel).
The xaml-page uses a viewmodel as datacontext.
Is there a way to basically replace part of the XAML content with another?
And still keep as little code as possible in the codebehind of the view.
I've seen WPF examples that for example use a DataTrigger bound to a viewmodelproperty which is very clean,
such as this article
... but Windows Phone does not have a DataTriggers, correct?
I'm trying to go for an MVVM-ish approach, so as little code as possible in the view code-behind is required.
So I want to change this:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource ListView}" />
To:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource PosterView}" />
DataTemplates with a DataTemplateSelector would be the way to go around this problem.
Base Data Template Selector:
public class DataTemplateSelector : ContentControl
{
public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
{
throw new NotImplementedException();
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ContentTemplate = SelectTemplate(newContent, this);
}
}
Specialized Template Selector for your CinemaShowsOverview
public class CinemaShowsTemplateSelector : DataTemplateSelector
{
public DataTemplate ListTemplate
{
get;
set;
}
public DataTemplate PosterTemplate
{
get;
set;
}
public DataTemplate DefaultTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return DefaultTemplate;
var viewModel = item as CinemaShowsOverview;
if (viewModel != null)
return viewModel.IsPoster ? PowerTemplate : ListTemplate;
else
return DefaultTemplate;
}
}
And then in XAML (replacing your current ContentControl):
<assets:CinemaShowsTemplateSelector PosterTemplate="{StaticResource PosterView}"
ListTemplate="{StaticResource ListView}"
Content="{Binding CinemaShowsOverview}">
Just to be pedantic, the blog you mention describes typed data-templates, not datatriggers (as the author class them). No, this feature is not available in Silverlight for WP7.
You could expose the template you requires as a string within your view model, i.e. a string that is either ListView or PosterView. You then bind your Template property to this view-model property via a value converter that provides the template, which it can access via your applications Resources.

XAML Resource with static items?

<Window.Resource>
<ResourceDictionary>
<local:SomeResourceWithObsCollection x:Key="MyItemWithCollection">
<local:SomeClass.Instance /> <!-- THIS DOES NOT WORK -->
</local:SomeResourceWithObsCollection>
</ResourceDictionary>
</Window.Resources>
I don't know how to get that line to work... I've tried doing <x:Static SomeClass.Instance />, but that also isn't allowed.
[ContentProperty("TheItems")]
public class SomeResourceWithObsCollection
{
public class SomeResourceWithObsCollection()
{
TheItems = new ObservableCollection<IMyInterface>();
}
public ObservableCollection<IMyInterface> TheItems { get; set; }
}
public class SomeClass : IMyInterface
{
private static SomeClass _instance = new SomeClass();
private SomeClass() { }
public SomeClass Instance { get { return _instance; } }
}
You can't do what you're asking to do in XAML as of right now. Perhaps future versions of XAML will account for this. You have to do it in the code behind, here is an example:
Adding a static object to a resource dictionary
The closest I can suggest is a combination of the CompositeCollection and using ListBoxItems (or some other equivalent) to wrap your static content (as I believe you can only pull static content into XAML using the {x:Static} markup extension)
This can be used in XAML as below:
<ListBox>
<ListBox.ItemsSource>
<CompositeCollection>
<ListBoxItem Content="{x:Static local:Example.One}" />
<ListBoxItem Content="{x:Static local:Example.Two}" />
</CompositeCollection>
</ListBox.ItemsSource>
</ListBox>