Xamarin.Forms ImageCell binding to a local file - xamarin.forms

I'm using data binding in a ListView to bind a list of ImageCells. The image is a file stored locally on the device as app data.
On Windows, using an absolute or relative path to the file does not work, I have to convert it to a file:// URI. Unfortunately, on Android, the file:// URI doesn't work, and it needs to be a path.
I'm currently working around the issue by using a different value in the view model depending on the target platform. Is there a better solution than this:
if (Device.OS == TargetPlatform.Windows) {
result.uri = new Uri(uri).AbsoluteUri;
}
Xaml:
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding Uri}"
Text="{Binding Name}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
The type of Uri is string, do I need to use a UriImageSource instead?

I solved it by creating converter and dependency service.
Xaml
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" Padding="5,20,5,0" >
<ListView x:Name="list" ItemsSource="{Binding MyList}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Text="{Binding Name}" ImageSource="{Binding ImagePath, Converter={StaticResource AndroidImageInvert}}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Converter
public class ByteImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string fileName = value as string;
return ImageSource.FromStream(() => new MemoryStream(DependencyService.Get<IWRDependencyService>().GetImageBytes(fileName)));
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Dependency Service
public byte[] GetImageBytes(string fileName)
{
fileName = fileName.Replace(".jpg", "").Replace(".png", "");
var resId = Forms.Context.Resources.GetIdentifier(
fileName.ToLower(), "drawable", Forms.Context.PackageName);
var icon = BitmapFactory.DecodeResource(Forms.Context.Resources, resId);
var ms = new MemoryStream();
icon.Compress(Bitmap.CompressFormat.Png, 0, ms);
byte[] bitmapData = ms.ToArray();
return bitmapData;
}

Related

