How to give certain colors to sliders in Xamarin with Binding - xamarin

I have created a slider to know where is exactly is the point between 1-100, and now I want to change the color depending on the range, 0-50 green with a text "OK", 50-90 yellow with text "Careful", and above 90 red with text "Danger". But I can not even make it work with red color above 90.
MainPageViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Input;
using Xamarin.Forms;
namespace App1
{
public class MainPageViewModel : INotifyPropertyChanged
{
private double slider;
public double Slider
{
get => slider;
set
{
slider = value;
OnPropertyChanged(nameof(Slider).ToString());
OnPropertyChanged(nameof(Color).ToString());
}
}
public string Color()
{
if (Slider > 90)
{
return "Red";
}
else return "Black";
}
public ICommand ResetCommand { get; set; }
public MainPageViewModel()
{
ResetCommand = new Command(Reset);
}
private void Reset()
{
Slider = double.MinValue;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
and this is my
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App1.MainPage">
<StackLayout>
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<Label HorizontalTextAlignment="Center"
Text="Title"
TextColor="White"
FontSize="36"/>
</Frame>
<Label HorizontalTextAlignment="Center"
Text="{Binding Slider}"
TextColor="{Binding Color}"
FontSize="36"/>
<Slider VerticalOptions="FillAndExpand"
Value="{Binding Slider}"
Maximum="100" />
<Button Text="Reset" Command="{Binding ResetCommand}"></Button>
</StackLayout>
</ContentPage>
Trying with Properties:
public Color mycolor;
public Color MyColor
{
get => mycolor;
set
{
mycolor = value;
{
if (Slider > 90)
{
OnColorChanged(Color.Red);
}
else OnColorChanged(Color.Yellow);
}
}
}
and
private void OnColorChanged(Color propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName.ToString()));
}

Color needs to be a public property that returns a Color, not a string.
private double slider;
public double Slider
{
get => slider;
set
{
slider = value;
OnPropertyChanged(nameof(Slider).ToString());
OnPropertyChanged(nameof(Color).ToString());
}
}
public Color Color
{
get {
if (Slider > 90)
{
return Color.Red;
}
else return Color.Black;
}
}

Always use Converter for such cases.
Step 1 - ViewModel :
private int slider = 10;
public int Slider { get => slider; set { slider = value; OnPropertyChanged("Slider"); } }
Step 2 - Converter :
public class SliderValueToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// int is if you bind via property
if (value is int sliderVal)
{
if (sliderVal < 50)
return Color.Green;
else if (sliderVal >= 50 && sliderVal < 90)
return Color.Yellow;
else
return Color.Red;
}
// double is you bind via x:reference
else if (value is double sliderVal2)
{
if (sliderVal2 < 50)
return Color.Green;
else if (sliderVal2 >= 50 && sliderVal2 < 90)
return Color.Yellow;
else
return Color.Red;
}
return Color.Black;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
Step 3 - View :
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DummyTestApp"
mc:Ignorable="d" x:Name="BottomTabGridPageXaml"
x:Class="DummyTestApp.BottomTabGridPage"
BindingContext="{x:Reference BottomTabGridPageXaml}">
<ContentPage.Resources>
<local:SliderValueToColorConverter x:Key="SliderValueToColorConverter"/>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Margin="5" Padding="5" Spacing="5" VerticalOptions="CenterAndExpand">
<Label HorizontalTextAlignment="Center" FontSize="36"
Text="{Binding Source={x:Reference mySlider},Path=Value}"
TextColor="{Binding Source={x:Reference mySlider},Path=Value,Converter={StaticResource SliderValueToColorConverter}}"/>
<Slider x:Name="mySlider" VerticalOptions="FillAndExpand" Value="{Binding Slider}" Maximum="100"
MinimumTrackColor="{Binding Slider,Converter={StaticResource SliderValueToColorConverter},Mode=TwoWay}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>

the expression TextColor="{Binding Color}" is like "i want to bind to a field called "Color". you need to specify an object or a class you bind to. this is the missing part of your code: a BindingContext. it is a pain to set it in xamarin. but if you do the true MVVM design as in example, it should work.

Related

.Net maui: How to reference a color in a binding?

So I have a label and I want to set the text colour from a mvvm variable.
VM
[ObservableProperty]
private string col = "White";
XAML
<Label Text="{Binding Name}"
FontSize="20"
TextColor="{Binding Col}">
So in general TextColor="White" works fine
I've tried using the Color object https://learn.microsoft.com/en-us/dotnet/maui/user-interface/graphics/colors
e.g.
[ObservableProperty]
private Color col = Colors.White;
but I can't get it to work.
I had hoped that a simple string would work...oh for my vain hopes...
Thanks, G.
Edit: I should add that my label is in a CollectionView?
BIG EDIT:
IT WORKS for a standalone label
i.e.
[ObservableProperty]
private Color col = Colors.White;
So the issue is if the label is in a CollectionView. I wonder why?
EDIT: Because the CollectionView is bound to the ItemsSource - doh what a dummy!
If you want to bind color(which type is string) to your view, you can use Binding value converters to achieve this.
I created a demo to achieve this , you can refer to the following code:
MyModel.cs
public class MyModel: INotifyPropertyChanged
{
string name;
public string Name
{
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
get
{
return name;
}
}
string _value;
public string Value
{
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged("Value");
}
}
get
{
return _value;
}
}
private string _textColor = "Green";
public string TextColor
{
get { return _textColor; }
set
{
_textColor = value;
OnPropertyChanged("TextColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyViewModel.cs
public class MyViewModel
{
public ObservableCollection<MyModel> dataList { get; set; }
public ICommand ColorChangeCommand { protected set; get; }
public MyViewModel()
{
dataList = new ObservableCollection<MyModel>();
dataList.Add(new MyModel() { Name = "test1", Value = "1" });
dataList.Add(new MyModel() { Name = "test2", Value = "2" });
dataList.Add(new MyModel() { Name = "test3", Value = "3" });
ColorChangeCommand = new Command<MyModel>(async (key) =>
{
key.TextColor = "Red";
});
}
}
StringToColorConverter.cs
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var color = value.ToString();
switch (color)
{
case "Green":
return Colors.Green;
case "Red":
return Colors.Red;
default:
return Colors.Yellow;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
A usage:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp0706.Tab1"
xmlns:local="clr-namespace:MauiApp0706"
Title="Tab1">
<ContentPage.Resources>
<ResourceDictionary>
<local:StringToColorConverter x:Key="ColorConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<VerticalStackLayout>
<CollectionView
ItemsSource="{Binding dataList}"
x:Name="mylistview"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Column="0">
<Label Text="{Binding Name}" TextColor="{Binding TextColor, Converter = {StaticResource ColorConverter}}"/>
</Grid>
<Grid Column="1">
<Label Text="{Binding Value}" TextColor="{Binding TextColor, Converter = {StaticResource ColorConverter}}"/>
</Grid>
<Grid Column="2">
<Button Text="change" Command="{Binding BindingContext.ColorChangeCommand, Source={x:Reference Name=mylistview} }" CommandParameter="{Binding .}"></Button>
</Grid>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
Not to detract from the answer already given, but a couple of points to note for people running into this...
"Color col = Colors.White" - Color and Colors aren't the same thing, and within "Color" there is System.Drawing.Color and Microsoft.Maui.Graphics.Color, so be careful you're not accidentally mixing types.
If you do your UI in C# rather than XAML, then you can just bind directly to a Color to begin with and get rid of all the string-converting.

Xamarin Forms Bindable Property with OneWay is not working

I want to bind a CustomLabel to a VM by creating a new bindable property.
In OneWay mode, the first VM data has properly changed the property of the CustomLabel. but It didn't work from second time.
Although The VM event has occur, the Bindable Property of CustomView has not fired its PropertyChanged event.
It works properly in TwoWay mode though.
I've been testing for two days and searching for the cause, but I coudn't find it well.
Anybody tell me how to do?
// HomeViewModel.cs
public class HomeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _customName = "-";
public string CustomName
{
get
{
Debug.WriteLine("Get_CustomName");
return _customName;
}
set
{
if (value != _customName)
{
Debug.WriteLine("Set_CustomName");
_customName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomName)));
}
}
}
}
// MainPage.cs
public partial class MainPage : ContentPage
{
HomeViewModel Vm = new HomeViewModel();
public MainPage()
{
InitializeComponent();
BindingContext = Vm;
}
void ButtonTrue_Clicked(object sender, EventArgs e)
{
Vm.CustomName = "True";
}
void ButtonFalse_Clicked(object sender, EventArgs e)
{
Vm.CustomName = "False";
}
}
<!-- MainPage.xaml -->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Ex_Binding"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Ex_Binding.MainPage">
<StackLayout Padding="50,0" VerticalOptions="Center">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="Custom Result : " />
<local:CustomLabel x:Name="lbCustom" MyText="{Binding CustomName}" HorizontalOptions="Center" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button Text="TRUE" BackgroundColor="LightBlue" HorizontalOptions="FillAndExpand" Clicked="ButtonTrue_Clicked" />
<Button Text="FALSE" BackgroundColor="LightPink" HorizontalOptions="FillAndExpand" Clicked="ButtonFalse_Clicked" />
</StackLayout>
</StackLayout>
</ContentPage>
// CustomLabel.cs
public class CustomLabel : Label
{
public static readonly BindableProperty MyTextProperty = BindableProperty.Create(nameof(MyText), typeof(string), typeof(CustomLabel), null, BindingMode.OneWay, propertyChanged: OnMyTextChanged);
private static void OnMyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var thisBindable = (CustomLabel)bindable;
if (thisBindable != null)
{
thisBindable.MyText = (string)newValue;
}
}
public string MyText
{
get => (string)GetValue(MyTextProperty);
set
{
SetValue(MyTextProperty, value);
Text = value;
}
}
}
Cause :
thisBindable.MyText = (string)newValue;
Because you set the value of MyText when its value changed . So it will never been invoked next time (in TwoWay the method will been invoked multi times).
Solution:
You should set the Text in OnMyTextChanged directly .
private static void OnMyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var thisBindable = (CustomLabel)bindable;
if (thisBindable != null)
{
thisBindable.Text = (string)newValue;
}
}
public string MyText
{
get => (string)GetValue(MyTextProperty);
set
{
SetValue(MyTextProperty, value);
//Text = value;
}
}

