Image source based on properties value in Xamarin.Forms - properties

I've got a custom MyCachedImage that inherits from FFImageLoading.Forms.CachedImage, which is used in a ListView to display images.
The source of this image is composed by 2 properties: a custom object as entity and an integer as size.
Let's say if entity is a "city" object and size is 10 then the image source will be "http://..../city/10/image.png"
Image source must be setted only when both properties are valorized.
So, my answer is, how and when create the source url?
MyCachedImage.vb
public class MyCachedImage : CachedImage
{
public static readonly BindableProperty EntityProperty =
BindableProperty.Create(nameof(Entity), typeof(MyObject), typeof(MyCachedImage));
public MyObject Entity
{
get { return (MyObject)GetValue(EntityProperty); }
set { SetValue(EntityProperty, value); }
}
public static readonly BindableProperty SizeProperty =
BindableProperty.Create(nameof(Size), typeof(int), typeof(MyCachedImage), defaultValue: 0);
public int Size
{
get { return (int)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public MyCachedImage()
{
??? set source here?
}
protected override void OnBindingContextChanged()
{
??? set source here?
}
}
MyPage.xaml
<ListView ....>
....
<control:MyCachedImage Size="10"
Entity="{Binding MyObject}"
WidthRequest="40"
HeightRequest="40" />
....
</ListView>

I was wondering on when create that string and I found the right solution.
The OnBindingContextChanged is called when all properties are setted, so:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_source == string.Empty)
{
Source = Helpers.ImageHelper.UriFromEntity(Entity, ImageSize);
}
}

Related

Access a custom controls bindable properties from its xaml

I want to declare a bindable property in my custom view and link it to the corresponding viewmodel.
I use the MVVM pattern and want to separate ui logic and data logic from eachother. So I keep my status and other data in the viewmodel and update my view according to viewmodel data changes.
This of course should be done by data binding.
Lets say I got the following xaml ...
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:MyApp.Views.Controls"
x:Class="MyApp.Views.Controls.MyView"
x:DataType="controls:MyViewVm">
<!--TODO: Content-->
</ContentView>
... with this code behind ...
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyView : ContentView
{
public static readonly BindableProperty StatusProperty = BindableProperty.Create(nameof(Status), typeof(MyStatus), typeof(MyView));
public MyStatus Status
{
get => (MyStatus)GetValue(StatusProperty);
set => SetValue(StatusProperty, value);
}
public MyView()
{
InitializeComponent();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(Status):
// TODO: Do styling ...
break;
}
}
}
}
... and this viewmodel and status enum:
namespace AbrechnungsApp.Views.Controls
{
public class MyViewVm : ViewModelBase
{
public MyStatus Status { get; set; }
}
public enum MyStatus
{
Enabled,
Disabled,
Readonly
}
}
Now the question is:
How do I link my viewmodels Status property to my views Status bindable property?
I typically create a helper property to cast BindingContext to the appropriate VM class:
private MyViewVm VM => (MyViewVm)BindingContext;
Then get/set VM properties in the bindable property:
public static readonly BindableProperty StatusProperty =
BindableProperty.Create(
nameof(Status), typeof(MyStatus), typeof(MyView),
propertyChanged: (binding, old, new) =>
{
// Needed if your XAML uses two-way binding.
Status = new;
});
public MyStatus Status
{
get => VM.Status;
set => VM.Status = value;
}

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}}" />

Image doesn't show in Xamarin.forms (PCL)

