OnPropertyChanged not being triggered when BindableProperty is set - c#

I am having trouble with setting the BindableProperty IconName in a custom ContentView in Xamarin.Forms. It is never set and stays empty. The OnPropertyChanged method is executed for every property except the IconName. When I change the type from enum to a string, it works as expected. What am I missing?
The OnPropertyChanged method is needed otherwise non of the properties are set and remain their default value..
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
x:Class="Peripass.Mobile.Framework.UIControls.PeripassIcon"
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"
mc:Ignorable="d">
<ContentView.Resources>
</ContentView.Resources>
<ContentView.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Image>
<Image.Source>
<FontImageSource x:Name="_icon" Glyph="" Color="Black" Size="20" FontFamily="{OnPlatform Android=PeripassIcon.ttf#}" />
</Image.Source>
</Image>
</StackLayout>
</ContentView.Content>
</ContentView>
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Peripass.Mobile.Framework.UIControls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PeripassIcon : ContentView
{
public static readonly BindableProperty IconSizeProperty = BindableProperty.Create(nameof(IconSize), typeof(double), typeof(PeripassIcon), 26.0);
public static readonly BindableProperty IconColorProperty = BindableProperty.Create(nameof(IconColor), typeof(Color), typeof(PeripassIcon), Color.White);
public static readonly BindableProperty IconNameProperty = BindableProperty.Create(nameof(IconName), typeof(IconType), typeof(PeripassIcon), IconType.NuclearExplosion);
public PeripassIcon()
{
InitializeComponent();
}
public IconType IconName
{
get => (IconType)GetValue(IconNameProperty);
set => SetValue(IconNameProperty, value);
}
public double IconSize
{
get => (double)GetValue(IconSizeProperty);
set => SetValue(IconSizeProperty, value);
}
public Color IconColor
{
get => (Color)GetValue(IconColorProperty);
set => SetValue(IconColorProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IconNameProperty.PropertyName) {
_icon.Glyph = $"&#x{(int)IconName:X4};";
}
if (propertyName == IconColorProperty.PropertyName) {
_icon.Color = IconColor;
}
if (propertyName == IconSizeProperty.PropertyName) {
_icon.Size = IconSize;
}
}
}
}
<StackLayout Grid.Row="1" Grid.Column="0">
<uiControls:PeripassIcon IconName="NuclearExplosion" IconColor="#EE4022" IconSize="85.5"/>
</StackLayout>

The problem you are experiencing has to do with the fact that OnPropertyChanged is only triggered when a property's value changes (it is not enough for it to be set!).
In your code you set the default value of IconName to IconType.NuclearExplosion. Later on, when you call the control you set
<uiControls:PeripassIcon IconName="NuclearExplosion" IconColor="#EE4022" IconSize="85.5"/>
which sets IconName to the default value, again... so its value does not really changes, and thus OnPropertyChanged is not triggered.
WARNING
That being said, i have to mention that the "normaly" you would use bindings between BindableProperties and the counterparts in the view. For instance, you would bind the value of Color property in FontImageSource in your ContentView to BindableProperty IconColor. For details see the documentation on ContentView.

Related

Custom control bindable Property not getting hit when set from a view