xamarin form does not knows controls (inside ListView.ItemTemplate's DataTemplate) by its x:Name

I have this in my xaml
<StackLayout VerticalOptions="CenterAndExpand">
<ListView x:Name="listViewItems" Margin="1,1" BackgroundColor="White" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="3" VerticalOptions="FillAndExpand" >
<!-- BorderColor="Gray" BackgroundColor="{StaticResource ElementBackgroundColor}"> -->
<Grid BackgroundColor="White" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label x:Name="labelCode" Grid.Column="0" Grid.RowSpan="2" Text="{Binding Code}" VerticalOptions="Center" HorizontalOptions="FillAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
<StackLayout Grid.Column="1" Spacing="0" Orientation="Vertical" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand">
<Label x:Name="labelEnglish" Text="{Binding NameEnglish}" VerticalOptions="Start" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
<Label x:Name="labelRussian" Text="{Binding NameRussian}" VerticalOptions="StartAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
</StackLayout>
</Grid>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
And I want to do this in code
labelCode.IsVisible = false;
But I get compile error
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'labelCode' does not exist in the current
context gttCompound C:\Development\Guido\Xamarin\gttCompound\gttCompound\Pages\PageViewOrSelectItem.xaml.cs 33 Active
I found a question/answer here with the same problem, but I checked and tried it all with no result
So I am at a loss here, how do I get my form to know these controls ?
EDIT
I also find more similar questions, but the always want to fill up an element in the listview in the code behind, that is not my problem.
I use binding for all the content.
My problem is that I want to hide one or two labels in the listview.
I have now 3 labels in the listview, but in some cases not all 3 will be filled, and when I just leave it like it is than the binding will show them empty, but it makes the gridrow to large in height, so I wanted to see if in these cases I can hide the empty labels to the gridrow will not be so high anymore.
I have now done this by building up the gridcontent in the code behind, which until now seems to work.
You get error because you cannot directly access objects by name in page code-behind (.cs file) within DataTemplate of ListView. If you really want to access this by name you then you should move <ViewCell> to the new .xaml file, then you could access objects by name in code-behind.
As #ewerspej mentioned "You cannot reference elements inside a DataTemplate by their x:Name. The reason being that DataTemplate are used to instantiate VisualElements dynamically at runtime and the names must be unique identifiers."
But the real solution is to use MVVM approach and DataBinding, so you wouldn't change ViewCells objects directly, but rather modify Model class of the given view cell. You would need to implement INotifyPropertyChanged interface into ViewModel, and then use binding for IsVisible property in your ViewCell
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/data-and-databinding.
Take a look at this implementation of very simple MVVM pattern:
this is a MainPage.xaml content
<?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:App1"
x:Class="App1.MainPage"
x:DataType="local:MyPageViewModel">
<StackLayout VerticalOptions="CenterAndExpand">
<ListView ItemsSource="{Binding ListViewItemsSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="cell" x:DataType="local:ListViewModel">
<Label Text="{Binding Title}"
IsVisible="{Binding IsVisible}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
we set here binding for ItemsSoruce for ListView and also we bind Title & IsVisible properties.
this is MainPage.xaml.cs
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MyPageViewModel();
}
}
}
we set there BindingContext for whole page.
and those are models:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace App1
{
public class ListViewModel : BaseModel
{
private string _title;
private bool _isVisible;
// properties that will fire PropertyChanged (auto genertated via JetBrains Rider)
public string Title
{
get => _title;
set => SetField(ref _title, value);
}
public bool IsVisible
{
get => _isVisible;
set => SetField(ref _isVisible, value);
}
}
public class MyPageViewModel : BaseModel
{
// Data source for your ListView
public ObservableCollection<ListViewModel> ListViewItemsSource { get; }
public MyPageViewModel()
{
// Init source for ListView
ListViewItemsSource = new ObservableCollection<ListViewModel>(new[]
{
new ListViewModel() { IsVisible = true, Title = "1" },
new ListViewModel() { IsVisible = true, Title = "2" },
new ListViewModel() { IsVisible = true, Title = "3" }
});
// Change second item to be invisible after 2 seconds
Task.Run(async () =>
{
await Task.Delay(2000);
ListViewItemsSource[1].IsVisible = false;
});
}
}
// base class that implements INotifyPropertyChanged (auto genertated via JetBrains Rider)
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
I've created Base Model, that can be used as base class for models for ViewCells and ViewModels for whole pages. In View Model im creating New list of items that are binded to View. In constructor I added delay to show that changing IsVisible property in Models changes also presentation of the View`.
In the ListViewModel i've created properties that can bind to View. Look how i call SetField in setters - this is the main idea - Model notifies View about change.
Most of the implementation code was autogenerated via JetBrains Rider IDE, but i think that Visual Sutido also can auto that basic code.
Based on refer to controls inside ListView / add new property to model,
you could bind IsVisible property of each label to a property in the model:
<Label x:Name="labelEnglish" IsVisible="{Binding HasEnglish}" .../>
Add to the model:
public bool HasEnglish => !string.IsNullOrEmpty(NameEnglish);

Xamarin form Command Parameter object is null passing from a template view

I am implementing list view with MVVM and have tap recogniser for the label. I have a custom template for displaying the cell. I am following tutorials on binding the command. I managed to bind the command but could not figure out how to bind Command Property. My command property is always null.
My list view is like this
***** For anyone looking for the solution. Before this line I had a grid view
like below
<Grid x:DataType="viewModels:CartViewModel">
************************************************
<ListView
ItemsSource="{Binding CartItem.Products}"
HasUnevenRows="True"
SeparatorVisibility="None"
VerticalOptions="FillAndExpand"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:CartItemTemplate
RemoveItemCommand="{Binding BindingContext.RemoveCartItemCommand, Source={x:Reference Cart}}"
UpdateCartCommandParameter="{Binding .}"
AddCommentCommand="{Binding BindingContext.AddCommentCommand, Source={x:Reference Cart}}"
UpdateCartCommand="{Binding BindingContext.UpdateCartCommand, Source={x:Reference Cart}}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my template is like this. Other codes are omitted.
<Label
Text="Update cart"
TextDecorations="Underline"
Margin="8, 0, 0, 0"
FontSize="12"
VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding UpdateCartCommand}"
CommandParameter="{Binding UpdateCartCommandParameter}"/>
</Label.GestureRecognizers>
</Label>
In the code behind of that template I have done like this
public static readonly BindableProperty UpdateCartCommandProperty =
BindableProperty.Create(nameof(UpdateCartCommand), typeof(ICommand), typeof(CartItemTemplate));
public ICommand UpdateCartCommand
{
get => (ICommand) GetValue(UpdateCartCommandProperty);
set => SetValue(UpdateCartCommandProperty, value);
}
public static BindableProperty UpdateCartCommandParameterProperty =
BindableProperty.Create(nameof(UpdateCartCommandParameter), typeof(Product), typeof(CartItemTemplate));
public Product UpdateCartCommandParameter
{
get => (Product) GetValue(UpdateCartCommandParameterProperty);
set => SetValue(UpdateCartCommandParameterProperty, value);
}
And finally in my MVVM code. I have implemented this.
public ICommand UpdateCartCommand { get; }
private readonly ICartService cartService;
private readonly INavigationService navigationService;
public CartPageViewModel(ICartService cartService, INavigationService navigationService)
{
this.cartService = cartService;
this.navigationService = navigationService;
UpdateCartCommand = new Command<object>(UpdateCartClicked);
}
private async void UpdateCartClicked(object cartItem)
{
await navigationService.ShowAlertAsync("Update Action", "Update cart quantity", "Ok");
}
The problem is object cartItem is always null. What am I doing wrong here? Any idea will be helpful. Thanks
You invoked the line like following which is illegal .
x:DataType="viewModels:CartPageViewModel"
Compiled bindings are currently disabled for any binding expressions that define the Source property. This is because the Source property is always set using the x:Reference markup extension, which can't be resolved at compile time.
If you want to set BindingContext in Xaml , use the following code
<ContentPage.BindingContext>
<viewModels:xxxViewModel/>
</ContentPage.BindingContext>