Xamarin Forms Frame not expanding with content

I'm new to Xamarin Forms, and I've met my first challenge. I want a Frame around my Stacklayout within a Listview. When the user selects an item in the Listview I want some controls to appear. This works fine without the Frame, but the Frame does not expand when the controls appear. How can I change or get around this behavior?
Code below.
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVMTest"
x:Class="MVVMTest.MainPage">
<StackLayout>
<ListView HasUnevenRows="True" SelectedItem="{Binding SelectedViewItem, Mode=TwoWay}" ItemsSource="{Binding Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="White" BorderColor="Black">
<StackLayout>
<Label Text="{Binding Name}"></Label>
<Entry Text="{Binding Details}" IsVisible="{Binding ShowDetails}"></Entry>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
XAML.cs
namespace MVVMTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new ViewModel()
{
Items = new List<ViewModelItem>()
{
new ViewModelItem()
{
Name = "Test",
Details = "details"
},
new ViewModelItem()
{
Name = "Test2",
Details = "details2"
}
}
};
InitializeComponent();
}
}
}
Model:
namespace MVVMTest
{
public class ViewModel : INotifyPropertyChanged
{
private ViewModelItem _selectedViewItem;
private List<ViewModelItem> _items;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModelItem SelectedViewItem
{
get
{
return _selectedViewItem;
}
set
{
_selectedViewItem = value;
OnPropertyChanged();
if (value != null)
{
value.ShowDetails = !value.ShowDetails;
SelectedViewItem = null;
}
}
}
public List<ViewModelItem> Items
{
get
{
return _items;
}
set
{
_items = value;
OnPropertyChanged();
}
}
public ViewModel()
{
}
}
public class ViewModelItem : INotifyPropertyChanged
{
private bool _showDetails;
private string _details;
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
public bool ShowDetails
{
get
{
return _showDetails;
}
set
{
_showDetails = value;
OnPropertyChanged();
}
}
public string Details
{
get
{
return _details;
}
set
{
_details = value;
OnPropertyChanged();
}
}
}
}
I ended up using the PropertyChanged event to react to when the ListView was displayed or hidden. In the eventhandler, I set the HeightRequest of the Frame and this forces it to resize itself.
Alternative solution/help can be found here:
https://forums.xamarin.com/discussion/comment/366577
XAML:
<StackLayout>
<ListView HasUnevenRows="True" SelectedItem="{Binding SelectedViewItem, Mode=TwoWay}" ItemsSource="{Binding Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="White" BorderColor="Black" Margin="2" Padding="2" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<StackLayout>
<Label Text="{Binding Name}"></Label>
<ListView HasUnevenRows="True" Margin="2" ItemsSource="{Binding DetailObjects}" IsVisible="{Binding ShowDetails}" PropertyChanged="ListView_PropertyChanged">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Entry Text="{Binding Details}"></Entry>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new ViewModel()
{
Items = new List<ViewModelItem>()
{
new ViewModelItem()
{
Name = "Test",
DetailObjects = new List<ViewModelItemDetails>
{
new ViewModelItemDetails
{
Details = "details1"
},
new ViewModelItemDetails
{
Details = "details2"
}
}
},
new ViewModelItem()
{
Name = "Test2",
DetailObjects = new List<ViewModelItemDetails>
{
new ViewModelItemDetails
{
Details = "details1"
},
new ViewModelItemDetails
{
Details = "details2"
}
}
}
}
};
InitializeComponent();
}
private void ListView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!(sender is ListView list)) return;
if (e.PropertyName == ListView.IsVisibleProperty.PropertyName)
{
Element parent = list;
Frame frame = null;
while (frame == null && parent != null)
{
if (parent is Frame) frame = parent as Frame;
parent = parent.Parent;
}
if (list.IsVisible)
{
list.HeightRequest = list.ItemsSource.Cast<ViewModelItemDetails>().Count() * 50;
if (frame != null) frame.HeightRequest = list.HeightRequest + 50;
}
else
{
if (frame != null) frame.HeightRequest = 50;
}
}
}
}