I've created a control and it has a bindable property, but when I try to set its value, it does not set, when I check its setter, it's not getting hit while debugging, not sure what am I doing wrong.
public decimal MetricValue
{
get => (decimal)GetValue(MetricValueProperty);
set => SetValue(MetricValueProperty, value);
}
public static readonly BindableProperty MetricValueProperty =
BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(HeightSelection),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MetricValuePropertyChanged);
I also have a propertychanged, which is not getting raised
<controls:customControl
CurrentSystemOfMeasure="{Binding CurrentSystemOfMeasure}"
MetricValue="{Binding CurrentHeight}"
TextAlignment="Start"
OnHeightSelectedCommand="{Binding HeightSelectionCommand}"
IsValid="True" />
any inputs would be helpful
have you set context in the xaml view?
Here my way:
public static readonly BindableProperty CancelButtonVisibleAvailableProperty = BindableProperty.Create(
propertyName: "CancelButtonVisible",
returnType: typeof(bool),
declaringType: typeof(SelphiDetailPhotosView),
defaultValue: true);
public bool CancelButtonVisible
{
get { return (bool)GetValue(CancelButtonVisibleAvailableProperty); }
set { SetValue(CancelButtonVisibleAvailableProperty, value); }
}
ContentView needs to have a x:name to set the context in the property IsVisible in your case you need to set the context in Text Property of label to show decimal value
I have set the context making reference to my custom view selphiDetailPhotosView
<Button IsVisible="{Binding Source={x:Reference selphiDetailPhotosView}, Path=CancelButtonVisible}"
Margin="27,0,27,18"
BackgroundColor="{StaticResource PlatinumColor}"
Command="{Binding CancelCommand}"
Text="Cancelar"
TextColor="White"
TextTransform="None" />
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="confia.Views.Views.OnBoardingAndLivenessSharedViews.SelphiDetailPhotosView"
xmlns:svg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
xmlns:transformation="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
xmlns:views="clr-namespace:confia.Views.Views"
xmlns:converters="clr-namespace:confia.Views.Converters"
x:Name="selphiDetailPhotosView"
>
Finally call your customcontrol in your ContentPage and bind with your ViewModel
<customControl:SelphiDetailPhotosView CancelButtonVisible="{Binding CanCancel}"/>
I have replicated your scenario and this work for me.
CodeBehind Custom Control
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static readonly BindableProperty MetricValueProperty = BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(CustomControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: 0m);
public decimal MetricValue
{
get { return (decimal)this.GetValue(MetricValueProperty); }
set { this.SetValue(MetricValueProperty, value); }
}
}
CustomControl Xaml
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ferreplace.Views.Controls.CustomControl"
x:Name="this"
>
<ContentView.Content>
<StackLayout HorizontalOptions="CenterAndExpand" >
<Label Text="Metric Value" FontSize="40"/>
<Label FontSize="20" Text="{Binding Source={x:Reference this}, Path=MetricValue}" ></Label>
</StackLayout>
</ContentView.Content>
ContenPage to call Custom control
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ferreplace.Views.Controls"
x:Class="ferreplace.MainPage">
<StackLayout>
<controls:CustomControl MetricValue="{Binding MetricValueBinding}"/>
</StackLayout>
</ContentPage>
ViewModel
public class MainPageViewModel: BindableBase, IInitializeAsync
{
private decimal _metricValueBinding;
public decimal MetricValueBinding
{
get
{
return _metricValueBinding;
}
set
{
SetProperty(ref _metricValueBinding, value);
}
}
public MainPageViewModel()
{
}
public async Task InitializeAsync(INavigationParameters parameters)
{
MetricValueBinding = 30m;
}
}
Decimal Binding Result
Maybe you forgot default value in bindable property creation

Binding a custom Entry in Xamarin Forms