How pass the current data with converter?

How can i send the current element of my Actors list to my converter? and as well i need pass a parameter with ConverterParameter
<ListView x:Name="Actors" ItemsSource="{Binding Actors}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
Source="" IsVisible="{Binding ., Converter={StaticResource ListConverter}, ConverterParameter={Binding ActorCurrent}}"}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this Actor exist in my view movel, i need to pass as parameter
private Actor _actorCurrent;
public Actor ActorCurrent
{
get => _actorCurrent;
set
{
_actorCurrent = value;
RaisePropertyChanged(() => ActorCurrent);
}
}
i tried with dot, but dont work
IsVisible="{Binding . // in my Converter class i dont nothing
EDIT 1:
i have as well this
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// i want to value = will be {Binding .,
// and parameter = will be ActorCurrent
// then i want to compare value with parameter and return a bool
}
Or can i use triggers?
<DataTrigger TargetType="Image" Binding="{Binding Name}" Value="{Binding ActorCurrent.name}">
i want to value = will be {Binding .}
You can get the value in the Convert method, you have to know the type of your value first. You can add a breakpoint there to get the type of value:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//if the type of value is Actor:
Actor tempActor = value as Actor;
//if the type of value is string:
string tempStr = value as string
return "123";
}
and parameter = will be ActorCurrent
Actually, you can't set binding to ConverterParameter as ConverterParameter is not a BindableProperty.
There is a discussion about this problem in this thread and you may find some idea there.

Xamarin : Date in string

I am new with xamarin, I am facing an issue in my xamarin forms project.
I have a Label inside listview-viewcell, to show time in UI. The date is received as a number like 1510822596449(Java Timestamp). I want to show the date in strings like "n days ago".How can I achieve this?
<StackLayout>
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding createdTime}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Anybody please suggest a solution with working code.
Thanks in advance.
First, create a class DatetimeToStringConverter and add the following code:
public class DatetimeToStringConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
.AddMilliseconds((long)value) // put your value here
.ToLocalTime().ToString("g");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Then in xaml add the following code at the root of the page:
<ContentPage.Resources>
<ResourceDictionary>
<local:DatetimeToStringConverter x:Key="cnvDateTimeConverter"></local:DatetimeToStringConverter>
</ResourceDictionary>
</ContentPage.Resources>
Then add namespace:
xmlns:local="clr-namespace:Myapp;assembly=Myapp"
Then change the label text like this:
Text="{Binding createdTime, Converter={StaticResource cnvDateTimeConverter}}"
Keep the createTime type as long in model, otherwise you get invalid cast exception.
As suggested in the comments you could do this with a ValueConverter.
Write a converter similar to this, in your shared code.
public class TicksToDateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!Int64.TryParse(value, out long ticks))
return DatTime.Now;
// TODO you can do a ToString and format it you want here but also in XAML
return new DateTime(ticks);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Not needed
throw new NotImplementedException();
}
}
Now in your XAML, declare te value convter like this under the root of your page, I'm assuming it's a ContentPage.
<ContentPage.Resources>
<ResourceDictionary>
<local:TicksToDateTimeConverter x:Key="TicksConverter" />
</ResourceDictionary>
</ContentPage.Resources>
And don't forget to declare the local namespace in your page root like: xmlns:local="clr-namespace:YourApp.Namespace", which should be the full namespace, without the class name to your converter class.
To finally use the converter in your layout, do this:
<StackLayout>
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding createdTime, Converter={StaticResource TicksConverter}}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Depending on whether or not you return a string from the converter or a DateTime, in the latter case you can also format it here in XAML, like so:
<Label Text="{Binding createdTime, Converter={StaticResource TicksConverter}, StringFormat='{0:dd-MM-yyyy}'}"/>
Or you could choose to do it differently altogether and convert the value inside the model that you bind to the ViewCell.

