Xamarin.Forms Bindable Properties - xamarin.forms

It may sound like a dumb question but why are bindable properties static?
public static readonly BindableProperty MapSpanProperty = BindableProperty.Create
(
propertyName: "MapSpan",
returnType: typeof(MapSpan),
declaringType: typeof(BindableMap),
defaultValue: null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MapSpanPropertyChanged
);
public MapSpan MapSpan
{
get
{
return (MapSpan)GetValue(MapSpanProperty);
}
set
{
SetValue(MapSpanProperty, value);
}
}
I have this code and it works just fine if I make the bindable property static, otherwise, it doesn't work. If I make this bindable property static it means, let's say if I have 2 maps opened at the same time, that this value will be the same on both if I set it on one of them?

Each bindable property has a corresponding public static readonly field of type BindableProperty that is exposed on the same class and that is the identifier of the bindable property.
You could check the source code of the control which provide the binable property.
I use the contentview for example. The code is from the link below. Xamarin forms update viewmodel field from a bindable property
public partial class MyCustomControl : ContentView
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged();
}
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(MyCustomControl),
string.Empty,
propertyChanged: (bindable, oldValue, newValue) =>
{
var control = bindable as MyCustomControl;
//var changingFrom = oldValue as string;
//var changingTo = newValue as string;
control.Title.Text = newValue.ToString();
});
public MyCustomControl()
{
InitializeComponent();
}
The ContentView provide the public static readonly BindableProperty.
//
// Summary:
// An element that contains a single child element.
//
// Remarks:
// The following example shows how to construct a new ContentView with a Label inside.
[ContentProperty("Content")]
public class ContentView : TemplatedView
{
//
// Summary:
// Backing store for the Xamarin.Forms.ContentView.Content property..
//
// Remarks:
// To be added.
public static readonly BindableProperty ContentProperty;
public ContentView();
//
// Summary:
// Gets or sets the content of the ContentView.
public View Content { get; set; }
//
// Summary:
// Method that is called when the binding context changes.
//
// Remarks:
// To be added.
protected override void OnBindingContextChanged();
}
You could check the MS docs about more for the static in binable property. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/bindable-properties

Related

Xamarin.Forms ScrollView.ScrollToAsync inside DataTemplate

I have ScrollView inside of a DataTemplate of a ListView. I have a need to horizontally auto scroll the scrollview for each item (to a different point determined by a value inside of my ViewModel which the ListItemViews are bound to).
To my knowledge there's no way to bind the scroll position. How can I call the ScrollView.ScrollToAsync method on the ScrollView inside the DataTemplate?
Thanks!
You can try to create a bindableProperty of ScrollView and bind your scroll value to this property, call the ScrollToAsync method in propertyChanged event:
public class CustomScrollView : ScrollView
{
public static readonly BindableProperty offSetProperty = BindableProperty.Create(
propertyName: nameof(offSet),
returnType: typeof(int),
declaringType: typeof(CustomScrollView),
defaultValue: 0,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ScrollOffsetChanged
);
static void ScrollOffsetChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (CustomScrollView)bindable;//here you should check if the bindable is CustomScrollView, I don't see your xaml
view.offSet = (int)newValue;
view.ScrollToAsync(view.offSet, 0, true);
}
public int offSet
{
get { return (int)GetValue(offSetProperty); }
set { SetValue(offSetProperty, value); }
}
}

bindableProperty can not get value?

I created a bindableproperty in a customview but it can not get value.
here is the code:
public string BackgroundImage
{
get
{
var image = (image)GetValue(BackgroundImageProperty);
return image;
}
set => SetValue(BackgroundImageProperty, value);
}
the image is null.
public static readonly BindableProperty BackgroundImageProperty = BindableProperty.Create(nameof(BackgroundImage), typeof(string), typeof(MyView), null);
the xaml code:
<local:MyView BackgroundImage="back.png" />
I do want give a string value and give it to a Image.
I have give the BackgroundImage a string value in the xaml.
Solution1:
You can get the value from the event propertyChanged
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(MyView),
null,
propertyChanged: (bindable, oldValue, newValue) =>
{
var value = newValue;
// do some thing you want .
}
);
Solution 2:
You can set the BindingContext in CustomView .
public MyView
{
//...
BindingContext = this;
//...
}

Xamarin Forms Entry Return Type not working as expected

