1

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

Đức Tú
  • 23
  • 1
  • 5
  • How about `BindableProperty.Create("NameText", typeof(object), typeof(EditForm), BindingMode.TwoWay);` ? – Shaw Jul 26 '21 at 21:29
  • Thanks for your comment but your solution does not work in this case. Could you suggest me another solutions. Thanks :) – Đức Tú Jul 28 '21 at 07:39

2 Answers2

2

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:

enter image description here

Cherry Bu - MSFT
  • 10,160
  • 1
  • 10
  • 16
0

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);
R15
  • 13,982
  • 14
  • 97
  • 173