How to use BindableProperty.Create in Xamarin.Forms? - c#

In xaml in Xamarin.Forms, I have a custom control, I want to add property of type int. I think I have to use Bindable properties, so later I can bind a property from ViewModel.
I found this topic, but I'm not sure how to use it.. there is:
BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(BindablePicker), null,
propertyChanged: OnItemsSourcePropertyChanged);
what's "BindablePicker"? Is it the view where property is declared?
Here's my try:
public int WedgeRating
{
get
{
return (int)GetValue(WedgeRatingProperty);
}
set
{
try
{
SetValue(WedgeRatingProperty, value);
}
catch (ArgumentException ex)
{
// We need to do something here to let the user know
// the value passed in failed databinding validation
}
}
}
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create(nameof(WedgeRating), typeof(int), typeof(GameCocosSharpView), null, propertyChanged: OnItemsSourcePropertyChanged);
private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
I didn't even use it in xaml, and it already doesn't work. No particular exception. Only the page where the custom control is initialized doesn't loead. When I comment line I pasted here, it works.

Your code is good, just change your default value from null to 0 or default(int). You have it as null but an int property could never be null. This was the reason of the "crash".
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create (nameof (WedgeRating), typeof (int), typeof (GameCocosSharpView), default(int), propertyChanged: OnItemsSourcePropertyChanged);
Hope this helps!

Here is the example for Bindable Property
public class GameCocosSharpView : View
{
public int WedgeRating
{
get { return (int)GetValue(WedgeRatingProperty); }
set { SetValue(WedgeRatingProperty, value); }
}
public static void WedgeRatingChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create("WedgeRating", typeof(int), typeof(GameCocosSharpView), 1, BindingMode.Default, null, WedgeRatingChanged);
}

Related

How do I define two bindable properties which update each other in Xamarin.Forms?

I want to declare a BindableProperty which acts as a convenience property to another BindableProperty in Xamarin.Forms. Setting one will update the other. I won't give the full context and the actual types, but a very simple and understandable scenario would be as follows:
I have a view which defines a Number and a Numberp1 property. Number is bindable, while the Numberp1 property acts as the convenience property here.
//Definition
public class MyView : ContentView
{
public static BindableProperty NumberProperty = BindableProperty.Create(nameof(Number), typeof(int), typeof(MyView));
public int Number { get => (int)GetValue(NumberProperty); set => SetValue(NumberProperty, value); }
public int Numberp1 { get => Number + 1; set => Number = value - 1; }
}
//Usage
<local:MyView Number="{Binding Number}"/>
Things go well until a customer discovers that Numberp1 isn't bindable and would like me to make it bindable.
<local:MyView Numberp1="{Binding Numberp1}"/>
//error : No property, bindable property, or event found for 'Numberp1', or mismatching type between value and property.
How would I make both of these properties bindable but make them update each other? I tried investigating using Converters but they seem to only be usable at the Binding, not the BindableProperty definition.
Things go well until a customer discovers that Numberp1 isn't bindable and would like me to make it bindable.
Please create another BindableProperty for Numberp1,the Placeholder for the BindableProperty 'Numberp1Property ' should always match the name without 'Property'.
public static BindableProperty NumberProperty = BindableProperty.Create(nameof(Number), typeof(int), typeof(MyView11), null, propertyChanged: OnNumberChanged);
private static void OnNumberChanged(BindableObject bindable, object oldValue, object newValue)
{
var num = (MyView11)bindable;
num.Number = (int)newValue;
}
public static BindableProperty NumberpProperty = BindableProperty.Create(nameof(Numberp), typeof(int), typeof(MyView11), null, propertyChanged: OnNumber1Changed);
private static void OnNumber1Changed(BindableObject bindable, object oldValue, object newValue)
{
var num = (MyView11)bindable;
num.Numberp = (int)newValue;
}
public int Number
{
get => (int)GetValue(NumberProperty);
set => SetValue(NumberProperty, value);
}
public int Numberp
{
get => Number + 1;
set => Number = value - 1;
}
Adding propertyChanged event for BindableProperty.
They both have to be defined as BindableProperty. Unfortunately, you can't rely on the setter methods being called, because the runtime doesn't necessarily go through the setter, it can directly call SetValue, which updates the definitive source of the value for a BindableProperty.
However you can rely on the fact that BindableProperty does get you PropertyChanged notifications automatically for either of your linked properties.
So, first define both properties:
public static BindableProperty Number1Property = BindableProperty.Create(nameof(Number1), typeof(int), typeof(MyView));
public static BindableProperty Number2Property = BindableProperty.Create(nameof(Number2), typeof(int), typeof(MyView));
public int Number1 { get => (int)GetValue(Number1Property); set => SetValue(Number1Property, value); }
public int Number2 { get => (int)GetValue(Number2Property); set => SetValue(Number2Property, value); }
Then, in the constructor, listen for changes:
public MyView ()
{
InitializeComponent();
PropertyChanged += MyView_PropertyChanged;
}
And finally, make sure that changes to either one is propagated to the other, where Number2 = Number1 + 1:
private void MyView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == Number1Property.PropertyName)
{
if (Number1 + 1 != Number2)
Number2 = Number1 + 1;
}
if (e.PropertyName == Number2Property.PropertyName)
{
if (Number1 + 1 != Number2)
Number1 = Number2 - 1;
}
}
EDIT As noted by #Knoop, the OP wanted the values to be related, not exactly the same.