I tried setting the new feature in Xamarin Forms 3 which is ReturnType and I have set it to Next. My form has multiple fields and I want to make that the next Entry is focused when the Next button is pressed. However it just closes the keyboard. I did read the documents however I could not find the way to focus it to the next Entry. Can someone please guide?
Thanks in advance.
Those who want to know how I implemented it, it is as follows:
I created a behavior which will handle the OnAttachedTo and OnDetachingFrom so that I can handle the Completed event to move the focus. Now for that, I need a BindableProperty. I created the following code out of the logic:
public class NextEntryBehavior : Behavior<Entry>
{
public static readonly BindableProperty NextEntryProperty = BindableProperty.Create(nameof(NextEntry), typeof(Entry), typeof(Entry), defaultBindingMode: BindingMode.OneTime, defaultValue: null);
public Entry NextEntry
{
get => (Entry)GetValue(NextEntryProperty);
set => SetValue(NextEntryProperty, value);
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.Completed += Bindable_Completed;
base.OnAttachedTo(bindable);
}
private void Bindable_Completed(object sender, EventArgs e)
{
if (NextEntry != null)
{
NextEntry.Focus();
}
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.Completed -= Bindable_Completed;
base.OnDetachingFrom(bindable);
}
}
As you can see, there is a NextEntry property, we use it via XAML to focus on the desired entry field once the user marks it as complete using the Next button.
XAML:
<Entry ReturnType="Next">
<Entry.Behaviors>
<behaviors:NextEntryBehavior NextEntry="{x:Reference LastName}" />
</Entry.Behaviors>
</Entry>
In the above behavior, the LastName reference I used is the control to which the focus should go when the user taps on Next.
This way, it worked as expected and is reusable across the project.
the property ReturnType for Entry will only set graphically the Return Key in Keyboard to the specified type - Next in your case.
In order to set Focus for another Entry in the view you need to call Focus() from within the targeted Entry in the code-behind.
For Example:
private void txtUsername_OnCompleted(object sender, EventArgs e)
{
txtPassword.Focus();
}
if you're applying MVVM pattern. You will need a property in the Entry to Bind on for ViewModel property. One way to achieve this is by extending Entry control to add a bindable property called "IsActive" and create a Trigger that listens for changes on this property and calls Focus(), like below:
public class ExtendedEntry : Entry
{
public static readonly BindableProperty IsActiveProperty = BindableProperty.Create(
nameof(IsActive),
typeof(bool),
typeof(ExtendedEntry),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: false);
public bool IsActive
{
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
private Trigger _isActiveTriger;
private EntryIsActiveAction _activeAction;
public ExtendedEntry()
{
InitTriggers();
}
private void InitTriggers()
{
InitIsActiveTrigger();
}
private void InitIsActiveTrigger()
{
_activeAction = new EntryIsActiveAction();
_isActiveTriger = new Trigger(typeof(ExtendedEntry))
{
Value = false,
Property = IsActiveProperty,
EnterActions = { _activeAction },
ExitActions = { _activeAction }
};
Triggers.Add(_isActiveTriger);
}
}
public class EntryIsActiveAction : TriggerAction<ExtendedEntry>
{
protected override void Invoke(ExtendedEntry sender)
{
if (sender.IsActive)
{
sender.Focus();
return;
}
sender.Unfocus();
}
}
Example Usage:
Xaml page:
<Entry x:Name="txtPassword" IsActive="{Binding IsPasswordActive}" />
ViewModel:
private bool _isPasswordActive;
public bool IsPasswordActive
{
get => _isPasswordActive;
set
{
_isPasswordActive = value;
OnPropertyChanged();
}
}

Adding Clicked as a bindable callback property to a custom Xamarin.Forms control?

Below is how I'm defining the property, it works for other types of property like string or bool but it won't work for the type Command:
public static readonly BindableProperty ClickedProperty = BindableProperty.Create(
nameof(Clicked),
typeof(Command),
typeof(MyCustomControl),
default(Command),
BindingMode.OneWay,
propertyChanging: (bindable, oldValue, newValue) => {
var control = bindable as MyCustomControl;
if (!control.GestureRecognizers.Contains(gesture))
{
control.GestureRecognizers.Add(gesture);
}
}
);
public Command Clicked
{
get { return (Command)GetValue(ClickedProperty); }
set { SetValue(ClickedProperty, value); }
}
I tried searching for how it's done for the class Xamarin.Forms.Button but it seems to use the IButtonController interface which is not for public use.
Update
I changed the property to event type, now the accessors are called when I bind the property from a XAML form, but neither propertyChanged nor propertyChanging is called.
public event EventHandler Clicked
{
add
{
lock (gesture)
{
gesture.Tapped += value;
}
}
remove
{
lock (gesture)
{
gesture.Tapped -= value;
}
}
}

BindableProperty getter returns null

Below is the BindableProperty definition on a custom control:
public static BindableProperty ItemsSourceProperty =
BindableProperty.Create<NodeListView, IEnumerable<Node>> (ctrl =>
ctrl.ItemsSource,defaultValue: null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanging: (bindable, oldValue, newValue) => {
var ctrl = (NodeListView)bindable;
ctrl.ItemsSource = newValue;
});
public IEnumerable<Node> ItemsSource {
get {
var itemsSource = (IEnumerable<Node>)GetValue (ItemsSourceProperty);
return itemsSource;
}
set {
SetValue (ItemsSourceProperty, value);
BindNodeIcons ();
}
}
When I set the BindingContext on the control I can see the newValue in propertyChanging is set to a correct object. Also in the setter of ItemsSource property the value variable takes the correct value. In the BindNodeIcons() method, I access ItemsSource property and it returns null. I can't see anything wrong in the code but still.
Try the following:
public class NodeListView : BindableObject
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable<Node>), typeof(NodeListView), propertyChanged: OnItemSourceChanged);
public IEnumerable<Node> ItemsSource
{
get { return (IEnumerable<Node>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var nodeListView = bindable as NodeListView;
nodeListView.BindNodeIcons();
}
}

Resources