How to use StringFormat in XAML elements? - xaml

I'm deep in a XAML stack of elements binding to orders.
The order date displays as e.g. "12/31/2008 12:00:00 AM".
I want it to display as e.g. "31.12.2008".
How can I do this? I have seen other stackoverflow questions mention StringFormat but they use multibinding in ways that I can't get to work.
Here is the kind of syntax I would like (this is pseudocode), simply specifying StringFormat where you need it, is this possible somehow?
<StackPanel>
<ListView ItemsSource="{Binding Orders}">
<ListView.View>
<GridView>
<GridViewColumn
Header="Order ID"
DisplayMemberBinding="{Binding Path=OrderID}"
StringFormat="{}{1:dd.MM.yyyy}"/>
<GridViewColumn
Header="Order Date"
DisplayMemberBinding="{Binding Path=OrderDate}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>

I haven't tested it, but I think this should work:
<GridViewColumn
Header="Order Date"
DisplayMemberBinding=
"{Binding Path=OrderDate, StringFormat='{}{0:dd.MM.yyyy}'}"/>

In general, you can look for an associated *StringFormat dependency property. For example, all ContentControl implementations (such as Label and Tooltip) have the ContentStringFormat dependency property:
<Label
Content="{Binding Path=DateAsked}"
ContentStringFormat="{}{0:yyyy/MM/dd HH:mm:ss}" />
In your case, while the GridViewColumn has the HeaderStringFormat dependency property to go along with Header, there is no analog for the DisplayMemberBinding and so you will need .NET 3.5 SP1 (get it with Visual Studio 2008 SP1) or better to use the new BindingBase.StringFormat Property:
<GridViewColumn
Header="Order ID"
DisplayMemberBinding="{Binding Path=OrderID, StringFormat='{}{0:dd.MM.yyyy}'}"
/>
There are lots more examples at the blog post Trying out Binding.StringFormat.

XAML
<UserControl.Resources>
<myNamespace:DateTimeConverter x:Key="DateTimeConverter" />
</UserControl.Resources>
<GridViewColumn
DisplayMemberBinding=="{Binding Path=OrderDate, Converter={StaticResource DateTimeConverter}}"
/>
C#
public class DateTimeConverter : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (value != null)
{
return ((DateTime)value).ToString("dd.MM.yyyy");
}
else
{
return String.Empty;
}
}
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return DateTime.Parse(value.ToString());
}
}

If you wanted to localize the date format, you can include it in your .resx file. You will have to set up your app for localization by following this guide: https://developer.xamarin.com/guides/xamarin-forms/advanced/localization/.
The resx entry:
<data name="DateFormat" xml:space="preserve">
<value>{0:dddd d MMMM H:mm}</value>
</data>
In your content page, you then include the location of the resx file
xmlns:il8n="clr-namespace:MyProject.Localization;assembly=MyProject"
And then you can use it in your binding like so:
<Label
Text = "{Binding Time, StringFormat={il8n:Translate DateFormat}}"

Related

Use ViewLocator inside TabControl.ContentTemplate

In an AvaloniaUI window, I want to have a TabControl whose tabs are added and removed from an ObservableCollection<T>. The tab's "title" (the text appearing on the tab strip) should be set inside each item of the collection, which could belong to a different type.
For that I defined a type:
public abstract class TabViewModelBase : ViewModelBase
{
public abstract string TabHeader { get; }
}
and my collection is defined like this:
public ObservableCollection<TabViewModelBase> OpenTabs { get; } = new();
In the axaml file, this is the definition of the TabControl:
<TabControl Items="{Binding OpenTabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
So far, this works like a charm.
The problem begins when I also want to set up a container for the view inside each tab, which should not be a part of the contained view itself. I've tried by editing the xaml above and setting a ContentTemplate like this:
<TabControl Items="{Binding OpenTabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Border Child="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
However this results in the following error:
[Binding] Error in binding to 'Avalonia.Controls.Border'.'Child': 'Could not convert 'Project.ViewModels.TestingViewModel' to 'IControl'.'
This seems to be because ViewLocator, which automatically matches a view model to a view based on its name, is not being called. I assume this is because I've defined a DataTemplate inside TabControl.ContentTemplate.
Is it possible to instruct Avalonia to use ViewLocator inside TabControl.ContentTemplate, so that a view is selected based on its name?
<Border Child="{Binding}"/>
Border expects an actual control as a child, not a view model. You need to use ContentControl instead. It can also have it's own data template or view locator.
I found a way to work around the issue, by defining an IValueConverter that uses ViewLocator internally:
public class ViewModelValueConverter : IValueConverter
{
public object? Convert(
object value, Type targetType, object parameter, CultureInfo culture
)
{
if (value == null)
return null;
if (
value is ViewModelBase viewModel
&& targetType.IsAssignableFrom(typeof(IControl))
)
{
ViewLocator viewLocator = new();
return viewLocator.Build(value);
}
throw new NotSupportedException();
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture
)
{
throw new NotSupportedException();
}
}
and using it in XAML:
<Window.Resources>
<local:ViewModelValueConverter x:Key="variableView"/>
</Window.Resources>
<TabControl Items="{Binding OpenTabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Border Child="{Binding, Converter={StaticResource variableView}}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
but it feels like there might be a simpler solution.

Cannot bind my Converter class in my XAML command that pass parameter