I'm trying to extract this Entry in a Frame into a custom element in Xamarin, to get a reusable Entry with border only on top and on bottom:
<Frame xmlns="..."
HasShadow="False"
CornerRadius="0"
Padding="0, 1, 0, 1"
BackgroundColor="#c0c0c0">
<Entry Padding="20, 10, 20, 10"
Placeholder="{Binding Placeholder}"
Text="{Binding Text}"
BackgroundColor="#ffffff" />
</Frame>
Code Behind:
public partial class CbSingleEntry : Frame
{
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(CbSingleEntry));
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(CbSingleEntry));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public CbSingleEntry()
{
InitializeComponent();
BindingContext = this;
}
}
When I try to use this custom field, the Placeholder and Text property are correctly set, but I can't bind them to attributes in my class:
// this one works fine
<local:CbSingleEntry Placeholder="Company" Text="My Company" />
// Placeholder works, but Text is always empty
<local:CbSingleEntry Placeholder="Company" Text="{Binding Company}" />
I can confirm that Company has a value, because with a normal text field it works correctly:
// This one works as expected, Text is displayed from binded attribute
<Entry Placeholder="Company" Text="{Binding Company}" />
Cause : in your case , you set the BindingContext in CbSingleEntry
BindingContext = this;
So the binding in ContentPage will not work any more .
Solution:
You could modify the code in CbSingleEntry
in xaml
<Frame 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"
mc:Ignorable="d"
x:Name="CustomView" // set the name here
x:Class="xxx">
<Entry
Placeholder="{Binding Source={x:Reference CustomView},Path=Placeholder}"
Text="{Binding Source={x:Reference CustomView},Path=Text}"
BackgroundColor="#ffffff" />
</Frame>
in code behind
public partial class CbSingleEntry : Frame
{
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(CbSingleEntry));
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(CbSingleEntry));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public CbSingleEntry()
{
InitializeComponent();
// BindingContext = this; don't need to set it any more
}
}

Xamarin Forms / ICommand Bindable property is not working on custom view

