Getting NullReferenceException in a ValueConverter - xamarin.forms

I have the following IValueConverter, StringCaseConverter.cs:
internal sealed class StringCaseConverter : IValueConverter
{
public bool IsUpperCase { private get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
var stringValue = value.ToString();
return this.IsUpperCase ? stringValue.ToUpperInvariant() : stringValue.ToLowerInvariant();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
{
return value;
}
}
I registered it in App.xaml:
<Application.Resources>
<ResourceDictionary>
<converters:StringCaseConverter x:Key="StringToLowerCaseConverter" IsUpperCase="False"/>
<converters:StringCaseConverter x:Key="StringToUpperCaseConverter" IsUpperCase="True"/>
</ResourceDictionary>
</Application.Resources>
And used it in MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="App1.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<CollectionView HorizontalOptions="Center">
<CollectionView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>A</x:String>
<x:String>B</x:String>
<x:String>C</x:String>
<x:String>D</x:String>
<x:String>E</x:String>
<x:String>F</x:String>
<x:String>G</x:String>
<x:String>H</x:String>
</x:Array>
</CollectionView.ItemsSource>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" ItemSpacing="20"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal" HorizontalOptions="End" VerticalOptions="Center">
<Label Text="{Binding ., Converter={StaticResource StringToLowerCaseConverter}}" HorizontalOptions="Center"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
When I run the application, I get a NullReferenceExecption in StringCaseConverter.cs (var stringValue = value.ToString();) because value parameter of Convert() method is null.
What am I missing?

You just need to make a null value judgment.
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
if (string.IsNullOrEmpty((string)value))
{
return null;
}
var stringValue = value.ToString();
return this.IsUpperCase ? stringValue.ToUpperInvariant() : stringValue.ToLowerInvariant();
}

Related

Can't get the CollectionView to Display data in my collection