This is my XAML that is trying to have my ListView pass a parameter to the ViewModel command.
xmlns:mvvm="http://www.galasoft.ch/mvvmlight"
<ListBox x:Name="MyListView" ItemsSource="{Binding Objects}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding MyCommand}"
PassEventArgsToCommand="True"
EventArgsConverter="{StaticResource ParamConverter }"
EventArgsConverterParameter ="{Binding Name}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this is my converter:
public class ParamConverter : IEventArgsConverter
{
public object Convert(object value, object parameter)
{
var args = (SelectionChangedEventArgs)value;
var name = parameter as string;
return (string)name.ToString();
}
}
But I got the following error:
The resource "ParamConverter" could not be resolved
Your converter is not declared in XAML, you should add something like
<ListBox.Resources>
<yournamespace:ParamConverter x:Key="ParamConverter"/>
</ListBox.Resources>
inside your listbox tags.
EDIT: I'm not an expert at all, so bear with my "imprecise" terms if any. I think you're missing something: there is no magic that allows your XAML to be aware of your C#. You need to tell XAML that somewhere in your code (in yournamespace) there will be a ParamConverter object, that can be referenced inside xaml with ParamConverter key.
You can declare your resource locally inside ListBox tags as suggested, or at outer scope if needed.
Once resource is declared inside XAML, you can access it via StaticResource.

How can I make it so that a control is visible only if a binding value is not an empty space?

I have this XAML:
<t:FooterTemplate Text="{Binding SourceFooter }" />
The object has margins and color.
How can I make it so that it's not visible using the IsVisible, if the value of SourceFooter = "" ?
I want to do something like:
<t:FooterTemplate Text="{Binding SourceFooter }" IsVisible="{ SourceFooter != "" }" />
But I know that's not possible to do.
First Option: Converter
You can use a converter for this. For example:
<t:FooterTemplate Text="{Binding SourceFooter }" IsVisible="{Binding SourceFooter, Converter={StaticResource StringEmptyConverter} }" />
And then the converter looks like this:
public class StringEmptyConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return !String.IsNullOrWhiteSpace(((string)value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
The converter simply returns false if the string is whitespace or null.
To use it as a StaticResource, of course you'll also have to add a line in the ResourceDictionary in the App.xaml file.
<converters:StringEmptyConverter x:Key="StringEmptyConverter" />
Second option: DataTrigger
Triggers can modify the properties of the visual element. As the name says: they are triggered when a certain property (SourceFooter) equals a certain value ("").
<t:FooterTemplate Text="{Binding SourceFooter}" >
<t:FooterTemplate.Triggers>
<DataTrigger
Binding="{Binding SourceFooter}"
TargetType="t:FooterTemplate"
Value="">
<Setter Property="IsVisible" Value="false" />
</DataTrigger>
</t:FooterTemplate.Triggers>
</t:FooterTemplate>
What do I use?
In this case you should go for the Converter solution. You're probably gonna be checking if a string is empty somewhere else in your app.
In my opinion, DataTriggers can be useful for more exotic 'Triggers'.

XAML converter binding for multiple properties

I want to display some text in a Xamarin forms ListView grid, based on the comparison of 2 date properties. I have a converter to compare the dates and return a string. What I am trying to do (without success) is pass the entire object to the converter.
XAML:
<ResourceDictionary>
<converters1:CancelConverter x:Key="CancelConverter" />
</ResourceDictionary>
...
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label Grid.Column="0" Text="{Binding ., Converter={StaticResource CancelConverter}}" />
</Grid>
</ViewCell>
</DataTemplate>
Converter:
public class CancelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Schedule schedule = ((Schedule)value);
DateTime date1 = schedule.ProposedCollectionDate;
DateTime date2 = schedule.OrderDate;
if (date1.CompareTo(date2) < 0)
{
return "Cancel this order";
}
}
}
The problem is that in the converter, value is null. How can I pass the object to the Converter, instead of just a single property of the object?
What you have shown looks good to me, but there may be something bad in what you've hidden. I can't tell, but are you properly setting the binding context of the ListView i.e. are you setting the ItemsSource property? From the information you've given, you should be setting it to something like an ObservableCollection<Schedule> or IEnumerable<Schedule> and then each item in the ListView would have a Schedule as its binding context and it would make its way into the value converter happily.

Retrieving value from static extension XAML

How do I retrieve the value (Int32.MaxValue) from the static extension:
<x:Static
x:Key="TooltipTimeout"
Member="s:Int32.MaxValue"
/>
...
<blablalba TooltipService.ShowDuration="{StaticResource TooltipTimeout}"/> <-- this does not work by the way
Methinks you're doing something else wrong. Slap this in kaxaml:
<Page
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<x:Static x:Key="Derp" Member="sys:Int32.MaxValue"/>
</Page.Resources>
<Grid>
<TextBlock
ToolTipService.ShowDuration="{StaticResource Derp}"
ToolTip="Derp" Text="Herp" />
</Grid>
</Page>
Mod tested, mother approved.
If I had to guess, I think you're not defining your xml namespace for Int32 correctly.
In WPF, You can access static member directly, like this,
<TextBlock TooltipService.ShowDuration="{x:Static s:Int32.MaxValue}"/>
However, you cannot do the same in Silverlight, as it wouldn't work. In silveright, you've to write a wrapper class, like this,
public class StaticMemberAccess
{
public int Int32Max { get { return Int32.MaxValue; } }
//define other wrapper propeties here, to access static member of .Net or your classes
}
Then do this in XAML,
<UserControl.Resources>
<local:StaticMemberAccess x:Key="SMA"/>
</UserControl.Resources>
<TextBlock TooltipService.ShowDuration="{Binding Source={StaticResource SMA}, Path=Int32Max}"/>
.
Try to bind to your resource by setting it as the Source of your binding:
{Binding Source={StaticResource TooltipTimeout}}