I've created a custom view called HeaderTemplate. This control has an image. What am I trying to achieve, is to click on Image and perform some action using MVVM.
Please find below the xml and cs of that control.
HeaderTemplate.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
x:Class="PS.Views.Templates.HeaderTemplate"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:PS.Behaviors">
<ContentView.Content>
<StackLayout
Padding="10"
BackgroundColor="Transparent"
Orientation="Horizontal">
<Image
x:Name="ImageSource_2"
HeightRequest="50"
HorizontalOptions="EndAndExpand"
Source="{Binding ImageSource2}">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="ImageSource2_Tapped" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ContentView.Content>
</ContentView>
HeaderTemplate.xml.cs
public partial class HeaderTemplate : ContentView
{
public HeaderTemplate()
{
InitializeComponent();
BindingContext = this;
}
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2), typeof(string), typeof(HeaderTemplate));
public string ImageSource2
{
get => (string)GetValue(ImageSource2Property);
set => SetValue(ImageSource2Property, value);
}
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(nameof(ImageSource2TapCommand),
typeof(ICommand),
typeof(HeaderTemplate),
null);
public ICommand ImageSource2TapCommand
{
get => (ICommand)GetValue(ImageSource2TapCommandProperty);
set => SetValue(ImageSource2TapCommandProperty, value);
}
private void ImageSource2_Tapped(object sender, EventArgs e)
{
if (ImageSource2TapCommand == null) return;
if (ImageSource2TapCommand.CanExecute(null))
{
ImageSource2TapCommand.Execute(null);
}
}
}
My Page (HolidaysView) has this custom control along with Image click/tap command.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="PS.Views.HolidaysView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:PS.Behaviors"
xmlns:templates="clr-namespace:PS.Views.Templates"
xmlns:viewModelBase="clr-namespace:PS.ViewModels.Base"
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
<ContentPage.Content>
<StackLayout>
<templates:HeaderTemplate
HeightRequest="60"
ImageSource2="upload.png"
ImageSource2TapCommand="{Binding NavigateToCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
and binded view model of this page contains the command
public class HolidaysViewModel : ViewModelBase
{
public HolidaysViewModel()
{
}
public ICommand NavigateToCommand => new Command(async () => await NavigateTo());
private async Task NavigateTo()
{
await NavigationService.NavigateToAsync<HolidayRequestViewModel>();
}
}
It's not working. I don't know where I'm wrong.
Am I missing something?
I've researched alots on this but still I'm not able to find any solution uptill now.
Thank you!
To make it work you will need to do just a couple of changes.
First, in the ContenView class file remove the BindingContext = this; line.
Then you will need to add PropertyChanged handlers to both of your BindableProperty
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2),
typeof(string),
typeof(HeaderTemplate),
defaultValue: default(string),
propertyChanged: OnImageSourcePropertyChanged);
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(
propertyName: nameof(ImageSource2TapCommand),
returnType: typeof(ICommand),
declaringType: typeof(HeaderTemplate),
defaultValue: default(ICommand),
propertyChanged: OnTapCommandPropertyChanged);
If you can't spot the difference I am talking about these: OnImageSourcePropertyChanged and OnTapCommandPropertyChanged. The other changes in the Create method are not necessary I just added the propertyName so it's more clear.
You need to implement those two methods of course:
static void OnTapCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is HeaderTemplate headerTemplate && newValue is ICommand command)
{
headerTemplate.ImageSource2TapCommand = command;
}
}
static void OnImageSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderTemplate headerTemplate && newValue is string imageSource)
{
headerTemplate.ImageSource_2.Source = ImageSource.FromFile(imageSource);
}
}
With these changes you should be able to tap on your Image and navigate as you want.
The reason why?
Since you are Binding values on your "Main Page" to your Custom Control when the latter is first created these values are null, this is why you need to listen to the value changes and this is possible by adding the onPropertyChanged implementation on the Create methods.
There's also a very good explanation here in this post.
Your full class should look something similar to:
public partial class HeaderTemplate : ContentView
{
public HeaderTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2),
typeof(string),
typeof(HeaderTemplate),
defaultValue: default(string),
propertyChanged: OnImageSourcePropertyChanged);
public string ImageSource2
{
get => (string)GetValue(ImageSource2Property);
set => SetValue(ImageSource2Property, value);
}
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(
propertyName: nameof(ImageSource2TapCommand),
returnType: typeof(ICommand),
declaringType: typeof(HeaderTemplate),
defaultValue: default(ICommand),
propertyChanged: OnTapCommandPropertyChanged);
public ICommand ImageSource2TapCommand
{
get => (ICommand)GetValue(ImageSource2TapCommandProperty);
set => SetValue(ImageSource2TapCommandProperty, value);
}
private void ImageSource2_Tapped(object sender, EventArgs e)
{
if (ImageSource2TapCommand == null) return;
if (ImageSource2TapCommand.CanExecute(null))
{
ImageSource2TapCommand.Execute(null);
}
}
static void OnTapCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is HeaderTemplate headerTemplate && newValue is ICommand command)
{
headerTemplate.ImageSource2TapCommand = command;
}
}
static void OnImageSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderTemplate headerTemplate && newValue is string imageSource)
{
headerTemplate.ImageSource_2.Source = ImageSource.FromFile(imageSource);
}
}
}
Hope this helps.-
Note: Try to follow the example posted in one of the other answers to change your TapEvent with the Command property.
TapGestureRecognizer class has a Command property accepting ICommand
I just tried it in a custom control similar to yours and is working fine.
So, for binding a ICommand to a TapGestureRecognizer, use the Command property, not the Tap event.
<Image
x:Name="ImageSource_2"
HeightRequest="50"
HorizontalOptions="EndAndExpand"
Source="{Binding ImageSource2}">
<Image.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding ImageSource2TapCommand}" />
</Image.GestureRecognizers>
</Image>
Reference Adding a tap gesture recognizer: Using ICommand
Set the Source of the BindingContext for the ICommand to your ContentPage.
MyViewCommand="{{Binding Path=BindingContext.NavigateToCommand, Source={x:Reference MyContentPage}}}"
Example:
<?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"
mc:Ignorable="d"
x:Class="MyApp.Views.PageWithCustomView"
xmlns:templates="clr-namespace:MyApp.Views.Templates"
x:Name="MyContentPage"
>
<ContentPage.Content>
<StackLayout>
<templates:HeaderTemplate
HeightRequest="60"
TemplateImageSource="upload.png"
TemplateTapCommand="{{Binding Path=BindingContext.NavigateToCommand, Source={x:Reference MyContentPage}}}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Xamarin binding property of base view