I am trying to make a collectionView display the items in a collection I have created but that doe snot work at all. I have been looking around to understand the issue but I can't figure it out. Can someone help me out ? See below the XAML code for a custom control I created and following it the code behind in c#
'''
<StackLayout xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomControls.CustomControl.DataGrid2"
xmlns:local="clr-namespace:CustomControls.CustomControl"
HeightRequest="500"
WidthRequest="500"
Orientation="Horizontal">
<CollectionView x:Name="c2" ItemsLayout="VerticalList" x:DataType="local:DataGrid2" ItemsSource="{Binding Data2see}" >
<CollectionView.Header>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Header>
<CollectionView.Footer>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Xamarin Monkey"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Footer>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:SomeDataExemple2">
<Grid Padding="10" ColumnDefinitions="100,100,100">
<Button Grid.Column="0" Text ="clickmenow2"/>
<Entry Grid.Column= "1" Text="{Binding Name}"/>
<Entry Grid.Column="2" Text="{Binding First_name}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
namespace CustomControls.CustomControl
{ using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
public partial class DataGrid2 : StackLayout
{
public DataGrid2()
{
Data2see = new Collection<SomeDataExemple2>();
Data2see.Add(new SomeDataExemple2("hamid", "britel"));
Data2see.Add(new SomeDataExemple2("mernissi", "wadie"));
Data2see.Add(new SomeDataExemple2("mhamed", "chraibi"));
Data2see.Add(new SomeDataExemple2("yazid", "alaoui"));
Data2see.Add(new SomeDataExemple2("amine", "gogole"));
Data2see.Add(new SomeDataExemple2("mehdi", "harras"));
InitializeComponent();
//c2.ItemsSource = Data;
}
public Collection<SomeDataExemple2> Data2see
{
get;
set;
}
}
public class SomeDataExemple2
{
public SomeDataExemple2(string name1, string name2)
{
Name = name1;
First_name = name2;
}
public string Name
{
get; set;
}
public string First_name
{
get;
set;
}
}}
.xaml code, you can use ContentPage instead of StackLayout
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp_test.MainPage"
xmlns:local="clr-namespace:MauiApp_test"
HeightRequest="500"
WidthRequest="500">
<CollectionView x:Name="c2" ItemsLayout="VerticalList" >
<CollectionView.Header>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Header>
<CollectionView.Footer>
<StackLayout BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Xamarin Monkey"
FontSize="Small"
FontAttributes="Bold" />
</StackLayout>
</CollectionView.Footer>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" ColumnDefinitions="100,100,100">
<Button Grid.Column="0" Text ="clickmenow2"/>
<Entry Grid.Column= "1" Text="{Binding Name}"/>
<Entry Grid.Column="2" Text="{Binding First_name}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
.xaml.cs
using System.Collections.ObjectModel;
namespace MauiApp_test;
public partial class MainPage : ContentPage
{
public Collection<SomeDataExemple2> Data2see
{
get;
set;
}
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Data2see = new Collection<SomeDataExemple2>();
Data2see.Add(new SomeDataExemple2("hamid", "britel"));
Data2see.Add(new SomeDataExemple2("mernissi", "wadie"));
Data2see.Add(new SomeDataExemple2("mhamed", "chraibi"));
Data2see.Add(new SomeDataExemple2("yazid", "alaoui"));
Data2see.Add(new SomeDataExemple2("amine", "gogole"));
Data2see.Add(new SomeDataExemple2("mehdi", "harras"));
c2.ItemsSource = Data2see
.ToList();
}
public class SomeDataExemple2
{
public SomeDataExemple2(string name1, string name2)
{
Name = name1;
First_name = name2;
}
public string Name
{
get; set;
}
public string First_name
{
get;
set;
}
}
}

How one ViewModel contains multiple ViewModel xamatin

I have 2 ViewModels (MVVM). I let show 2 as shown, it only shows data 1 ViewModel (the one below).
I put 1 and it shows up as normal.
This is how I display the data
<RefreshView x:DataType="locals:SliderViewModel"
Command="{Binding LoadSliderCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout x:DataType="model:SliderShowInfo">
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView x:DataType="locals:ProductViewModel"
Command="{Binding LoadProductCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
<StackLayout x:DataType="model:ProductInfo">
</StackLayout>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
This is how I display the data. I'm trying to display product listing and photo listing data. Please give me a solution that can combine 2 ViewModel
Update
SliderViewModel.cs
public class SliderViewModel:BaseSliderViewModel
{
ISliderShowRepository sliderShowRepository = new SliderShowService();
public Command LoadSliderCommand { get; }
public ObservableCollection<SliderShowInfo> SliderShowInfos { get; }
public SliderViewModel()
{
LoadSliderCommand = new Command(async () => await ExecuteLoadSliderCommand());
SliderShowInfos = new ObservableCollection<SliderShowInfo>();
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadSliderCommand()
{
}
}
ProductViewModel.cs
public class ProductViewModel : BaseProductViewModel
{
IProductRepository productRepository = new ProductService();
public Command LoadProductCommand { get; }
public ObservableCollection<ProductInfo> ProductInfos { get; }
public Command ProductTappedView { get; }
public ProductViewModel(INavigation _navigation)
{
LoadProductCommand = new Command(async () => await ExecuteLoadProductCommand());
ProductInfos = new ObservableCollection<ProductInfo>();
ProductTappedView = new Command<ProductInfo>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(ProductInfo prod)
{
await Navigation.PushAsync(new DetailProduct(prod));
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadProductCommand()
{
IsBusy = true;
try
{
ProductInfos.Clear();
var prodList = await productRepository.GetProductsAsync();
foreach (var prod in prodList)
{
ProductInfos.Add(prod);
}
}
catch(Exception)
{
throw;
}
finally
{
IsBusy = false;
}
}
}
DashboardViewModel.cs
public class DashboardViewModel
{
public SliderViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
Dashboard.xaml.cs
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
You cant just have 2 ViewModels for a single page. Instead, you can create another ViewModel, which will contains those 2 ViewModels that you need.
public class DashboardViewModel
{
public SliderShowViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
And in dashboard constructor, assign the BindingContext
BindingContext = new DashboardViewModel();
And in your view, bind data like this:
...
<Image Source="{Binding SliderShowVM.ImagesSlider}">
...
BindableLayout.ItemsSource="{Binding ProductVM.ProductInfos}"
...
Also, if you don't need all data from SliderShowViewModel or ProductViewModel on your Dashboard, then you can define the properties that you really need for the dashboard inside DashboardViewModel, and to inject SliderShowViewModel and ProductViewModel instances via constructor (Might not be the best solution, but this way you will keep you view cleaner)
public class DashboardViewModel
{
public string RelevantProperty1 { get; set; }
public int RelevantProperty2 { get; set; }
...
public DashboardViewModel(SliderShowViewModel sliderVm, ProductViewModel productVm)
{
RelevantProperty1 = sliderVm.Something;
RelevantProperty2 = productVm.SomethingElse;
...
}
}
More explicit
DashboardViewModel class: It is a class with two properties of type SliderShowViewModel and ProductViewModel. These properties will store some instances (aka objects) of SliderShowViewModel and ProductViewModel respectively.
Create a DashboardViewModel instance and pass it as BindingContext for your Dashboard View:
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
var viewModel = new DashboardViewModel();
// Now we have the viewModel, but there is a problem.
// Remember those two viewModels (SliderShowViewModel and ProductViewModel)?
// Well, those properties are null, we have to provide values for them
// before setting the binding context.
viewModel.SliderViewModel = new SliderViewModel();
viewModel.ProductViewModel = new ProductViewModel();
BindingContext = viewModel;
}
Bind values from DashboardViewModel on our View:
<RefreshView Command="{Binding SliderViewModel.LoadSliderCommand}"
IsRefreshing="{Binding SliderViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderViewModel.SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding SliderViewModel.ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView Command="{Binding ProductViewModel.LoadProductCommand}"
IsRefreshing="{Binding ProductViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductViewModel.ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Give it a try and let me know if it works.

OnApplyTemplate doesn't capture the BindingContext of the parent?

In a test project, I'm trying to get the BindingContext of the parent control with template binding,
here, in the MainPage, I have two templates temp1 and temp2
<?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:vm="clr-namespace:MyXam.ViewModels"
xmlns:views="clr-namespace:MyXam.Views"
x:Class="MyXam.Views.MainPage"
x:DataType="vm:MainViewModel">
<ContentPage.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="temp1">
<views:View1/>
</ControlTemplate>
<ControlTemplate x:Key="temp2">
<views:View2/>
</ControlTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout x:Name="stk">
<Button Text="Switch view" Command="{Binding SwitchViewCommand}"/>
<ContentView x:Name="cv" ControlTemplate="{StaticResource temp2}" VerticalOptions="Start" HorizontalOptions="Center">
<ContentView.Triggers>
<DataTrigger TargetType="ContentView" Binding="{Binding IsView1}" Value="False">
<Setter Property="ControlTemplate" Value="{StaticResource temp2}"/>
</DataTrigger>
<DataTrigger TargetType="ContentView" Binding="{Binding IsView1, Mode=TwoWay}" Value="True">
<Setter Property="ControlTemplate" Value="{StaticResource temp1}"/>
</DataTrigger>
</ContentView.Triggers>
</ContentView>
</StackLayout>
</ContentPage>
I want to get the BindingContext of the MainPage in View2, in the ctor:
SetBinding(BindingContextProperty, new Binding("Parent.BindingContext", source: RelativeBindingSource.TemplatedParent));
but when I try to get its vzlue in the OnApplyTemplate it's null:
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
vm = this.GetValue(BindingContextProperty);
}
However binding is resolved in xaml:
<Label Text="{Binding Name, Source={Reference this}}"/>
I couldn't see the detail of your other code, but you can refer to the following code.
Define a BindableProperty for this Label in xaml.cs and use the Name Property,e.g. x:Name="TestControlView" and binding like this
<Label Text="{Binding Source={x:Reference TestControlView}, Path=TestText}" />
You can check the full demo here.
The main code is like this:
TestControl.xaml.cs(TestControl is a ContentView)
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestControl : ContentView
{
public static readonly BindableProperty TestTextProperty = BindableProperty.Create(nameof(TestText), typeof(string), typeof(TestControl));
public string TestText
{
get { return (string)GetValue(TestTextProperty); }
set { SetValue(TestTextProperty, value); }
}
public TestControl()
{
InitializeComponent();
}
}
TestControl.xaml
MainPage.xaml
<ListView x:Name="lstView" HorizontalOptions="Fill" HasUnevenRows="True" RefreshAllowed="true" IsPullToRefreshEnabled="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame CornerRadius="10" BackgroundColor="White" Margin="0,5,0,5">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<controls:TestControl TestText="{Binding Title}" VerticalOptions="Center"/>
<Label Text="{Binding Type}" FontSize="Medium" TextColor="#F0BB7F" FontAttributes="Bold" VerticalOptions="Center"/>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public ObservableCollection<TestModel> veggies { get; set; }
public MainPage()
{
InitializeComponent();
veggies = new ObservableCollection<TestModel>();
veggies.Add(new TestModel { Title = "Tomato", Type = "Fruit" });
veggies.Add(new TestModel { Title = "Zucchini", Type = "Vegetable" });
veggies.Add(new TestModel { Title = "Tomato" , Type = "Vegetable" });
veggies.Add(new TestModel { Title = "Romaine", Type = "Fruit" });
veggies.Add(new TestModel { Title = "Zucchini", Type = "Vegetable" });
lstView.ItemsSource = veggies;
BindingContext = this;
}
}

My Application is not working after added custom behavior, cant understand why

I have created a custom stepper Behavior, and added that behavior to a stepper in my xaml, but for some reason after adding the behavior the application doesn't compile, and i get this error:
Position 82:87. No property, bindable property, or event found for 'ValueChangedCommand', or mismatching type between value and property. (ComanderoMovil)
here is my code of the behavior:
using System;
using System.Windows.Input;
using Xamarin.Forms;
namespace ComanderoMovil.Behaviors
{
public class StepperQuantityChangedBehavior : Behavior<Stepper>
{
public static readonly BindableProperty StepperValueChangedProperty =
BindableProperty.Create("ValueChangedCommand", typeof(ICommand), typeof(StepperQuantityChangedBehavior), null);
public ICommand ValueChangedCommand
{
get
{
return (ICommand)GetValue(StepperValueChangedProperty);
}
set
{
SetValue(StepperValueChangedProperty, value);
}
}
protected override void OnAttachedTo(Stepper bindable)
{
base.OnAttachedTo(bindable);
bindable.ValueChanged += Bindable_ValueChanged;
}
protected override void OnDetachingFrom(Stepper bindable)
{
base.OnDetachingFrom(bindable);
bindable.ValueChanged -= Bindable_ValueChanged;
}
private void Bindable_ValueChanged(object sender, ValueChangedEventArgs e)
{
if (ValueChangedCommand == null)
{
return;
}
var stepper = sender as Stepper;
var prueba = e.NewValue;
if (ValueChangedCommand.CanExecute(prueba))
{
ValueChangedCommand.Execute(prueba);
}
}
}
}
and here is my code of the xaml where I add the behavior:
<?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="ComanderoMovil.Views.DishView"
xmlns:converterPack="clr-namespace:Xamarin.Forms.ConvertersPack;assembly=Xamarin.Forms.ConvertersPack"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.UseSafeArea="true"
xmlns:local="clr-namespace:ComanderoMovil.Behaviors"
x:Name="DishSelectedPage">
<ContentPage.ToolbarItems>
<ToolbarItem Icon="shopping_cart" Text="Search" Command="{Binding ShowCartCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<converterPack:CurrencyConverter x:Key="CurrencyConverter"></converterPack:CurrencyConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<StackLayout>
<Label Text="{Binding Dish.Name}"
FontSize="Title"
HorizontalOptions="Center"
FontAttributes="Bold"></Label>
<Label Text="Precio"
FontSize="Subtitle"
HorizontalOptions="Center"
FontAttributes="Bold"></Label>
<Label Text="{Binding Dish.Price1, Converter={StaticResource CurrencyConverter}}"
FontSize="Subtitle"
HorizontalOptions="Center"></Label>
<Label Text="Modificadores"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"></Label>
<ListView ItemsSource="{Binding DishesMods}"
x:Name="ModsListView"
HasUnevenRows="True"
SeparatorVisibility="Default"
SeparatorColor="Black"
IsGroupingEnabled="True"
HeightRequest="{Binding ListHeight}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell Height="30">
<StackLayout VerticalOptions="FillAndExpand"
Padding="10"
BackgroundColor="DimGray">
<Label Text="{Binding Key}"
TextColor="White"
VerticalOptions="Center"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<CheckBox Color="#102536">
<CheckBox.Behaviors>
<local:CheckBoxModChangedState ItemCheckedCommand="{Binding BindingContext.SelectedModCommand, Source={Reference DishSelectedPage}}"></local:CheckBoxModChangedState>
</CheckBox.Behaviors>
</CheckBox>
<Label Text="{Binding Name}"
VerticalOptions="Center"></Label>
<Label Text="Precio:"
VerticalOptions="Center"></Label>
<Label Text="{Binding Price}"
VerticalOptions="Center"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Cantidad: "></Label>
<Label Text="1"></Label>
</StackLayout>
<StackLayout>
<Stepper HeightRequest="40"
WidthRequest="40">
<Stepper.Behaviors>
<local:StepperQuantityChangedBehavior ValueChangedCommand="{Binding BindingContext.ModQuantityCommand, Source={Reference DishSelectedPage}}"></local:StepperQuantityChangedBehavior>
</Stepper.Behaviors>
</Stepper>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Footer>
<ContentView>
<Frame HasShadow="False"
Padding="50">
<Button Padding="20"
Text="Agregar Orden"
TextColor="White"
BackgroundColor="#102536"
Command="{Binding BindingContext.AddOrderCommand, Source={Reference DishSelectedPage}}"></Button>
</Frame>
</ContentView>
</ListView.Footer>
</ListView>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
the funny thing is that, I have added custom behavior for other controls, like the checkbox, and it works without a problem, but only with this new behavior I am having trouble.
Anyone know's what is happening?
public static readonly BindableProperty StepperValueChangedProperty
public ICommand ValueChangedCommand
the issue is on the lines,as the error message said
event found for 'ValueChangedCommand', or mismatching type between
value and property. (ComanderoMovil)
you should change StepperValueChangedProperty to ValueChangedCommandProperty to keep the name consistent with ValueChangedCommand
change
public static readonly BindableProperty StepperValueChangedProperty =
BindableProperty.Create("ValueChangedCommand", typeof(ICommand), typeof(StepperQuantityChangedBehavior), null);
public ICommand ValueChangedCommand
{
get
{
return (ICommand)GetValue(StepperValueChangedProperty);
}
set
{
SetValue(StepperValueChangedProperty, value);
}
}
to
public static readonly BindableProperty ValueChangedCommandProperty =
BindableProperty.Create("ValueChangedCommand", typeof(ICommand), typeof(StepperQuantityChangedBehavior), null);
public ICommand ValueChangedCommand
{
get
{
return (ICommand)GetValue(ValueChangedCommandProperty);
}
set
{
SetValue(ValueChangedCommandProperty, value);
}
}

Xamarin set IsVisible of a button inside a Grid based on multiple bound properties in the same Grid.Row without editing the original model

I have a button that can appear in each row of a grid.
I need to hide the button if any bound property in the "row" is null.
This is using the MVVM pattern so that the ViewModel has an ObservableCollection.
Very simplified example:
public class TestClass
{
public int? Prop1 { get; set; }
public int? Prop2 { get; set; }
}
public class TestViewModel
{
private ObservableCollection<TestClass> _TestClasses;
public ObservableCollection<TestClass> TestClasses
{
get { return _TestClasses; }
set { _TestClasses = value; OnPropertyChanged(nameof(TestClasses)); }
}
// LOAD TEST DATA IN CONSTRUCTOR
public TestViewModel()
{
var testClasses = new List<TestClass>();
testClasses.Add(new TestClass { Prop1 = 1, Prop2 = 1 };
testClasses.Add(new TestClass { Prop1 = 2, Prop2 = null };
testClasses.Add(new TestClass { Prop1 = null, Prop2 = 2 };
TestClasses = new ObservableCollection<TestClass>(testClasses);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class NullBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? false : true;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The following XAML will hide the button when "Prop1" is null BUT I need to hide the button if "EITHER" Prop1 or Prop2 is NULL.
<?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:converters="clr-namespace:TestProject.Converters"
x:Class="TestProject">
<ContentPage.Resources>
<ResourceDictionary>
<converters:NullBooleanConverter x:Key="NullBooleanConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ListView ItemsSource="{Binding TestClasses}"
SelectionMode="Single"
HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Label Text="{Binding Prop1}"/>
</StackLayout>
<StackLayout Grid.Column="1">
<Label Text="{Binding Prop2}"/>
</StackLayout>
<StackLayout Grid.Column="2">
<Button Text="Do Something"
IsVisible="{Binding Path=Prop1, Converter={StaticResource NullBooleanConverter}}" />
</StackLayout>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
UPDATE 1
I can't edit the original model because it is in a shared library that is used among several other projects.
I could create a partial class of the model and but seems overly complicated in that in all our other projects this is easily achievable in the UI or using a IMultiValueConverter but this is not supported in Xamarin.
UPDATE 2
I have found a "hack" way of doing this inside the NullBooleanConverter.
public class NullBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TestClass)
{
TestClass testClass = (TestClass)value;
if (testClass.Prop1 == null || testClass.Prop2 == null)
{
return false;
}
else
{
return true;
}
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can also binding the property IsVisible to ViewModel
<StackLayout Grid.Column="2">
<Button Text="Do Something"
IsVisible="{Binding isVisible}" />
</StackLayout>
in Model
public class TestClass
{
public string? Prop1 { get; set; }
public string? Prop2 { get; set; }
public bool isVisible { get; private set; }
public TestClass(string? p1,string?p2)
{
if(p1==null||p2==null)
{
isVisible = false;
}
else
{
isVisible = true;
}
}
}
And in ViewModel
var testClasses = new List<TestClass>();
testClasses.Add(new TestClass("1","1") );
testClasses.Add(new TestClass(null,"2") );
testClasses.Add(new TestClass("1",null));
TestClasses = new ObservableCollection<TestClass>(testClasses);
In addition,the type of text is string not int .
Update:
I think your way is wisely (not a hack way) . You just need to set
<Button Text="Do Something" IsVisible="{Binding , Converter={StaticResource NullBooleanConverter}}" />
Otherwise, you can also create a subclass of Button and define two BindableProperties , then binding them to Prop1 and Prop2 . It will be complex which I don't suggest you to choose it .

Resources