Binding Indexer - xaml

I have a collection of items in my app, and I want to set the Content of a ContentPresenter to one of these items. The item will be randomly defined by an int index. I can bind an item like this:
<ContentPresenter Content={Binding Items[0]}/>
but not like this:
<ContentPresenter Content={Binding Items[{Binding Index}]}/>
I've seen a number of answers suggesting using MultiBinding in WPF, but this isn't available in UWP. Is there an alternative?

You could create a view model property, returning Items[Index]:
public string RandomItem => Items[Index];
For the PropertyChanged notifications to work, you will need to raise the event whenever Index or Items changes, e.g.:
public int Index
{
get { return _index; }
set
{
_index = value;
RaisePropertyChanged();
RaisePropertyChanged(() => RandomItem);
}
}
If you prefer to have the logic in the view and go the multi-binding way, you can use the Cimbalino toolkit. For that to work, first add 2 NuGet packages:
Cimbalino.Toolkit
Microsoft.Xaml.Behaviors.Uwp.Managed
Now you can create a converter:
public class CollectionIndexConverter : MultiValueConverterBase
{
public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var collection = (IList) values[0];
var index = (int?) values[1];
return index.HasValue ? collection[index.Value] : null;
}
public override object[] ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
And use it from XAML:
<ContentPresenter>
<interactivity:Interaction.Behaviors>
<behaviors:MultiBindingBehavior PropertyName="Content" Converter="{StaticResource CollectionIndexConverter}">
<behaviors:MultiBindingItem Value="{Binding Items}" />
<behaviors:MultiBindingItem Value="{Binding Index}" />
</behaviors:MultiBindingBehavior>
</interactivity:Interaction.Behaviors>
</ContentPresenter>

Related

Why does Xamarin.Forms databinding work for one control but not another?

I am developing a Xamarin app which I am testing on an Android device. I have a XAML view and I am binding an enum property in the viewmodel to multiple controls - one for text value, and the other to background color with an IValueConverter. Relevant XAML code:
<ContentView
BackgroundColor="{Binding MyField, Converter={StaticResource MyFieldEnumValueToColorConverter}}"
>
<ContentView.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding MyFieldClickCommand}" />
</ContentView.GestureRecognizers>
<Label
Text="{Binding MyField}"
/>
</ContentView>
IValueConverter implementation:
public class MyFieldEnumValueToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is MyFieldEnum)
{
switch ((MyFieldEnum)value)
{
case MyFieldEnum.Value1:
return Color.Orange;
case MyFieldEnum.Value2:
return Color.Green;
case MyFieldEnum.Value3:
return Color.Red;
}
}
return Color.White;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Property in the viewmodel (yes, it does implement INotifyPropertyChanged):
public event PropertyChangedEventHandler PropertyChanged;
private MyFieldEnum _myField;
public MyFieldEnum MyField
{
get => _myField;
set
{
_myField= value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyField)));
}
}
When I load this page, the controls load properly for every distinct enum value: the text and the background color both reflect the actual value.
In a click handler, I simply set the property value like this:
MyField = MyFieldEnum.Value2;
The text changes, but the background color does not. Why?
I also tried introducing a new field (of type Color), implement the IValueConverter logic directly in the getter, and bind that to the BackgroundColor attribute of the ContentView. Same issue. The page has many other data-bound items and all work properly except this one.
UPDATE: it appears that the problem is with the ContentView. I put the exact same BackgroundColor binding to the Label itself and there it works as expected, but for the ContentView it does not.

Xamarin forms URL binding on Image

I have a Image in my xaml in which the source is a URL that Iam binding.The URL from json will be like this : "/images/Uploads/e0111.png". I have the URL in my Common values stored class as CommonValues.URL. How can I add this "CommonValues.URL" before the json at the time of binding? So that the source for Image will be http://example.com//images/Uploads/e0111.png.?
If you need Uri
var myUrl= new Uri(CommonValues.URL + "images/Uploads/e0111.png");
If string than
var myUrl=CommonValues.URL + "images/Uploads/e0111.png";
Or you can do it like this in your ViewModel or Page
public string Url => string.Format("{0}{1}", CommonValues.URL,"/images/Uploads/e0111.png");
Then in XAML:
<Button Text="{Binding Url}"/>
You can use a converter, which will allow you to reuse in all your views/application
public class UrlConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var test = value as string;
if (!string.IsNullOrEmpty(test))
{
return CommonValues.URL + test;
}
return false;
}
public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException ();
}
#endregion
}
Then, in your page:
<ContentPage.Resources>
<ResourceDictionary>
<converter:UrlConverter x:Key="UrlConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Image Source="{Binding YourProperty, Converter={StaticResource UrlConverter}}"/>