I have a base XAML View that have a custom control with bindable property that is used to show/hide a Grid control.
I need to change this property from a XAML View that inherit from the base View.
the custom control view
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Syncfusion.SfBusyIndicator.XForms;
using System.Runtime.CompilerServices;
namespace TEST_SF
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class VolosLoading : ContentView
{
private static Grid _LoadingContainer = null;
public bool Mostra
{
get { return (bool)GetValue(MostraProperty); }
set { SetValue(MostraProperty, value); OnPropertyChanged(nameof(Mostra)); }
}
public static BindableProperty MostraProperty = BindableProperty.Create(
propertyName: nameof(Mostra),
returnType: typeof(bool),
declaringType: typeof(VolosLoading),
defaultValue: false,
defaultBindingMode: BindingMode.TwoWay
, propertyChanged: MostraPropertyChanged
);
private static void MostraPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
_LoadingContainer.IsEnabled = (bool)newValue;
_LoadingContainer.IsVisible = (bool)newValue;
}
public VolosLoading()
{
InitializeComponent();
_LoadingContainer = (Grid)FindByName("LoadingContainer");
OnPropertyChanged(nameof(Mostra));
}
}
}
its view
<?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:busyindicator="clr-namespace:Syncfusion.SfBusyIndicator.XForms;assembly=Syncfusion.SfBusyIndicator.XForms"
x:Class="TEST_SF.VolosLoading">
<ContentView.Content>
<Grid x:Name="LoadingContainer" IsEnabled="{Binding Mostra}" IsVisible="{Binding Mostra}">
<Grid BackgroundColor="LightGray" Opacity="0.6" />
<busyindicator:SfBusyIndicator x:Name="Loading" AnimationType="DoubleCircle"
ViewBoxWidth="150" ViewBoxHeight="150"
TextColor="Green" BackgroundColor="Transparent"
HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ContentView.Content>
</ContentView>
the base class
namespace TEST_SF.Base
{
public class BaseClass
{
public static VolosLoading PageLoading = new VolosLoading { Mostra = false };
}
}
the base 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:local="clr-namespace:TEST_SF"
x:Class="TEST_SF.BasePage">
<ContentPage.ControlTemplate>
<ControlTemplate>
<StackLayout>
<Label BackgroundColor="Red" Text="Welcome to Xamarin.Forms!"
VerticalOptions="StartAndExpand"
HorizontalOptions="CenterAndExpand" />
<local:VolosLoading></local:VolosLoading>
<ContentPresenter></ContentPresenter>
</StackLayout>
</ControlTemplate>
</ContentPage.ControlTemplate>
</ContentPage>
Then I have a view that inherit from the base View with a button that calls a command that execute this code:
PageLoading.Mostra = !PageLoading.Mostra;
The class is:
class MainPage_Repo : Base.BaseClass
its view:
<local:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TEST_SF"
x:Class="TEST_SF.MainPage">
<ContentPage.BindingContext>
<local:MainPage_Repo />
</ContentPage.BindingContext>
<StackLayout>
<Button VerticalOptions="Center" HorizontalOptions="Center" Text="Loading SF" Command="{Binding MyMockCommand}" CommandParameter="1" />
</StackLayout>
</local:BasePage>
The problems are that the Grid is visible at start, and when the button is pressed nothing changes, the value of Mostra is changed correctly but he Grid is always visible.
How can i solve this?
If you have static properties in ContentView, weird problems are expected to happen, as they are shared across instances, in your case that relates to the _LoadingContainer
Even if this doesn't resolve your problem that is something that can cause huge problems and shouldn't be done.
The problem is when you are calling:
_LoadingContainer = (Grid)FindByName("LoadingContainer");
This line is creating a copy of the Grid container defined in the XAML by x:Name="LoadingContainer". Then, when the Mostra property changes, you are then doing:
_LoadingContainer.IsEnabled = (bool)newValue;
_LoadingContainer.IsVisible = (bool)newValue;
The above is accessing and changing the properties of the copy of the Grid view, not the actual LoadingContainer itself.
Replace all instances of _LoadingContainer with LoadingContainer, and your problem should be solved.
Also, you can remove the bindings to the IsVisible and IsEnabled properties of the Grid. These will cause unnecessary complexity in the compiled code.
EDIT
In addition, in your MostraPropertyChanged handler, you should change it to the following:
private static void MostraPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (VolosLoading)bindable;
if (control != null)
{
control.LoadingContainer.IsEnabled = (bool)newValue;
control.LoadingContainer.IsVisible = (bool)newValue;
}
}