Xamarin.Forms Listview

I'm making vocabulary app and trying to implement favorite word in my Xamarin.Forms Listview.
Please have a look below screenshots
For now i use hard code instead of MVVM
Here is my model
namespace Mamtil.Models
{
public class Word
{
public int Id { get; set; }
public string word { get; set; }
public string translation { get; set; }
public string audio { get; set; }
public bool favorite { get; set; }
public string groupBy => translation[0].ToString().ToUpper();
}
}
XAML
...
<ViewCell>
<StackLayout Orientation="Horizontal">
<Button x:Name="FavoriteButton" Image="gray_star.png" BackgroundColor="#F5F5F5" Clicked="Favorite" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding .}" />
<Switch IsToggled="{Binding favorite}" WidthRequest="50"/>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" Margin="5, 0, 0, 0">
<Label Text="{Binding translation}">
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double" iOS="11" Android="18" />
</Label.FontSize>
</Label>
<Label Text="{Binding word}" TextColor="#1565C0">
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double" iOS="11" Android="13" />
</Label.FontSize>
</Label>
</StackLayout>
<Button x:Name="SpeechButton" Image="ic_mic_black_24dp.png" BackgroundColor="#F5F5F5" Clicked="GoToSpeach" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding Id}" />
</StackLayout>
</ViewCell>
So the question is when i load data from database how do i initialize Image of FavoriteButton like
<!-- Here switch is toggled or not depending on Binding value. I want to do something like this but with image of Button -->
<Switch IsToggled="{Binding favorite}">
in code above it is hard coded but i want to change depending Word.favorite value. At run time i managed to change Image like this
async void Favorite(object sender, EventArgs e)
{
var b = (Button)sender;
Word t = (Word)b.CommandParameter;
//update in database
await App.MamtilRepo.UpdateFavoriteAsync(t.Id);
if (t.favorite)
b.Image = "gray_star.png";
else
b.Image = "yellow_star.png";
t.favorite = !t.favorite;
}
for sake of examle:
...
// Some thing like
if(Word.favorite)
Image = yellow_star.png;
else
Image = gray_star.png;
should i do it some where in code or in XAML
I hope could clearly describe my question. thanks in advance
You'd have to use the OnPropertyChanged() method so that it picks up the new value for the image Url, and you should also set up the binding context.
private bool _imageUrl;
public bool ImageUrl
{
get { return _imageUrl; }
set { _imageUrl = value; OnPropertyChanged(); }
}
And use BindingContext = this; in your constructor.
an old post, but here's another way to do this.
Classically one would use a converter for this.
So your button XAML would now look something like this:
<Button x:Name="FavoriteButton" Image="{Binding favorite, Converter={x:Static converter:FavoriteButton.Instance}}" BackgroundColor="#F5F5F5" Clicked="Favorite" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding .}" />
you'd need to add a reference for where your converter lives in the top of your XAML file, i.e.
xmlns:converter="clr-namespace:Mamtil.Converter;assembly=Interact.Client"
With your converter looking something like this:
namespace Mamtil.Converter {
public class FavoriteButton : IValueConverter {
// this Instance property just means your converter only needs to get created once, I saw it in a Xamarin sample.
public static FavoriteButton Instance = new FavoriteButton();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value == null) {
// only doing this because I've had encounters where an ImageSource has taken Exception at having null returned.
return ImageSource.FromFile("gray_star.png");
}
var fav = (bool)value;
if (fav == true) { // redundant ==, but it makes it quite explicit for any future reader glancing over your code what you intend here.
return ImageSource.FromFile("yellow_star.png");
}
return ImageSource.FromFile("gray_star.png");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
If you're using embedded resources rather than local files then refer to the link Rohit posted in his answer on Aug 24 '16 for how guidance on how to use those in your converter instead.
You can Bind your Image's Source to File, Uri or Resource, which sets the image to display.
FromFile - Requires a filename or filepath that can be resolved on each platform.
FromUri - Requires a Uri object, eg. new Uri("http://server.com/image.jpg") .
FromResource - Requires a resource identifier to an image file embedded in the application or PCL, with a Build Action:EmbeddedResource .
XAML :
<Image Source="{Binding MyImage}" />
ViewModel :
public ImageSource MyImage {get; set; }
You can read more about working with Images here.
DataTrigger or Converter are best solution for your problem.
<Button Image="gray_star.png">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding favorite}" Value="True">
<Setter Property="Image" Value="yellow_star.png" />
</DataTrigger>
</Button.Triggers>
</Button>

Resources