I am developing a chat application in Xamarin Forms and I am trying to add conditional formatting depending on whether it is an incoming or outgoing message.
This is my XAML:
<Frame
Margin="1"
Padding="0"
x:Name="FrameRef"
x:DataType="model:ChatMessage">
<Frame
CornerRadius="10"
Padding="7"
BackgroundColor="LightBlue"
HasShadow="false"
Margin="10,10,80,0">
<Frame.Triggers>
<DataTrigger
TargetType="Frame"
Binding="{Binding Source={x:Reference FrameRef}, Path=x:DataType.From}" Value="+1456456456">
<Setter Property="BackgroundColor" Value="Yellow"/>
</DataTrigger>
</Frame.Triggers>
When I use Path="Margin" and Value="1" it works.
I am now trying to make it work with the Path being x:DataType="model:ChatMessage" and checking the 'from'-field (indicating if the message was incoming or outgoing).
I'm not sure the Data Trigger is quite right for this application, since you're really depending on a data type and not really the content per se of another field. From the documentation:
The DataTrigger class is suitable for checking values on other controls, as well as any property on the control to which it has been added.
What you probably want instead is a Value Converter that handles locating a StaticResource and applying a style for you based on the message type. Full Microsoft Documentation here.
On your XAML element, you'd do something like this:
<Frame Style="{Binding foo, Converter={StaticResource FooToStyleConverter}}"/>
Your converter would work something like this:
public class FooToStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someValue = (DataTye)value; // Convert 'object' to whatever type you are expecting
// evaluate the converted value
if (someValue.From != null && someValue.From == Enum.SomeoneElse)
return (Style)App.Current.Resources["StyleReceived"]; // return the desired style indicating the message is from someone else
return (Style)App.Current.Resources["StyleSent"]; // return a style indicating the message is from the sender
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Usually unused, but inverse the above logic if needed
throw new NotImplementedException();
}
}
Lastly, set up your converter as a Static Resource in App.xaml (or as a local resource on the page) so your page can properly reference it
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<ResourceDictionary>
<local:FooToStyleConverter x:Key="FooToStyleConverter" />
....
Related
I am using converter with Label's IsVisible property.
<Label IsVisible="{Binding products, Converter={StaticResource EmptyCollectionToBoolConverter}}" Text="No data found">
If products is empty EmptyCollectionToBoolConverter returns true otherwise false. When screen is loading first time "No data found" message appears for fraction of seconds and then data is getting loaded.
I want to fix it, I need to show Label only if when products is empty. How can I do it?
You can overwrite IsVisible value in the code behind.
<Label x:Name="MyLabel" IsVisible="{Binding products, Converter={StaticResource EmptyCollectionToBoolConverter}}" Text="No data found">
Code behind
// probably ctor
MyLabel.IsVisible = false;
Second option can be to use a DataTrigger
<Label Text="No data found" IsVisible="false">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding products, Converter={StaticResource EmptyCollectionToBoolConverter}}" Value="True">
<Setter Property="IsVisible" Value="True" />
</DataTrigger>
</Label.Triggers>
</Label>
If you are using a CollectionView you can use the EmptyView , it will display whatever you put in that XAML when the collection is empty.
Or you can implement bindablelayout that also implements the emptyViewTemplate.
Or you will have to create another binding or another converter.
Something like public bool MyBinding{get=> myList!=null | myList.Count != | isLoadingFlag } . But you will have to call the propertychanged event if you modify your collection
I am not sure if this could be your problem, but i would check a couple of things:
You could set the binding not in XAML but in code behind, after data is loaded.
You might need to set the BindingContext to products, if not yet done so.
Also, maybe it is better if you set the binding path of the label to Count property of your collection.
Finally, set IsVisible to false (default) in XAML. This hard code will be overriden by the binding that is set when the data is loaded.
Anyway it is, i worked out a minimal complete working sample that does the job.
It works bassically as follows: Application starts and displays nothing... then goes on to load the data. When data is loaded label appears showing the number of items in collection. Two buttons are also available in Toolbar: Add Item and Remove Item. If you remove all items, No data found label appears.
See comments in code
Page1.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace scrollviewPrompt
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page1 : ContentPage
{
// products is an observable collection so that
// it notifies when it changes.
public ObservableCollection<string> products { get; set; }
public Page1()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
// Initialize your products collection
products = new ObservableCollection<string>();
// Set binding context of the whole Page1
// to your products collection
BindingContext = products;
// Load data asynchronously.
// At this point the data is already bound to
// our collection, so when data is loaded
// "No data found" label will dissapear.
await LoadDataAsync();
base.OnAppearing();
}
private async Task LoadDataAsync()
{
await Task.Delay(4000);
products.Add("Toks");
noDataLabel.SetBinding(Label.IsVisibleProperty, new Binding()
{
Path="Count",
Converter = new EmptyCollectionToBoolConverter()
});
}
// Add items to collection.
private void AddClicked(object sender, EventArgs e)
{
products.Add("locs");
}
// If collection not empty, remove first item.
private void RemoveClicked(object sender, EventArgs e)
{
if (products.Count>0)
products.RemoveAt(0);
}
}
public class EmptyCollectionToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var count = (int)value;
return count==0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Not_EmptyCollectionToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var count = (int)value;
return count > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Page1.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:scrollviewPrompt"
x:Class="scrollviewPrompt.Page1">
<ContentPage.Resources>
<ResourceDictionary>
<local:EmptyCollectionToBoolConverter x:Key="EmptyCollectionToBoolConverter"/>
<local:Not_EmptyCollectionToBoolConverter x:Key="Not_EmptyCollectionToBoolConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add Item"
Clicked="AddClicked"/>
<ToolbarItem Text="Remove Item"
Clicked="RemoveClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<!--No data found is bound to Count property of BindingContext (products).
when products change, Count changes and IsVisible is updated.-->
<Label x:Name="noDataLabel"
Text="No data found"
IsVisible="false"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Label Text="{Binding Path=Count, StringFormat='{0} items'}"
IsVisible="{Binding Path=Count, Converter={StaticResource Not_EmptyCollectionToBoolConverter}}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Consider using binding fallbacks, which allow you to specify "default" values when either the binding context is not set, or the binding target is null.
<Label IsVisible="{Binding products, Converter={StaticResource EmptyCollectionToBoolConverter}, FallbackValue='False', TargetNullValue='False'}" Text="No data found">
You may not need both, but it will ensure that the Label is always hidden until both the binding context (ViewModel) can be resolved and the binding target (products) is not null so the converter can properly evaluate whether to show the Label.
I am trying to access to method class from XAML file.
My class is on folder: project.Utils.
Adding on xaml Content Page:
xmlns:local="project.Utils"
I try to use myConverterMethod class inside Utils folder and use it as:
Converter={StaticResource myConverterMethod}
but error Type myConverterMethod not found in xmlns project.Utils.
Where is my fault?
You can use
xmlns:local="clr-namespace:project.Utils;assembly=project"
It is not possible to refer to a Method within a specific class but to a IValueConverter.
In order to achieve what you want, you need to define a class that implements IValueConverter:
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
Define the created converter in accessible scope: Page/View or Application. By scope I mean resources:
<ContentPage.Resources>
<ResourceDictionary>
<local:IntToBoolConverter x:Key="intToBool" />
</ResourceDictionary>
</ContentPage.Resources>
and finally consume the converter in the next way:
<Button Text="Search"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
Xamarin has a very nice documentation that will answer all your questions and it usually has a good code samples.
would it be possible to databind a static resource in Xamarin Forms?
Something like
Style="{StaticResource {Binding foo, StringFormat='SomeStyle{0}'}}"
Thanks
What you probably want is a Value Converter that handles locating the StaticResource for you. Full Microsoft Documentation here.
On your XAML element, you'd do something like this:
<Entry Style="{Binding foo, Converter={StaticResource FooToStyleConverter}}"/>
Your converter would work something like this:
public class FooToStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someValue = (string)value; // Convert 'object' to whatever type you are expecting
// evaluate the converted value
if (someValue != null && someValue == "bar")
return (Style)App.Current.Resources["StyleOne"]; // return the desired style
return (Style)App.Current.Resources["StyleTwo"];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Usually unused, but inverse the above logic if needed
throw new NotImplementedException();
}
}
Lastly, set up your converter as a Static Resource in App.xaml (or as a local resource on the page) so your page can properly reference it
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<ResourceDictionary>
<local:FooToStyleConverter x:Key="FooToStyleConverter" />
....
I know that we can directly bind properties in Xaml.
But My requirement is bit different. I want full control on binding data.
So I am looking for adapter type approach. I want to display some elements based on number of lines in textblock of that item. Here i cant use value converter because at that time my UI won't be ready and I cant find number of lines of each textblocks.
I will explain with example, let's suppose I want to change time format from hh:mm:ss to hh:mm during binding.
First, I will create public class that implements IValueConverter.
for example :-
public class RemoveSecondsInTime : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string date = value as string;
date = date.Substring(0, date.Length - 3);
return date;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Second, To use this converter class, we need to add this converter class in page resources.
for example :-
<Page.Resources>
<local:RemoveSecondsInTime x:Key="ChangeTimeFormat" />
</Page.Resources>
Third, We will create create our ListView as following :-
<ListView>
<ListView.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding TimeWithSeconds, Converter {StaticResource ChangeTimeFormat}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So, TimeWithSeconds will pass as "value" parameter in Convert function, Convert function will return formatted string to be displayed in textBox.
References :-
1) https://channel9.msdn.com/Series/Windows-Phone-8-1-Development-for-Absolute-Beginners/Part-25-Advanced-Binding-with-Value-Converters
2) https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.data.ivalueconverter
I'm trying to bind the PlayerFramework.MediaPlayer.CanPause Property to a Button in my windows 10 universal app. This works using the default font, but when I switch to Segoe MDL2 to get those fancy icons the button shows garbage.
<mmppf:MediaPlayer x:Name="mediaElement">
...
<Button Name="btnPlay"
Style="{StaticResource transportStyle}" Content="{Binding CanPause, ElementName=mediaElement, Converter={StaticResource CanPauseToPlayPauseConverter}}"/>
This is from the converter:
public object Convert(object value, Type targetType, object parameter, string language)
{
bool canPause = (bool)value;
if (canPause)
return #"";
// "play"
return "";
}
...and this from the button style:
<Style x:Name="transportStyle" TargetType="Button">
<!-- <Setter Property="FontFamily" Value="Segoe MDL2 Assets" />-->
</Style>
After disabling the Setter property the button shows the expected value
which, directly set as the button content, shows the play symbol.
Any ideas why this doesn't work?
edit: Copying the character from the character table and returning it does work.
is a unicode character escape sequence in XML (and hence also in XAML). In C# it is written as \uE102.
So the converter should return strings (or characters) with proper C# unicode character escape sequences:
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool)value ? "\uE769" : "\uE102";
}
You have to return a char not a string:
public object Convert(object value, Type targetType, object parameter, string language)
{
bool canPause = (bool)value;
if (canPause)
return '\xE769';
// "play"
return '\xE102';
}