GetValue and SetValue vs INotifyPropertyChanged.PropertyChanged when creating BindableProprties?

I usually extend controls by creating bindable properties, in this form:
public static readonly BindableProperty OnTextProperty = BindableProperty.Create(nameof(OnText),
typeof(string), typeof(TextSwitch), defaultValue: string.Empty, defaultBindingMode: BindingMode.TwoWay,
propertyChanged: HandleOnTextPropertyChanged);
private static void HandleOnTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as TextSwitch)?.Rebuild();
}
public string OnText
{
get { return (string)GetValue(OnTextProperty); }
set { SetValue(OnTextProperty, value); }
}
for me, since I did some WPF, bindable properties consists of two parts: the static readonly BindableProperty field, and a corresponding property with GetValue in getter and SetValue in setter. But I stumbled over this: https://github.com/adamped/NavigationMenu/blob/master/NavigationMenu/NavigationMenu/NavigationItem.xaml.cs
which just fire the PropertyChanged event :
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(NavigationItem),
string.Empty,
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (NavigationItem)bindable;
ctrl.Text = (string)newValue;
},
defaultBindingMode: BindingMode.OneWay);
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged();
}
}
How can this incorporate with the bindable properties to get it working without GetValue and SetValue?, and in which cases we need to use one approach over another?
EDIT
Apparently I'm not accustomed to the notion of the self binding reusable control.. but isn't calling GetValue and SetValue essential for binding properties?
These two implementations are the same.
GetValue(BindableProperty) and SetValue are used to access the values of properties that are implemented by a BindableProperty. That is, application developers typically provide an interface for a bound property by defining public property whose get accessor casts the result of GetValue(BindableProperty) to the appropriate type and returns it, and whose set accessor uses SetValue to set the value on the correct property.
Your achievement
private static void HandleOnTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as TextSwitch)?.Rebuild();
}
is same with
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (NavigationItem)bindable;
ctrl.Text = (string)newValue;
}
Used
OnPropertyChanged(); in setValuemethod

BindableProperty: Type Mismatch

