Unable to disable a button which has IsEnabled bound to a property - c#

I have a problem where I can't seem to disable a button which has IsEnabled bound to a property.
I have the following XAML:
<Button Text="Skip" IsEnabled="{Binding SkipEnabled}" ... />
It is being bound to this BindableProperty:
public static readonly BindableProperty SkipEnabledProperty = BindableProperty.Create(
propertyName: nameof(SkipEnabled), returnType: typeof(bool?), declaringType: typeof(WizardMasterPage), propertyChanged: SetSkipEnabled,
defaultValue: null, defaultBindingMode: BindingMode.TwoWay);
public bool? SkipEnabled
{
get { return (bool?)GetValue(SkipEnabledProperty); }
set { SetValue(SkipEnabledProperty, value); }
}
The problem is the button is always enabled.
The propertyChanged event isn't firing on the pages where the property is set to false - presumably because it is false when the page is created, so doesn't think it has changed and hence the binding is not updated. I notice that it does fire on the pages where I set the property to True.
Has anyone else seen this? Are there any workarounds?
Note: I tried just bool as well and that didn't work either.
Thanks

Is this Button included in a ContentView, right?
If yes, then you can refer to the following code.
I achieved this function and I added a Button on the current page to reset the value, then the UI could refresh automatically.
ChildView.xaml.cs
public partial class ChildView : ContentView
{
public String YourName
{
get
{
String value = (String)GetValue(YourNameProperty);
return value;
}
set
{
SetValue(YourNameProperty, value);
}
}
public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
, typeof(String)
, typeof(ChildView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
public bool SkipEnabled
{
get
{
bool value = (bool)GetValue(SkipEnabledProperty);
return value;
}
set
{
SetValue(YourNameProperty, value);
}
}
public static readonly BindableProperty SkipEnabledProperty = BindableProperty.Create(nameof(SkipEnabled)
, typeof(bool)
, typeof(ChildView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("-----------------> " + newValue);
}
public ChildView()
{
InitializeComponent();
}
}
ChildView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiContentViewApp.ChildView"
x:Name="TestControlView"
>
<VerticalStackLayout>
<Label Text="{Binding Source={x:Reference TestControlView}, Path=YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button Text="Skip" IsEnabled="{Binding Source={x:Reference TestControlView}, Path=SkipEnabled}" />
</VerticalStackLayout>
</ContentView>
MainPage.xaml
<?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"
xmlns:mauiapp="clr-namespace:MauiContentViewApp"
x:Class="MauiContentViewApp.MainPage">
<ContentPage.BindingContext>
<mauiapp:MyViewModel></mauiapp:MyViewModel>
</ContentPage.BindingContext>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<mauiapp:ChildView YourName="{Binding TestName}" SkipEnabled="{Binding TestBoolvalue}" ></mauiapp:ChildView>
<Button Text="reset" Command="{Binding ResetValueCommand}"/>
</VerticalStackLayout>
</ContentPage>
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
private string _testName;
public string TestName
{
set { SetProperty(ref _testName, value); }
get { return _testName; }
}
private bool _testBoolValue;
public bool TestBoolvalue
{
set { SetProperty(ref _testBoolValue, value); }
get { return _testBoolValue; }
}
public ICommand ResetValueCommand => new Command(resetMethod);
private void resetMethod(object obj)
{
TestName = "reset test string";
TestBoolvalue = !TestBoolvalue;
}
public MyViewModel()
{
TestName = "test string";
TestBoolvalue = false;
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

Related

How to bind data to the bindableproperty in xamarin forms

I have two pages that use the same form so I created a content view of form and apply bindable properties like this:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ListContact.Extension.EditForm">
<StackLayout Padding="20">
<Label Text="Edit Contact Forms"
HorizontalOptions="Center"
FontSize="20"
TextColor="Blue"
VerticalOptions="Start"/>
<Label Text="Name"/>
<Entry x:Name="txtName" Text="{Binding NameText}" Placeholder="Name"/>
<Label Text="Phone number"/>
<Entry x:Name="txtPhoneNumber" Text="{Binding PhoneNumberText}" Placeholder="Phone number" Keyboard="Telephone"/>
<Label Text="Email"/>
<Entry x:Name="txtEmail" Text="{Binding EmailText}" Placeholder="Email" Keyboard="Email"/>
<Label Text="Description"/>
<Editor x:Name="txtDescription" Text="{Binding DescriptionText}" Placeholder="Description"
HeightRequest="70"/>
</StackLayout>
</ContentView>
This is the code behind:
using ListContact.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ListContact.Extension
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EditForm : ContentView
{
private static readonly BindableProperty NameProperty = BindableProperty.Create("NameText", typeof(object), typeof(EditForm));
public string NameText { get => (string)GetValue(NameProperty); set => SetValue(NameProperty, value); }
private static readonly BindableProperty PhoneProperty = BindableProperty.Create("PhoneNumberText", typeof(string), typeof(EditForm));
public string PhoneNumberText { get => (string)GetValue(PhoneProperty); set => SetValue(PhoneProperty, value); }
private static readonly BindableProperty EmailProperty = BindableProperty.Create("EmailText", typeof(string), typeof(EditForm));
public string EmailText { get => (string)GetValue(EmailProperty); set => SetValue(EmailProperty, value); }
private static readonly BindableProperty DescriptionProperty = BindableProperty.Create("DescriptionText", typeof(string), typeof(EditForm));
public string DescriptionText { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); }
public EditForm()
{
InitializeComponent();
BindingContext = this;
}
}
}
And in the view I call back this form that I created before and bind data to the bindable property like below:
This is the xaml file:
<ContentPage xmlns:local="clr-namespace:ListContact.Extension"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ListContact.View.ListContactAddingForm">
<local:EditForm NameText="{Binding Name, Mode=TwoWay}" PhoneNumberText="egwebwev" EmailText="ưewevefqwf" DescriptionText="ewebe"/>
<ContentPage.Content>
<Button Text="Save"
HorizontalOptions="Center"
TextColor="White"
Command="{Binding AddContactCommand}"
BackgroundColor="#0A7CFF"/>
</ContentPage.Content>
</ContentPage>
And this is the code behind:
namespace ListContact.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListContactAddingForm : ContentPage
{
private SQLiteAsyncConnection connection;
public ListContactAddingForm()
{
connection = new SQLiteAsyncConnection(BaseConnection.DatabasePath);
ViewModel = new ContactViewModel(new PageService());
InitializeComponent();
}
private ContactViewModel ViewModel
{
get => BindingContext as ContactViewModel;
set => BindingContext = value;
}
}
}
`
This is my view model:
namespace ListContact.ViewModel
{
public class ContactViewModel : BaseViewModel
{
public int Id { get; set; }
private string name;
public string Name
{
get => name;
set
{
SetValue(ref name, value);
}
}
private string description;
public string Description
{
get => description;
set
{
SetValue(ref description, value);
}
}
private string phoneNumber;
public string PhoneNumber
{
get => phoneNumber;
set
{
SetValue(ref phoneNumber, value);
}
}
private string email;
public string Email
{
get => email;
set
{
SetValue(ref email, value);
}
}
public ICommand AddContactCommand { get; private set; }
private IPageService pageService;
public object Alert { get; private set; }
public ContactViewModel(IPageService pageService)
{
this.pageService = pageService;
AddContactCommand = new Command(async () => await AddContacts());
}
private async Task AddContacts()
{
var newContact = new Contact()
{
Name = Name,
PhoneNumber = PhoneNumber,
Email = Email,
Description = Description
};
var result = await connection.InsertAsync(newContact);
if (result == 1)
await App.Current.MainPage.DisplayAlert("Successfully", "", "OK");
await pageService.PopAsycn();
}
}
}
But when I run this code I got the error:
No property, bindable property, or event found for "NameText", or mismatching type between value and property
My code was okay before I separated the form into another content view and call it back from the view and got this problem
So my questions are that:
Is the way that I create form and bindable property correct?
Could I bind data to the bindable property in the form? How to do it if it could?.
And how to fix the above error?
I use MVVM to build this code
Btw, sorry for my bad English
From Xamarin.Forms Bindable Properties, a bindable property can be created by declaring a public static readonly property of type BindableProperty.
public static readonly BindableProperty NameTextProperty = BindableProperty.Create
("NameText", typeof(string), typeof(EditForm),"",BindingMode.OneWay,propertyChanged:NamePropertychanged);
Then you need take care of BindableProperty Name, for example, PropertyNameProperty
public static readonly BindableProperty NameTextProperty = BindableProperty.Create
("NameText", typeof(string), typeof(EditForm),"",BindingMode.OneWay,propertyChanged:NamePropertychanged);
public string NameText
{
get => (string)GetValue(NameTextProperty);
set => SetValue(NameTextProperty, value);
}
Finally, you don't need to use Binding in EditForm, just using propertyChanged event to notify property value has changed.
private static void NamePropertychanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (EditForm)bindable;
control.txtName.Text = (string)newValue;
}
Do one sample that you can take a look:
EditForm xaml.
<ContentView.Content>
<StackLayout Padding="20">
<Label
FontSize="20"
HorizontalOptions="Center"
Text="Edit Contact Forms"
TextColor="Blue"
VerticalOptions="Start" />
<Label Text="Name" />
<Entry x:Name="txtName" Placeholder="Name" />
<Label Text="Phone number" />
<Entry
x:Name="txtPhoneNumber"
Keyboard="Telephone"
Placeholder="Phone number" />
<Label Text="Email" />
<Entry
x:Name="txtEmail"
Keyboard="Email"
Placeholder="Email" />
<Label Text="Description" />
<Editor
x:Name="txtDescription"
HeightRequest="70"
Placeholder="Description" />
</StackLayout>
</ContentView.Content>
public partial class EditForm : ContentView
{
public static readonly BindableProperty NameTextProperty = BindableProperty.Create
("NameText", typeof(string), typeof(EditForm),"",BindingMode.OneWay,propertyChanged:NamePropertychanged);
public string NameText
{
get => (string)GetValue(NameTextProperty);
set => SetValue(NameTextProperty, value);
}
public static readonly BindableProperty PhoneNumberTextProperty = BindableProperty.Create
("PhoneNumberText", typeof(string), typeof(EditForm),"",BindingMode.OneWay,propertyChanged:PhoneNumberchanged);
public string PhoneNumberText
{
get => (string)GetValue(PhoneNumberTextProperty);
set => SetValue(PhoneNumberTextProperty, value);
}
public static readonly BindableProperty EmailTextProperty = BindableProperty.Create
("EmailText", typeof(string), typeof(EditForm),"",BindingMode.OneWay,propertyChanged:Emailpropertychanged);
public string EmailText
{
get => (string)GetValue(EmailTextProperty);
set => SetValue(EmailTextProperty, value);
}
public static readonly BindableProperty DescriptionTextProperty = BindableProperty.Create
("DescriptionText", typeof(string), typeof(EditForm), "", BindingMode.OneWay, propertyChanged: Descriptionchanged);
public string DescriptionText
{
get => (string)GetValue(DescriptionTextProperty);
set => SetValue(DescriptionTextProperty, value);
}
private static void NamePropertychanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (EditForm)bindable;
control.txtName.Text = (string)newValue;
}
private static void PhoneNumberchanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (EditForm)bindable;
control.txtPhoneNumber.Text = (string)newValue;
}
private static void Emailpropertychanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (EditForm)bindable;
control.txtEmail.Text = (string)newValue;
}
private static void Descriptionchanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (EditForm)bindable;
control.txtDescription.Text = (string)newValue;
}
public EditForm()
{
InitializeComponent();
}
}
<StackLayout>
<local:EditForm
DescriptionText="{Binding option}"
EmailText="{Binding Email}"
NameText="{Binding Name}"
PhoneNumberText="{Binding Phone}" />
</StackLayout>
public partial class Page5 : ContentPage
{
public Contact contact1 { get; set; }
public Page5()
{
InitializeComponent();
contact1 = new Contact() { Name = "cherry", Phone = "1234567", Email = "xxxxxxxx", option = "this is test" };
this.BindingContext = contact1 ;
}
}
public class Contact:ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
private string _phone;
public string Phone
{
get { return _phone; }
set
{
_phone = value;
RaisePropertyChanged("Phone");
}
}
private string _email;
public string Email
{
get { return _email; }
set
{
_email = value;
RaisePropertyChanged("Email");
}
}
private string _option;
public string option
{
get { return _option; }
set
{
_option = value;
RaisePropertyChanged("option");
}
}
}
The ViewModelBase implementing INotifyPropertyChanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The screenshot:
When we want to extend any Controls properties we use Bindable Properties. For example for Label Control we need Border and Padding. In Xamarin 4.+ we do not have it. So we can go with Bindable Properties.
I will show you this with Padding. Create a class ExLabel in Shared project.
public class ExLabel : Label
{
public ExLabel()
{
}
public static readonly BindableProperty TitlePaddingProperty = BindableProperty.CreateAttached("Padding", typeof(Thickness), typeof(ExLabel), default(Thickness));
public Thickness TitlePadding
{
get { return (int)base.GetValue(TitlePaddingProperty); }
set { base.SetValue(TitlePaddingProperty, value); }
}
public static Thickness GetTitlePadding(BindableObject view)
{
return (Thickness)view.GetValue(TitlePaddingProperty);
}
}
In android level project implement Custom renderer to use ExLabel class.
[assembly: ExportRenderer(typeof(ExLabel), typeof(ExLabelRenderer))]
namespace Chemicals.Droid.Renderers
{
public class ExLabelRenderer : LabelRenderer
{
Context context;
public ExLabelRenderer(Context context) : base(context)
{
this.context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ExLabel exLabel = (ExLabel)Element;
var padding = ExLabel.GetTitlePadding(exLabel);
Control.SetPadding((int)padding.Left, (int)padding.Top, (int)padding.Right, (int)padding.Bottom);
}
}
}
}
In xaml use padding like this
<local:ExLabel IsBorder="False"
TitlePadding="40,0,0,0"
Text="TR01"/>
DONE.
Similarly we can add other Bindable Properties, say along with padding you want border also for Label and need border for few Labels, not for all the ExLabel. Then we can have Bindable Properties like
public static readonly BindableProperty IsBorderProperty
public bool IsBorder
{
get { return (bool)GetValue(IsBorderProperty); }
set { SetValue(IsBorderProperty, value); }
}
public static readonly BindableProperty ShowBorderProperty
public Color ShowBorder
{
get { return (bool)GetValue(ShowBorderProperty); }
set { SetValue(ShowBorderProperty, value); }
}
From xaml we can pass IsBorder true false and ShowBorder Color. In custom renderer file we can check if IsBorder is true then set Color for border otherwise skip it.
if(ExLabel.GetBorder(exLabel))
Control.SetBackground(set resource here);

Bindable Entry is not working inside custom control in Xamarin.forms?

I have an entry field inside custom control. I've created bindable property for the same.
Here's my code :
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged:MyEntryPropertyChanged );
public static void MyEntryPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var cView = (MyView)bindable;
cView.myent.Text = (string)newValue;
}
Then on my actual view xaml :
<MyView MyEntry={Binding Something}/>
But it's not working.? Any suggestions?
Maybe you do not set the binding correctly. I make a code sample for your reference.
MyView:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
x:Class="XamarinDemo.Custom_Control.MyView"
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">
<StackLayout>
<Entry
x:Name="myent"
HorizontalOptions="CenterAndExpand"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<Button />
</StackLayout>
</ContentView>
Code Behind: Same with yours.
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MyEntryPropertyChanged);
public static void MyEntryPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var cView = (MyView)bindable;
cView.myent.Text = (string)newValue;
}
}
Usage: Page1.xaml
<ContentPage.Content>
<StackLayout>
<local:MyView MyEntry="{Binding Something}"></local:MyView>
</StackLayout>
</ContentPage.Content>
Binding:
public partial class Page1 : ContentPage
{
public string Something { get; set; }
public Page1()
{
InitializeComponent();
Something = "Hello";
this.BindingContext = this;
}
}
Screenshot:
I have had this issue with a custom control of mine, the main problem was my binding context was not set correctly.
The way that I used to check this was
I gave my control a x:name in the xaml
I went to the pagename.cs (code behind) in the constructor after the InializeComponents() method I typed this
Console.WriteLine($"Control-{ControlXname.BindingContext();}");
Console.WriteLine($"Page-{this.BindingContext();}");
After i excuted my code i headed to the output panel and i pressed ctrl + f to search for the word "Control-" and "Page-" only then i have found out that thier binding contexts was different.
You are only setting value to Entry, you are not reading back the changes from Entry. You have to add following in your constructor.
// ... add this in your constructor...
myent.SetBinding(Entry.TextProperty, new Binding{
Path = "MyEntry",
Source = this,
Mode = BindingMode.TwoWay
});
public string MyEntry
{
get => (string)GetValue(MyEntryProperty);
set => SetValue(MyEntryProperty, value);
}
// remove onPropertyChanged
public static BindableProperty MyEntryProperty = BindableProperty.Create(
propertyName: "MyEntry",
returnType: typeof(string),
declaringType: typeof(MyView),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay);

Why is this Xamarin.Forms autowire binding connecting my Command but not my Entry?

I have this XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
...
x:Class="MyProj.LoginPageView"
local:ViewModelLocator.AutoWireViewModel="True">
...cut for brevity...
<Entry MinimumWidthRequest="60" Grid.Row="0" Grid.Column="2" VerticalOptions="Center" Text="{Binding Username, Mode=TwoWay}"/>
<Entry Grid.Row="1" Grid.Column="2" VerticalOptions="Center" Text="{Binding Password, Mode=TwoWay}"/>
<Button Text="Login" Command="{Binding LoginCommand}"/>
<Button Text="Register" Command="{Binding RegisterCommand}"/>
Attached using autowiring to this viewmodel:
public class LoginPageViewModel: BindableObject
{
public ICommand LoginCommand { get; set; }
public ICommand RegisterCommand { get; set; }
private string username;
private string password;
public string Username { get { return username; } set { username = value; OnPropertyChanged("Username"); } }
public string Password { get { return password; } set { password = value; OnPropertyChanged("Password"); } }
public LoginPageViewModel()
{
LoginCommand = new Command(() =>
Login());
RegisterCommand = new Command(() =>
Register());
}
private void Login()
{
}
private void Register()
{
Console.WriteLine("sdfsd");
}
}
The command bindings are working fine and the Register and Login methods get called when I click the button. In the autowiring the binding context of the view is set to the viewmodel as follows:
static ViewModelLocator()
{
_container = new TinyIoCContainer();
//register viewmodels
_container.Register<LoginPageViewModel>();
}
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable)
{
return (bool)bindable.GetValue(ViewModelLocator.AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(BindableObject bindable, bool value)
{
bindable.SetValue(ViewModelLocator.AutoWireViewModelProperty, value);
}
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
if (view == null)
{
return;
}
var viewType = view.GetType();
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = string.Format(
CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null)
{
return;
}
var viewModel = _container.Resolve(viewModelType);
view.BindingContext = viewModel;
}
When I click register in debug mode the register method gets called which means the command binding has been connected but the value I put in the Entry fields for username and password aren't connected to the corresponding properties on the VM and the properties just have null values.

Value is not updating in custom control bindable property - Xamarin forms / Prism

I created a custom control which has an Entry and a Label inside a StackControl. Then I exposed the IsEnabled property of the Entry thorough a bindable property called IsEntryEnabled which I wanted to bind to a bool property of my VM. But it never triggers. Any idea what am I doing wrong here?
Custom control - Xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:VMSTablet.Effects;assembly=VMSTablet"
x:Class="VMSTablet.Controls.StandardFormEntry">
<StackLayout >
<Label Text="{Binding LabelText}" Style="{StaticResource DefaultLabelStyle}" TextColor="{StaticResource DarkGreenColor}"/>
<Entry x:Name="CustomEntry" Text="{Binding Text}"
IsEnabled="{Binding IsEntryEnabled}"
Keyboard="{Binding Keyboard}"
Behaviors="{Binding Behaviors}"
TextColor="{StaticResource DarkGreenColor}"
Placeholder="{Binding Placeholder}" Style="{StaticResource DefaultEntryStyle}" >
<Entry.Effects>
<effects:EntryBarColorEffect/>
</Entry.Effects>
</Entry>
</StackLayout>
</ContentView>
Custom control - Code Behind
namespace VMST.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StandardFormEntry
{
public event EventHandler OnTextChanged;
public event EventHandler OnUnfocused;
public event EventHandler OnFocused;
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create("Placeholder", typeof(string), typeof(StandardFormEntry), string.Empty);
public static readonly BindableProperty LabelTextProperty =
BindableProperty.Create("LabelText", typeof(string), typeof(StandardFormEntry), string.Empty);
public static readonly BindableProperty EntryTextProperty =
BindableProperty.Create("EntryText", typeof(string), typeof(StandardFormEntry), string.Empty);
public static BindableProperty IsEntryEnabledProperty =
BindableProperty.Create("IsEntryEnabled", typeof(bool), typeof(StandardFormEntry), true);
public static readonly BindableProperty KeyboardProperty =
BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(StandardFormEntry), Xamarin.Forms.Keyboard.Default);
public static readonly BindableProperty BehaviorsProperty =
BindableProperty.Create(nameof(Behaviors), typeof(IList<Behavior>), typeof(StandardFormEntry));
public string LabelText
{
set
{
SetValue(LabelTextProperty, value);
}
get => (string)GetValue(LabelTextProperty);
}
public string EntryText
{
set => SetValue(EntryTextProperty, value);
get => (string)GetValue(EntryTextProperty);
}
public string Placeholder
{
set => SetValue(PlaceholderProperty, value);
get => (string)GetValue(PlaceholderProperty);
}
public bool IsEntryEnabled
{
set
{
SetValue(IsEntryEnabledProperty, value);
}
get => (bool)GetValue(IsEntryEnabledProperty);
}
public Keyboard Keyboard
{
set => SetValue(KeyboardProperty, value);
get => (Keyboard)GetValue(KeyboardProperty);
}
public IList<Behavior> Behaviors
{
set => SetValue(BehaviorsProperty, value);
get => (IList<Behavior>)GetValue(BehaviorsProperty);
}
public StandardFormEntry()
{
InitializeComponent();
BindingContext = this;
CustomEntry.BindingContext = this;
PropertyChanged += StandardFormEntry_PropertyChanged;
CustomEntry.Unfocused += CustomEntry_Unfocused;
CustomEntry.TextChanged += CustomEntry_TextChanged;
CustomEntry.Focused += CustomEntry_Focused;
}
private void StandardFormEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == IsEntryEnabledProperty.PropertyName)
{
**//This never happens!**
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
}
private void CustomEntry_Focused(object sender, FocusEventArgs e)
{
OnFocused?.Invoke(sender, e);
}
private void CustomEntry_TextChanged(object sender, TextChangedEventArgs e)
{
OnTextChanged?.Invoke(sender, e);
}
private void CustomEntry_Unfocused(object sender, FocusEventArgs e)
{
OnUnfocused?.Invoke(sender, e);
}
}
}
View Model
I'm trying to trigger the IsEntryEnabled property inside the EditForm() method below. But it doesn't work.
public class PassRegistrationPageViewModel : ViewModelBase
{
public DelegateCommand AddressCommand { get; set; }
public DelegateCommand EditCommand { get; set; }
public DelegateCommand ConfirmCommand { get; set; }
public PassRegistrationPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Page Title";
AddressCommand = new DelegateCommand(ShowBuildings);
EditCommand = new DelegateCommand(EditForm);
ConfirmCommand = new DelegateCommand(ConfirmPass);
//IsQrVisible = false;
}
private bool _isQrVisible;
public bool IsQrVisible
{
get { return _isQrVisible; }
set {
SetProperty(ref _isQrVisible, value);
}
}
private bool _isEditingEnabled;
public bool IsEditingEnabled //Bound to the view below
{
get { return _isEditingEnabled; }
set { SetProperty(ref _isEditingEnabled, value); }
}
private string _text;
public string Text
{
get { return _text; }
set
{
SetProperty(ref _text, value);
}
}
private async void ShowBuildings()
{
await NavigationService.NavigateAsync(nameof(BuildingListPage));
}
public void EditForm()
{
IsEditingEnabled = IsEditingEnabled ? false : true;
}
public void ConfirmPass()
{
}
}
View
I bind the IsEditingEnabled property to IsEntryEnabled property in my custom control which is supposed to trigger the IsEnabled property in the Entry inside. But it never triggers.
<controls:StandardFormEntry IsEntryEnabled="{Binding IsEditingEnabled}" EntryText="{Binding Text}" LabelText="Name" Grid.Column="0" Grid.Row="3"/>
In your custom control, you need to give a name to your content view and refer the name to the source of the binding property, like below:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:VMSTablet.Effects;assembly=VMSTablet"
x:Class="VMSTablet.Controls.StandardFormEntry"
x:Name="CustomEntryControl">
<StackLayout >
<Label Text="{Binding LabelText}" Style="{StaticResource DefaultLabelStyle}" TextColor="{StaticResource DarkGreenColor}"/>
<Entry x:Name="CustomEntry" Text="{Binding Text}"
IsEnabled="{Binding IsEntryEnabled, Source={x:Reference CustomEntryControl}}"
Keyboard="{Binding Keyboard}"
Behaviors="{Binding Behaviors}"
TextColor="{StaticResource DarkGreenColor}"
Placeholder="{Binding Placeholder}" Style="{StaticResource DefaultEntryStyle}" >
<Entry.Effects>
<effects:EntryBarColorEffect/>
</Entry.Effects>
</Entry>
</StackLayout>

How to use bindable property by Binding in xamarin forms?

I am trying to create a content view that contain picker with special design, and wrote all bindable properties like this:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ShobeekClientApp.Custom.Views.Picker">
<ContentView.Content>
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Picker x:Name="FSPicker" Title="{Binding PickerTitle,Source={x:Reference this}"
ItemsSource="{Binding PickerItemsSource,Source={x:Reference this}}"
ItemDisplayBinding="{Binding PickerItemDisplayBinding,Source={x:Reference this}}"
HorizontalOptions="FillAndExpand"/>
</Grid>
</ContentView.Content>
</ContentView>
The code behind of contentView:
using System;
using System.Collections;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ShobeekClientApp.Custom.Views
{
///for more information follow this tutorial
///https://mindofai.github.io/Creating-Custom-Controls-with-Bindable-Properties-in-Xamarin.Forms/
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Picker : ContentView
{
#region selected index property
public static readonly BindableProperty PickerSelectedIndexProperty = BindableProperty.Create(
nameof(PickerSelectedIndex), typeof(int), typeof(Picker), -1, BindingMode.TwoWay, propertyChanged: selctedIndexChanged);
private static void selctedIndexChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.SelectedIndex = (int)newValue;
}
public int PickerSelectedIndex
{
get { return (int)GetValue(PickerSelectedIndexProperty); }
set { SetValue(PickerSelectedIndexProperty, value); }
}
#endregion
#region title Property
public static readonly BindableProperty PickerTitleProperty = BindableProperty.Create(
nameof(PickerTitle), typeof(string), typeof(Picker), defaultValue: "", defaultBindingMode : BindingMode.TwoWay,
propertyChanged: titleChanged);
private static void titleChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.Title = newValue.ToString();
}
public string PickerTitle
{
get { return (string)GetValue(PickerTitleProperty); }
set { SetValue(PickerTitleProperty, value); }
}
#endregion
#region items source property
public static readonly BindableProperty PickerItemsSourceProperty = BindableProperty.Create(
nameof(PickerItemsSource), typeof(IList), typeof(Picker), null, BindingMode.TwoWay, propertyChanged: ItemsSourceChanged);
private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Picker)bindable;
control.FSPicker.ItemsSource = (IList)newValue;
}
public IList PickerItemsSource
{
get { return (IList)GetValue(PickerItemsSourceProperty); }
set { SetValue(PickerItemsSourceProperty, value); }
}
#endregion
public static readonly BindableProperty PickerItemDisplayBindingProperty = BindableProperty.Create(nameof(PickerItemDisplayBinding), typeof(BindingBase), typeof(Picker));
public BindingBase PickerItemDisplayBinding
{
get { return (BindingBase)GetValue(PickerItemDisplayBindingProperty); }
set { SetValue(PickerItemDisplayBindingProperty, value); }
}
public Picker ()
{
try
{
InitializeComponent();
BindingContext = this;
//FSPicker.SetBinding(FSPicker.ItemsSource, new Binding(nameof(Property), source: BindingContext));
//SetBinding(PickerSelectedIndexProperty, );
}
catch (Exception ex)
{
var msg = ex.Message;
}
}
}
}
Using the control code:
<customViwes:Picker PickerTitle="{Binding PickerTitle,Mode=TwoWay}" `PickerItemsSource="{Binding pickerData,Mode=TwoWay}" PickerItemDisplayBinding="{Binding .}"/>`
I want to bind on this property from another design that use this control using {Binding}
If I use absolute value it appears successfully and I will use it with binding as I use MVVM structure, it does not work
You need to define a name for your root and then reference the binding of the root.
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="Root"
x:Class="ShobeekClientApp.Custom.Views.Picker">
<ContentView.Content>
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Picker x:Name="FSPicker" Title="{Binding Source={ x:Reference Root }, Path=PickerTitle}"
ItemsSource="{Binding Source={ x:Reference Root }, Path=PickerItemsSource}"
ItemDisplayBinding="{Binding Source={ x:Reference Root }, Path=PickerItemDisplayBinding}"
HorizontalOptions="FillAndExpand"/>
</Grid>
</ContentView.Content>
</ContentView>

Categories

Resources