I have an image withn name fb.png and it's in root project(prtable) and I add this image to Resource>drawble in Droid project.
Thees is my MainPage.xaml code:
<Image x:Name="img1"></Image>
And Thees is my MainPage.xaml.cs code:
public MainPage()
{
InitializeComponent();
ImageSource img = ImageSource.FromResource("App2.fb.png");
img1.Source = img;
img1.Aspect = Aspect.AspectFit;
img1.BackgroundColor = Color.Navy;
}
What changed is need that image will be appeared?
If the file is saved in the Resources/Drawable directory, then you use FromFile, not FromResource. FromResource is used for images packaged as embedded resources in your built library.
You also need to specify the exact name of the file as it appears in Resources/Drawable, so this should do it:
public MainPage()
{
InitializeComponent();
ImageSource img = ImageSource.FromFile("fb.png");
img1.Source = img;
img1.Aspect = Aspect.AspectFit;
img1.BackgroundColor = Color.Navy;
}
Here is a full implementation of MVVM bound image resource to Image control. You need to set your viewmodel as the context of your page where the XAML is. Also accessing as "App2.fb.png" seems odd, it should just be fb.png. That might be a simpler fix.. just rename the image source to the exact name of the image as listed in Droid > resources
XAML
<Image
Aspect="AspectFit"
Source="{Binding PropertyImageStatusSource}">
Base ViewModel
Have your viewmodels inherit from a viewmodel base class so INotifyPropertyChanged is implemented on your accessors universally.
public class _ViewModel_Base : INotifyPropertyChanged
{
//figure out what is getting set
public virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
//attach handler with property name args to changing property, overridable in inheriting classes if something else needs to happen on property changed
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ViewModel
Public MyViewModel : _ViewModel_Base
{
private string ImageStatusSource = "fb.png";
public string PropertyImageStatusSource
{
set { SetProperty(ref ImageStatusSource, value); }
get { return ImageStatusSource; }
}
}

XamlParseException Failed to assign to property. Binding not working with attached property

I want to create custom text box with attached property for Windows Store app. I am following this solution. Now it uses hard coded value as property value but I want to set value using binding, but it's not working. I tried to search a lot but didn't helped me any solution.
The exception details is like this
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException'
occurred in CustomTextBox.exe but was not handled in user code
WinRT information: Failed to assign to property
'CustomTextBox.Input.Type'.
MainPage.xaml
<!-- local:Input.Type="Email" works -->
<!-- local:Input.Type="{Binding SelectedTextboxInputType}" not working -->
<TextBox x:Name="txt" local:Input.Type="{Binding SelectedTextboxInputType}" Height="30" Width="1000" />
<ComboBox x:Name="cmb" ItemsSource="{Binding TextboxInputTypeList}" SelectedItem="{Binding SelectedTextboxInputType}" Height="30" Width="200"
Margin="451,211,715,527" />
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel();
}
}
Input.cs
//InputType is enum
public static InputType GetType(DependencyObject obj)
{
return (InputType)obj.GetValue(TypeProperty);
}
public static void SetType(DependencyObject obj, InputType value)
{
obj.SetValue(TypeProperty, value);
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is InputType)
{
var textBox = (TextBox)d;
var Type = (InputType)e.NewValue;
if (Type == InputType.Email || Type == InputType.URL)
{
textBox.LostFocus += OnLostFocus;
}
else
{
textBox.TextChanged += OnTextChanged;
}
}
}
ViewModel.cs
public class ViewModel : BindableBase
{
public ViewModel()
{
TextboxInputTypeList = Enum.GetValues(typeof(InputType)).Cast<InputType>();
}
private InputType _SelectedTextboxInputType = InputType.Currency;
public InputType SelectedTextboxInputType
{
get { return _SelectedTextboxInputType; }
set { this.SetProperty(ref this._SelectedTextboxInputType, value); }
}
private IEnumerable<InputType> _TextboxInputTypeList;
public IEnumerable<InputType> TextboxInputTypeList
{
get { return _TextboxInputTypeList; }
set { this.SetProperty(ref this._TextboxInputTypeList, value); }
}
}
This is a pretty common mistake. The problem is, binding targets cannot be CLR properties in XAML. It's just the rules. A binding source can be a CLR property, just fine. The targets simply must be dependency properties.
We all get the error! :)
I describe the whole thing here: http://blogs.msdn.com/b/jerrynixon/archive/2013/07/02/walkthrough-two-way-binding-inside-a-xaml-user-control.aspx
Best of luck.
Incorrect
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
Correct
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(Input), new PropertyMetadata(default(InputType), OnTypeChanged));

Custom Entity object property doesn't update in Silverlight DataGrid

I have a Silverlight Application with Domain Service.
Entity Object (Part Of) :
[EdmEntityTypeAttribute(NamespaceName="MiaoulisModel", Name="AbroadTravel")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class AbroadTravel : EntityObject
{
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
[DataMemberAttribute()]
public global::System.String Description
{
get
{
return _Description;
}
set
{
OnDescriptionChanging(value);
ReportPropertyChanging("Description");
_Description = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("Description");
OnDescriptionChanged();
}
}
private global::System.String _Description;
partial void OnDescriptionChanging(global::System.String value);
partial void OnDescriptionChanged();
Here is my Partial Classe with Custom Property :
public partial class AbroadTravel : INotifyPropertyChanged
{
[DataMember]
public String ShortDescription
{
get
{
if (this.Description == null)
{
return this.Description;
}
if (this.Description.Contains("\n"))
{
var index = this.Description.IndexOf("\n");
if (index < 50)
{
return this.Description.Substring(0, index) + " [...]";
}
}
if (this.Description.Length >= 50)
{
return this.Description.Substring(0, 50) + " [...]";
}
return this.Description;
}
}
}
In my DataGrid, I have :
<c1:Column x:Name="dgcDescription" Binding="{Binding Path=ShortDescription}" Width="4*" />
And a RichTextBox with :
<c1:C1RichTextBox Text="{Binding Path=Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
When I update the RichTextBox which the Description value, the DataGrid with the ShortDescription does not update.
Any Idea ? (I do not use MVVM, I use the Code Behind)
You need to tell the UI that the property ShortDescription (an autocalculated property) has changed, when you change the property Description.
In order to do that, you need to raise the PropertyChanged-Event for the property ShortDescription when the property Description changed. Otherwise has the UI now chance to know that the property ShortDescription has changed and that it should update the binding.
In CodeBehind (in Silverlight-Client-Project) you can do it like so:
public partial class AbroadTravel
// omitted your code
partial void OnDescriptionChanged(){
RaisePropertyChanged("ShortDescription");
}
}