Bind a custom view to page model in xamarin forms

I am trying to create a custom view that will be used as a header in some of the pages in the application. A custom view has a button to save info, and an image to show if the info was saved, but I can also receive info from the API if the info was saved. (this is a simplified version of the scenario)
So, I have MainPage.xaml (any page that will use the custom view)
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Messages"
xmlns:controls="clr-namespace:Messages.Controls"
x:Class="Messages.MainPage">
<StackLayout Spacing="5">
<controls:HeaderMenu x:Name="menu" HorizontalOptions="FillAndExpand" VerticalOptions="Start" SaveCommand="{Binding MyCommand}" IsControlClosed="{Binding ControlClosedValue, Mode=TwoWay}" />
.....
</StackLayout>
MainPageViewModel.cs
public class MainPageViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; set; }
private bool _controlClosedvalue;
public bool ControlClosedValue
{
get => _controlClosedvalue;
set
{
_controlClosedvalue = value;
OnPropertyChanged(nameof(ControlClosedValue));
}
}
public MainPageViewModel()
{
MyCommand = new Command(MyCommandExecute);
_controlClosedvalue = false;
}
private void MyCommandExecute()
{
// do stuff
_controlClosedvalue = true; //change value to change the value of control
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
HeaderMenu.xaml
<Grid>
<Image Source="save.png" HeightRequest="25" WidthRequest="25">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="SaveImage_OnTapped" />
</Image.GestureRecognizers>
</Image>
<Image IsVisible="{Binding IsControlClosed}" Source="check.png" HeightRequest="30" WidthRequest="30" />
HeaderMenu.xaml.cs
public partial class HeaderMenu : ContentView
{
public HeaderMenu ()
{
InitializeComponent();
imgControlClosed.BindingContext = this;
}
public static readonly BindableProperty SaveCommandProperty =
BindableProperty.Create(nameof(SaveCommand), typeof(ICommand), typeof(HeaderMenu));
public static readonly BindableProperty IsControlClosedProperty =
BindableProperty.Create(nameof(IsControlClosed), typeof(bool), typeof(HeaderMenu), false, BindingMode.TwoWay, null, ControlClosed_OnPropertyChanged);
public ICommand SaveCommand
{
get => (ICommand) GetValue(SaveCommandProperty);
set => SetValue(SaveCommandProperty, value);
}
public bool IsControlClosed
{
get => (bool) GetValue(IsControlClosedProperty);
set => SetValue(IsControlClosedProperty, value);
}
private static void ControlClosed_OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderMenu control)
{
control.imgControlClosed.IsVisible = (bool)newValue;
}
}
private void SaveImage_OnTapped(object sender, EventArgs e)
{
if (SaveCommand != null && SaveCommand.CanExecute(null))
{
SaveCommand.Execute(null);
}
}
}
So, what I need is that when the save command is tapped to execute some code in the page that is using control, and binding of SaveCommand works as expected. But after the code is executed, or in some different cases, I wish to change the property in the page model and this should change the property on the custom view, but this does not work.
Does anyone know what is wrong with this code?
If I just put True or False when consuming control it works.
<controls:HeaderMenu x:Name="menu" HorizontalOptions="FillAndExpand" VerticalOptions="Start" SaveCommand="{Binding MyCommand}" IsControlClosed="True" />
But it does not work when binding it to the property.
I have found out what an issue was. A stupid mistake, I was setting the value of the variable instead of property.
In the main page view model, instead of
_controlClosedvalue = false; // or true
it should be
ControlClosedValue = false; // or true

Categories