Set IsVisible property by a string value of model

I am using XAML to define a ListView, with multiple buttons for each cell.
I want to trigger visibility depending on whether a string value is empty or not.
My button inside the ListView is:
<Button Text="{Binding Phone}"
Clicked="OnPhoneClicked"
CommandParameter="{Binding Telefono}"
x:Name="btnPhone" />
Binding Phone is read from my model. It is correctly shown.
How can set a IsVisible property button if Phone's value is an empty string?
Try this code
<Button Text="{Binding Phone}"
Clicked="OnPhoneClicked"
CommandParameter="{Binding Telefono}"
x:Name="btnPhone"
IsVisible="{Binding Phone,Converter={StaticResource StringNullOrEmptyBoolConverter"} />
StringNullOrEmptyBoolConverter.cs file
public class StringNullOrEmptyBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var s = value as string;
return !string.IsNullOrWhiteSpace(s);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Finally add this in App.xaml file
<Application.Resources>
<ResourceDictionary>
<Converter:StringNullOrEmptyBoolConverter x:Key="StringNullOrEmptyBoolConverter" />
</ResourceDictionary>
</Application.Resources>

Xamarin Forms show/hide option for entry

Currently I am working on Xamarin.Forms and wondering about any possibility to add show/hide option to an entry field?
I have solved a similar issue by using an expand/collapse icon above a number of entry fields.
The show/hide element in XAML
Add a clickable image with fixed size(20x20) referring to embedded resources in the PCL:
<Image Source="{Binding ShowHideIcon, Converter={StaticResource StringToResImageSourceConverter}}" WidthRequest="20" HeightRequest="20"">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowHideCommand}" />
</Image.GestureRecognizers>
</Image>
The ViewModel processes the command:
Switch the boolean every time the image is touched.
public bool EntryVisible { get; set; }
public Command ShowHideCommand{
get {
return new Command((object o) => {
EntryVisible = !EntryVisible;
if (EntryVisible) {
ShowHideIcon = "ic_collapse";
} else {
ShowHideIcon = "ic_expand";
}
}
}
}
The label and Entry in XAML
Bind the IsVisible attribute of the Label and Entry to the boolean in the ViewModel.
<Label Text="Quantity" IsVisible="{Binding EntryVisible}" />
<Entry Text="{Binding Quantity}" IsVisible="{Binding EntryVisible}" />
For completeness sake, I have used https://developer.xamarin.com/guides/xamarin-forms/working-with/images/#Embedded_Images to store images ic_expand.png and ic_collapse.png in the PCL Resources folder.
A Converter is required to turn a string e.g. "ic_expand" into an image reference that XAML can use.
public class StringToResImageSourceConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var resString = (string)value;
if (!string.IsNullOrEmpty(resString)) {
return ImageSource.FromResource("ProjectName.Resources." + resString + ".png");
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Entry entry = new Entry();
// Hide it
entry.IsVisible = false;

Use RelayCommand with not only buttons

I am using MVVM Light in my project and I am wondering if there is any way to use RelayCommand with all controls (ListView or Grid, for example).
Here is my current code:
private void Item_Tapped(object sender, TappedRoutedEventArgs e)
{
var currentItem = (TechItem)GridControl.SelectedItem;
if(currentItem != null)
Frame.Navigate(typeof(TechItem), currentItem);
}
I want to move this code to Model and use RelayCommand, but the ListView, Grid and other controls don't have Command and CommandParameter attributes.
What does MVVM Light offer to do in such cases?
Following on from the link har07 posted this might be of some use to you as I see you mention CommandParameter.
It is possible to send the "Tapped" item in the list to the relay command as a parameter using a custom converter.
<ListView
x:Name="MyListView"
ItemsSource="{Binding MyCollection}"
ItemTemplate="{StaticResource MyTemplate}"
IsItemClickEnabled="True">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemClick">
<core:InvokeCommandAction Command="{Binding ViewInMoreDetail}" InputConverter="{StaticResource TapConverter}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
Custom converter class
public class TapConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = value as ItemClickEventArgs;
if (args != null)
return args.ClickedItem;
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
In your view model you then have a relaycommand.
public RelayCommand<MyObject> MyRelayCommand
{
get;
private set;
}
In your constructor initialise the relay command and the method you want to fire when a tap happens.
MyRelayCommand = new RelayCommand<MyObject>(HandleTap);
This method receives the object that has been tapped in the listview as a parameter.
private void HandleTap(MyObject obj)
{
// obj is the object that was tapped in the listview.
}
Don't forget to add the TapConverter to your App.xaml
<MyConverters:TapConverter x:Key="TapConverter" />