How can I add a disclosure indicator / checkmark to my view cell in Xamarin.Forms Android?

Here's what I have implemented so far for iOS:
using System;
using Xamarin.Forms;
namespace Japanese
{
public class ExtCheckedTextCell: TextCell
{
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
"IsChecked", typeof(bool), typeof(ExtCheckedTextCell),
defaultValue: false);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
}
and my renderer looks like this:
using System;
using System.ComponentModel;
using System.Diagnostics;
using Japanese;
using Japanese.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExtCheckedTextCell), typeof(ExtCheckedTextCellRenderer))]
namespace Japanese.iOS
{
public class ExtCheckedTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var nativeCell = base.GetCell(item, reusableCell, tv);
if (item is ExtCheckedTextCell formsCell)
{
SetCheckmark(nativeCell, formsCell);
SetTap(nativeCell, formsCell);
}
return nativeCell;
}
protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.HandlePropertyChanged(sender, args);
System.Diagnostics.Debug.WriteLine($"HandlePropertyChanged {args.PropertyName}");
var nativeCell = sender as CellTableViewCell;
if (nativeCell?.Element is ExtCheckedTextCell formsCell)
{
if (args.PropertyName == ExtCheckedTextCell.IsCheckedProperty.PropertyName)
SetCheckmark(nativeCell, formsCell);
}
}
void SetCheckmark(UITableViewCell nativeCell, ExtCheckedTextCell formsCell)
{
if (formsCell.IsChecked)
nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
else
nativeCell.Accessory = UITableViewCellAccessory.None;
}
}
For reference here's the XAML where it is used:
<TableSection>
<local:CheckedTextCell Text="{Binding [6].Name}" IsChecked="{Binding [6].IsSelected}" Tapped="atiSelectValue" />
<local:CheckedTextCell Text="{Binding [7].Name}" IsChecked="{Binding [7].IsSelected}" Tapped="atiSelectValue" />
<local:CheckedTextCell Text="{Binding [8].Name}" IsChecked="{Binding [8].IsSelected}" Tapped="atiSelectValue" />
</TableSection>
Does anyone have any ideas how can I implement this in Android using a custom renderer or if it is even possible to do it?
Here's an example (not mine) of what it looks like in iOS. What I am hoping for is the Android can show a similar tick mark on the right side.
Custom Renderer
You can build a custom renderer in Android (although, I think an easier approach is to create a custom ViewCell):
using System.ComponentModel;
using Android.Content;
using Android.Views;
using Android.Widget;
using Sof;
using Sof.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using AView = Android.Views.View;
[assembly: ExportRenderer(typeof(ExtCheckedTextCell), typeof(ExtCheckedTextCellRenderer))]
namespace Sof.Droid
{
public class ExtCheckedTextCellRenderer : TextCellRenderer
{
public const string CheckedText = "✓";
private TextView Check { get; set; }
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
{
var view = base.GetCellCore(item, convertView, parent, context) as BaseCellView;
if (this.Check == null)
{
this.Check = new TextView(context);
this.Check.Gravity = GravityFlags.Center;
using (var lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent))
{
view.AddView(this.Check, lp);
}
var paddingRight = context.Resources.GetDimension(Resource.Dimension.abc_list_item_padding_horizontal_material);
view.SetPadding(view.PaddingLeft, view.PaddingTop, (int)paddingRight, view.PaddingBottom);
}
return view;
}
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnCellPropertyChanged(sender, args);
if (args.PropertyName.Equals(ExtCheckedTextCell.IsCheckedProperty.PropertyName) &&
sender is ExtCheckedTextCell extCheckedTextCell && this.Check != null)
{
this.Check.Text = extCheckedTextCell.IsChecked ? CheckedText : string.Empty;
}
}
}
}
Custom Xamarin.Forms.ViewCell (no platform-specific code needed)
For a simple layout like you want (label and checkmark), a custom ViewCell seems more appropriate and allows direct control over the style.
ExtCheckedTextCell2.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Sof.ExtCheckedTextCell2"
x:Name="this">
<ViewCell.View>
<StackLayout Orientation="Horizontal"
Padding="12, 0">
<Label HorizontalOptions="FillAndExpand"
Text="{Binding Text, Source={x:Reference this}}"
VerticalTextAlignment="Center" />
<Label IsVisible="{Binding IsChecked, Source={x:Reference this}}"
HorizontalOptions="End"
Text="✓"
VerticalTextAlignment="Center"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
ExtCheckedTextCell2.xaml.cs
public partial class ExtCheckedTextCell2 : ViewCell
{
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
nameof(IsChecked),
typeof(bool),
typeof(ExtCheckedTextCell2),
default(bool));
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(ExtCheckedTextCell2),
default(string));
public ExtCheckedTextCell2()
{
InitializeComponent();
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
protected override void OnTapped()
{
base.OnTapped();
this.IsChecked = !this.IsChecked;
}
}
Result
<TableView>
<TableSection Title="Custom Renderer">
<local:ExtCheckedTextCell Text="Test1" Tapped="Handle_Tapped" />
<local:ExtCheckedTextCell Text="Test2" Tapped="Handle_Tapped" />
<local:ExtCheckedTextCell Text="Test3" Tapped="Handle_Tapped" />
</TableSection>
<TableSection Title="Custom Xamarin.Forms ViewCell">
<local:ExtCheckedTextCell2 Text="Test1" />
<local:ExtCheckedTextCell2 Text="Test2" />
<local:ExtCheckedTextCell2 Text="Test3" />
</TableSection>
</TableView>
But you can also do it in xaml ?
This is a xaml only solution :) should work for Android and Ios .
.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"
x:Class="StackoverflowQ.Views.MainPage"
Title="Driving & Navigation">
<ContentPage.Resources>
</ContentPage.Resources>
<ScrollView>
<StackLayout>
<StackLayout x:Name="Header" BackgroundColor="#efeff4" HorizontalOptions="FillAndExpand" HeightRequest="30" Padding="10">
<Label Text="NAVIGATION VOICE VOLUME" Margin="0, 0, 0, 5" VerticalOptions="EndAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal" Padding="10">
<Label Text="No Voice" TextColor="Black" />
<Image Source="checkboxchecker.png" IsVisible="{Binding IsCheckBoxVisible}" HorizontalOptions="EndAndExpand" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCheckBoxCommand}" NumberOfTapsRequired="1" />
</StackLayout.GestureRecognizers>
</StackLayout>
<BoxView HeightRequest="1" HorizontalOptions="FillAndExpand" BackgroundColor="#efeff4" />
</StackLayout>
</ScrollView>
</ContentPage>
ViewModel
namespace StackoverflowQ.ViewModels
{
public class MainPageViewModel : ViewModelBase
{
public DelegateCommand TapCheckBoxCommand { get; set; }
private bool _isCheckBoxVisible;
public bool IsCheckBoxVisible
{
get => _isCheckBoxVisible;
set => SetProperty(ref _isCheckBoxVisible, value);
}
public MainPageViewModel(INavigationService navigationService)
: base(navigationService)
{
Title = "Main Page";
TapCheckBoxCommand = new DelegateCommand(TapCheckBoxSelected);
}
public void TapCheckBoxSelected()
{
IsCheckBoxVisible = !IsCheckBoxVisible;
}
}
}