I want to extend the Xamarin.Forms Picker so I can bind a collection to it. After a quick search I found this page: Picker Example with two great examples. Refusing to just copy&paste the code (for learning purposes) I went on and make my own based on the two examples.
It's almost identical except mine does not work.
When I do not provide a collection to the ItemsSource everything works fine. Whenever I do assign a collection I get the following error:
Xamarin.Forms.Xaml.XamlParseException: Position 9:32. Cannot assign property "ItemsSource": type mismatch between "Xamarin.Forms.Binding" and "System.Collections.IEnumerable"
Picker:
public class BindablePicker : Picker
{
private static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);
private static readonly BindableProperty SelectedItemProperty =
BindableProperty.Create("SelectedItem", typeof(object), typeof(BindablePicker));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public string DisplayMember { get; set; }
private static void OnItemsSourceChanged(BindableObject bindable, Object oldValue, Object newValue)
{
var newval = newValue as IEnumerable; //Had to implement this because of the non-generic .Create() method expects Object as param.
var picker = bindable as BindablePicker;
if (picker != null)
{
picker.Items.Clear();
if (newval == null) return;
foreach (var item in newval)
{
if (string.IsNullOrEmpty(picker.DisplayMember))
{
picker.Items.Add(item.ToString());
}
else
{
var prop = item.GetType()
.GetRuntimeProperties()
.FirstOrDefault(p => string.Equals(p.Name, picker.DisplayMember, StringComparison.OrdinalIgnoreCase));
picker.Items.Add(prop.GetValue(item).ToString());
}
}
}
}
private void OnSelectedIndexChanged(object sender, EventArgs args)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = ItemsSource.ItemOf(SelectedIndex); //ItemOf is an extension method I made for IEnumerable (has to be tested).
}
}
}
ViewModel (parts):
public class HomePageViewModel : ViewModelBase
{
//In the app this is populated with a List<Person>.
public IEnumerable<Person> People { get; set; }
}
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:TestApp.Controls;assembly=TestApp"
x:Class="TestApp.Views.HomePage">
<ContentPage.Content>
<StackLayout>
<controls:BindablePicker ItemsSource="{Binding People}"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Note that the first example picker from the linked page works with the provided VM/View setup.
I'm also not finished with the picker, I still want to provide TwoWay binding to the SelectedItem property and support for ObservableCollection.
Yours,
At first I thought I was turning mad.
Then I thought something was seriously broken.
But finally I figured it out...
private static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource", typeof(IEnumerable),
typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);
In order for the Xaml parser to see a BindableProperty as such, it has to be public (and static, but you got that part right).
In your case, the Xaml parser doesn't see the BindableProperty, so it fallback to the property, but it doesn't have any way to set the Binding, and as the types doesn't match, you get the exception.
Change your code to
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource", typeof(IEnumerable),
typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);

WPF Dependency Property Error

I am trying to define a dependency property like this:
public static readonly DependencyProperty DependencyPropertyName= DependencyProperty.Register("DepName", typeof(EnumName), typeof(MyWindow1), new FrameworkPropertyMetadata("FrameWorkProperty", FrameworkPropertyMetadataOptions.AffectsRender, Target));
private static void Target(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//some logic here
}
public EnumName DepName
{
get { return (EnumName)GetValue(DependencyPropertyName); }
set { SetValue(DependencyPropertyName, value); }
}
And i get this error, and dont understand why:
{"Default value type does not match type of property 'DepName'."}
The default value type (String) of your Dependency Property does not match the Type of your property DepName (EnumName).
Change the default type in your dependency property and it should work.
public static readonly DependencyProperty DependencyPropertyName= DependencyProperty.Register(
"DepName",
typeof(EnumName),
typeof(MyWindow1),
new FrameworkPropertyMetadata(
EnumName.SomeValue, // this is the defalt value
FrameworkPropertyMetadataOptions.AffectsRender,
Target));

PropertyMetadata to register a DependencyProperty of Type "System.Windows.Point"?

Here is my code :
public class ExoticPoint : DependencyObject
{
public Point PointValue
{
get { return (Point)GetValue(PointValueProperty); }
set { SetValue(PointValueProperty, value); }
}
public static readonly DependencyProperty PointValueProperty =
DependencyProperty.Register("PointValue", typeof(Point), typeof(ExoticPoint), new PropertyMetadata(0));
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
public static readonly DependencyProperty DescriptionProperty =
DependencyProperty.Register("Description", typeof(string), typeof(ExoticPoint), new UIPropertyMetadata(0));
public ExoticPoint()
{
}
public ExoticPoint (string objectName)
: base(objectName)
{
}
}
It compiles but when I want to CreateInstance of my type it crashes with the following error :
{"Default value type does not match type of property 'PointValue'."}
So Im kinda sure the problem is :
new PropertyMetadata(0)
but as far as I understand PropertyMetadata it should work as there is a Point constructor that takes an int as parameter : http://msdn.microsoft.com/en-us/library/bf1b4f2b.aspx
So... what is wrong ?
Yes you are right the problem is with the object passed into PropertyMetada.The parameter will be the default value for your property so it has to match with the type of your dependency property.
In your case PointValue type is Point so you should write new PropertyMetadata(new Point(0)).
Also your Description property which is string: new UIPropertyMetadata("0") or new UIPropertyMetadata("") depending on your needs.

Categories

Resources