How can I pass a command to a template and have it execute in my back end code and pass the parameter?

I have this template:
<?xml version="1.0" encoding="utf-8"?>
<Grid Padding="20,0" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.Templates.DataGridTemplate"
x:Name="this" HeightRequest="49" Margin="0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand, Source={x:Reference this}}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="{Binding Test" />
</Grid>
Behind this I have:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
"Command",
typeof(ICommand),
typeof(DataGridTemplate),
null);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
}
and I am trying to call the template like this in file: Settings.xaml.cs
<template:DataGridTemplate TapCommand="openCFSPage" />
hoping that it will call my method here in file: Settings.cs
void openCFSPage(object sender, EventArgs e)
{
Navigation.PushAsync(new CFSPage());
}
The code compiles but when I click on the grid it doesn't call the openCFSPage method.
1) Does anyone have an idea what might be wrong?
2) Also is there a way that I can add a parameter to the template and then have that parameter passed to my method in the CS back end code?
Note that I would like to avoid adding a view model if possible. The application is small and I'd like to just have the code I need in the CS code of the page that calls the template.
Please note that the simplest way to implement this would be through MVVM (i.e. a view-model), but if you want to side-step this option (as you mentioned in the question) then you can use one of the following options
Option1 : Wrap delegate into command object
If you look at it from the perspective of a XAML parser, you are technically trying to assign a delegate to a property of type ICommand. One way to avoid the type mismatch would be to wrap the delegate inside a command-property in the page's code-behind.
Code-behind [Settings.xaml.cs]
ICommand _openCFSPageCmd;
public ICommand OpenCFSPageCommand {
get {
return _openCFSPageCmd ?? (_openCFSPageCmd = new Command(OpenCFSPage));
}
}
void OpenCFSPage(object param)
{
Console.WriteLine($"Control was tapped with parameter: {param}");
}
XAML [Settings.xaml]
<!-- assuming that you have added x:Name="_parent" in root tag -->
<local:DataGridView TapCommand="{Binding OpenCFSPageCommand, Source={x:Reference _parent}}" />
Option2 : Custom markup-extension
Another option (a bit less mainstream) is to create a markup-extension that wraps the delegate into a command object.
[ContentProperty("Handler")]
public class ToCommandExtension : IMarkupExtension
{
public string Handler { get; set; }
public object Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
var lineInfo = (serviceProvider?.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo ?? new XmlLineInfo();
object rootObj = Source;
if (rootObj == null)
{
var rootProvider = serviceProvider.GetService<IRootObjectProvider>();
if (rootProvider != null)
rootObj = rootProvider.RootObject;
}
if(rootObj == null)
{
var valueProvider = serviceProvider.GetService<IProvideValueTarget>();
if (valueProvider == null)
throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
//we assume valueProvider also implements IProvideParentValues
var propInfo = valueProvider.GetType()
.GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(propInfo == null)
throw new ArgumentException("valueProvider does not provide an ParentObjects");
var parentObjects = propInfo.GetValue(valueProvider) as IEnumerable<object>;
rootObj = parentObjects?.LastOrDefault();
}
if(rootObj != null)
{
var delegateInfo = rootObj.GetType().GetMethod(Handler,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(delegateInfo != null)
{
var handler = Delegate.CreateDelegate(typeof(Action<object>), rootObj, delegateInfo) as Action<object>;
return new Command((param) => handler(param));
}
}
throw new XamlParseException($"Can not find the delegate referenced by `{Handler}` on `{Source?.GetType()}`", lineInfo);
}
}
Sample usage
<local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
You have 2 options depending on the the use case :
FYI, there's no way to call another method directly from the view (its a bad design pattern to do so)
Using Event Aggregator :
Create interface
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
}
All you have to do is call it from you TapCommand
_eventAggregator.GetEvent<ItemSelectedEvent>().Publish(_selectedItem);
Then in your Settings.cs you can Create a method that can receive the data
this.DataContext = new ListViewModel(ApplicationService.Instance.EventAggregator);
Inheritance and Polymorphism / Making openCFSPage a service :
Creating a interface / service that links both models
public interface IOpenCFSPage
{
Task OpenPage();
}
and a method :
public class OpenCFSPage : IOpenCFSPage
{
private INavigationService _navigationService;
public OpenCFSPage(INavigationService navigationService){
_navigationService = navigationService;
}
public async Task OpenPage()
{
await _navigationService.NavigateAsync(new CFSPage());
}
}
Settings.xaml:
<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" />
<!-- Uncomment below and corresponding parameter property code in DataGridTemplate.xaml.cs to pass parameter from Settings.xaml -->
<!--<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" CommandParameter="A" />-->
Settings.xaml.cs:
public Settings()
{
InitializeComponent();
OpenCFSPage = new Command(p => OpenCFSPageExecute(p));
BindingContext = this;
}
public ICommand OpenCFSPage { get; private set; }
void OpenCFSPageExecute(object p)
{
var s = p as string;
Debug.WriteLine($"OpenCFSPage:{s}:");
}
DataGridTemplate.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
Padding="0,20"
HeightRequest="49" Margin="0"
x:Class="Japanese.DataGridTemplate">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="Test" />
</Grid>
DataGridTemplate.xaml.cs:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
nameof(TapCommand), typeof(ICommand), typeof(DataGridTemplate), null,
propertyChanged: OnCommandPropertyChanged);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
//public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
// nameof(CommandParameter), typeof(string), typeof(DataGridTemplate), null);
//public string CommandParameter
//{
// get { return (string)GetValue(CommandParameterProperty); }
// set { SetValue(CommandParameterProperty, value); }
//}
static TapGestureRecognizer GetTapGestureRecognizer(DataGridTemplate view)
{
var enumerator = view.GestureRecognizers.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
if (item is TapGestureRecognizer) return item as TapGestureRecognizer;
}
return null;
}
static void OnCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is DataGridTemplate view)
{
var tapGestureRecognizer = GetTapGestureRecognizer(view);
if (tapGestureRecognizer != null)
{
tapGestureRecognizer.Command = (ICommand)view.GetValue(TapCommandProperty);
//tapGestureRecognizer.CommandParameter = (string)view.GetValue(CommandParameterProperty);
}
}
}
}
Check this code you help you. Here you have to pass a reference of list view and also you need to bind a command with BindingContext.
<ListView ItemsSource="{Binding Sites}" x:Name="lstSale">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text="{Binding FriendlyName}" />
<Button Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding
Path=BindingContext.RoomClickCommand,
Source={x:Reference lstSale}}"
CommandParameter="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Resources