Bind viewmodel property to property in custom view - xaml

I created a custom view (NavProgressbar) which has a step property.
private static readonly BindableProperty ProgressStepProperty = BindableProperty.Create(
nameof(ProgressStep), typeof(int), typeof(NavProgressbar),
0, BindingMode.TwoWay, propertyChanged: ProgressStepPropertyChanged);
private static void ProgressStepPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
//update view, removed for brevity
}
public int ProgressStep
{
get => (int)GetValue(ProgressStepProperty);
set => SetValue(ProgressStepProperty, value);
}
In my MvxContentPage I can use it like this by setting the value of ProgressStep
<npb:NavProgressbar
x:Name="NavProgressBar"
ProgressStep="2"/>
That works so far. Now I want to set it from my viewmodel, so in my viewmodel I created a property...
private int _progressStep;
public int ProgressStep
{
get => _progressStep;
set => SetProperty(ref _progressStep, value);
}
...and in my MvxContentPage instead of the fixed value I bind to my viewmodel property by doing
<npb:NavProgressbar
x:Name="NavProgressBar"
ProgressStep="{Binding ProgressStep}"/>
But it does not work. Other bindings to buttons and labels etc work fine.
Where is my mistake?
Edit: In my MvxContentPage which has the NavProgressbar I set
xmlns:viewModels="clr-namespace:x.y.z.ViewModels;assembly=myAssembly"
x:TypeArguments="viewModels:myViewModel"
x:DataType="viewModels:myViewModel"
and Resharper shows in the binding
ProgressStep="{Binding path={myViewModel}.ProgressStep}"
so I think the binding context is setup correctly.
Maybe it's also important that the view and viewmodel are abstract and I am using subclasses of this abstract view and viewmodel?
Other bindings work as expected, e.g. for a button Resharper shows
<Button
Text="{Binding path={myViewModel}.ButtonText}"

You need to set the BindingContext to point at the ViewModel.
When using {Binding _____} to connect a C# property, the XAML needs to know what it's binding to. By default, it will bind to the code behind file associated with it. The following might work for you (double check the namespaces are correct):
<npb:NavProgressbar
x:Name="NavProgressBar"
ProgressStep="{Binding ProgressStep}"
<npb:NavProgressbar.BindingContext>
<npb:NavProgressBarViewModel />
</npb:NavProgressbar.BindingContext>
/>
This page from Microsoft has some good examples on how BindingContext works:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

According to the code you provided, everything is right. So if you make sure the property will call the property changed event in the viewmodel, you can check the similar case which has an example show how to bind the bindableproperty in the custom view to the viewmodel.
Case link:Get value of a BindableProperty used in ViewModel in control?

So, the problem was that Resharper suggested to make this property private
private static readonly BindableProperty ProgressStepProperty =
BindableProperty.Create(nameof(ProgressStep), typeof(int), typeof(NavProgressbar), 0, BindingMode.TwoWay, propertyChanged: ProgressStepPropertyChanged);
which I did, but it has to be public
public static readonly BindableProperty ProgressStepProperty =
BindableProperty.Create(nameof(ProgressStep), typeof(int), typeof(NavProgressbar), 0, BindingMode.TwoWay, propertyChanged: ProgressStepPropertyChanged);

Related

While Binding Collection of Colors System.ArgumentException is occuring

I have a custom view in my PCL project in Xamarin.Forms. I am not able bind a collection of colors in Xaml to a bindable object in my CustomView.
I have set the binding as in below in xaml:
<local:CustomView x:Name="customView" ColorPalette="{Binding Colors}"/>
My CustomView is as below:
public class CustomView : View
{
public CustomView()
{
}
public static void OnColorsChanged(BindableObject bindable, object oldValue, object newValue)
{
// Some Code
}
public static readonly BindableProperty ColorsPaletteProperty =
BindableProperty.Create("ColorPalette", typeof(IEnumerable<Color>), typeof(CustomView), new List<Color>(){ Color.FromRgb(0, 0, 0),
Color.FromRgb(251, 176, 59)}, BindingMode.Default, null, OnColorsChanged);
public IEnumerable<Color> ColorPalette
{
get { return (IEnumerable<Color>)GetValue(ColorsPaletteProperty); }
set { SetValue(ColorsPaletteProperty, value); }
}
}
While performing the binding in Xaml, I get an exception "System.ArgumentException: Object of type 'Xamarin.Forms.Binding' cannot be converted to type 'Xamarin.Forms.Color'".
But when I bind the Colors in using SetBinding in code behind it is working properly.
Code Behind:
//Binding using SetBinding is working where as {Binding Colors} in xaml is not working
customView.SetBinding<ViewModel>(CustomView.ColorsPaletteProperty, vm => vm.Colors);
Colors is a collection of colors of type IEnumerable / IList / List / ObservableCollection.
Any help is appreciated.
Regards,
Nitish
A custom view embedded in another view will inherit the binding context of the parent view. Are you setting the binding context of the view to a viewmodel that has a Colors property? Some more code samples of where you're setting the binding context and of your viewmodel might be helpful.
Your property name is ColorPalette but your bindable property name has not followed the standards, it should be like "ColorPaletteProperty" not "ColorsPaletteProperty" you have added an "s" to the "Color" which is wrong. So can you remove that and check? it should work, let me know if this helps.
Thanks,
Michael

Ignore the Binding initialization

The inital problem is coming from a personal project about the polyline of the Xamarin.Forms.Map where the initialization is realized by a binding from the XAML part..
Let me be clear by an example :
I have an object CustomMap.cs which inherit from Xamarin.Forms.Map (This file is in the PCL part -> CustomControl/CustomMap.cs)
public class CustomMap : Map, INotifyPropertyChanged
{
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), null);
public List<string> PolylineAddressPoints
{
get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
set
{
SetValue(PolylineAddressPointsProperty, value);
this.GeneratePolylineCoordinatesInner();
}
}
// ...
}
As you can see, I have a bindable property with an assessor and the XAML doesn't seem to use this assessor..
So the MainPge.xaml part of the page, where the control is called, looks like that:
<?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:control="clr-namespace:MapPolylineProject.CustomControl;assembly=MapPolylineProject"
x:Class="MapPolylineProject.Page.MainPage">
<ContentPage.Content>
<control:CustomMap x:Name="MapTest" PolylineAddressPoints="{Binding AddressPointList}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
The MainPge.xaml.cs part:
public partial class MainPage : ContentPage
{
public List<string> AddressPointList { get; set; }
public MainPage()
{
base.BindingContext = this;
AddressPointList = new List<string>()
{
"72230 Ruaudin, France",
"72100 Le Mans, France",
"77500 Chelles, France"
};
InitializeComponent();
//MapTest.PolylineAddressPoints = AddressPointList;
}
}
So, everything is fine if I edit the PolylineAddressPoints from the object instance (if the commented part isnt' commented..), but if I init the value from the XAML (from the InitializeComponent();), it doesn't work, the SetValue, in the Set {}, of the CustomMap.PolylineAddressPoints, isn't called..
I then searched on the web about it and get something about the Dependency Properties? or something like that. So I tried some solutions but, from WPF, so some methods, such as DependencyProperty.Register();. So yeah, I can't find the way to solve my problem..
I also though about something, if DependencyProperty.Register(); would exists in Xamarin.Forms, then it means I would have to do it for each values? Because, if every value has to be set by a XAML binding logic, it would not work, I would have to register every value, doesn't it?
I'm sorry if I'm not clear, but I'm so lost about this problem.. Please, do not hesitate to ask for more details, thank in advance !
Finaly, the initial problem is that I'm trying to set a value of an object/control, from the XAML. Doing this by a Binding doesn't work, it seems like it ignored.. However, it does work if I do the following:
MapTest.PolylineAddressPoints = AddressPointList;
There are multiple questions in this:
Why is the property setter never called when using Xaml ?
Am I properly defining my BindableProperty ?
Why is my binding failing ?
Let me answer them in a different order.
Am I properly defining my BindableProperty ?
The BindableProperty declaration is right, but could be improved by using an IList<string>:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null);
but the property accessor is wrong, and should only contains this:
public IList<string> PolylineAddressPoints
{
get { return (IList<string>)GetValue(PolylineAddressPointsProperty); }
set { SetValue(PolylineAddressPointsProperty, value); }
}
I'll tell you why while answering the next question. But you want to invoke a method when the property has changed. In order to do that, you have to reference a propertyChanged delegate to CreateBindableProperty, like this:
public static readonly BindableProperty PolylineAddressPointsProperty =
BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null,
propertyChanged: OnPolyLineAddressPointsPropertyChanged);
And you have to declare that method too:
static void OnPolyLineAddressPointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
((CustomMap)bindable).OnPolyLineAddressPointsPropertyChanged((IList<string>)oldValue, (IList<string>)newValue);
}
void OnPolyLineAddressPointsPropertyChanged(IList<string> oldValue, IList<string> newValue)
{
GeneratePolylineCoordinatesInner();
}
Why is the property setter never called when using Xaml ?
The property, and the property accessors, are only meant to be invoked when accessing the property by code. C# code.
When setting a property with a BindablePrperty backing store from Xaml, the property accessors are bypassed and SetValue() is used directly.
When defining a Binding, both from code or from Xaml, property accessors are again bypassed and SetValue() is used when the property needs to be modified. And when SetValue() is invoked, the propertyChanged delegate is executed after the property has changed (to be complete here, propertyChanging is invoked before the property change).
You might wonder why bother defining the property if the bindable property is only used by xaml, or used in the context of Binding. Well, I said the property accessors weren't invoked, but they are used in the context of Xaml and XamlC:
a [TypeConverter] attribute can be defined on the property, and will be used
with XamlC on, the property signature can be used to infer, at compile time, the Type of the BindableProperty.
So it's a good habit to always declare property accessors for public BindableProperties. ALWAYS.
Why is my binding failing ?
As you're using CustomMap as both View and ViewModel (I won't tell the Mvvm Police), doing this in your constructor should be enough:
BindingContext = this; //no need to prefix it with base.
As you're doing it already, your Binding should work once you've modified the BindableProperty declaration in the way I explained earlier.

OnPropertyChanged is being called twice for a bindable property

In Xamarin Forms, I created a bindable property like so:
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem));
public MyItem SelectedItem
{
get { return (MyItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Here's my constructor:
public MyView()
{
InitializeComponent();
PropertyChanged += OnPropertyChanged;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == "SelectedItem")
{
// called twice
}
}
Can somebody explain why property changed event is firing twice? If I create a changed handler in the definition of the bindable property, then the handler is called once.
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem), null, SelectedItemChanged);
I have noticed the issue exists only in code-behind. If I set the property directly in XAML, property changed event fires once.
We don't really have enough information to answer this question with certainty, but I can speculate.
Without seeing your SetValue method, my assumption would be that it lacks a short circuit, e.g. "don't do anything if the new value and the old value are the same".
Then my second assumption would be that the control that is being bound to this property is setting it (after being bound). This can happen with list-type controls when SelectedItem is bound.
The resulting chain of events might be something like:
Code sets property
PropertyChanged event is fired
Binding sets the value on a control
Control reports it's value has been changed, and two-way binding sets the value on the ViewModel again
The lack of a short circuit causes the PropertyChanged event to be raised again
The binding sets the value on the control again (to the same value as before)
The control does not report a change, because it's property is short-circuited properly
My guess is that if you were to short circuit your setter (by checking against the existing value and bailing out if they are the same) this behavior would stop.

Xamarin.Forms. Custom Property Binding issue

I created custom controls in Xamarin. One of it is ValidatedEntryCell:
public class ValidatedEntryCell: EntryCell
{
public static readonly BindableProperty ShowBorderProperty = BindableProperty.Create(nameof(ShowBorder), typeof(bool), typeof(ValidatedEntryCell), false);
public bool ShowBorder
{
get { return (bool)GetValue(ShowBorderProperty); }
set
{
SetValue(ShowBorderProperty, value);
OnPropertyChanged();
}
}
}
I want to change that ShowBorder property in case if Text is empty. I tried several cases how to invoke that property:
Invoking that property straight forward in xaml. Smth like: renderers:ValidatedPicker x:Name="Gender" Title="Gender" ShowBorder="True"
That case works fine.
<renderers:ValidatedEntryCell ShowBorder="{Binding FirstNameIsNotValid}" x:Name="FirstName"... (I setup FirstNameIsNotValid = true in codebehind)
I used converters <renderers:ValidatedEntryCell x:Name="LastName" ShowBorder="{Binding Source={x:Reference LastName}, Converter={StaticResource EmptyTextToBoolConverter}, Path=Text.Length}" that always return true
Tried to use style triggers for property.
Unfortunately anything helps here. Will be glad for any advice
Edit: partially helps that link. For now I can setup property in constructor in codebehind and changes made will be visible.

VisualStateManager and MVVM

I'm working on a Windows 8 Store App (using the Grid App Template) and while I'm loading data from a server I want to show a ProgressRing and hide the GridView or ListView (depends on if the app is snapped or not) that will display the data once it is fully loaded.
The issue is that when the ViewModel is loading data I need to be able to change the VisualState.
I found what I thought was a solution Here, but this code will not build.
public class StateManager : DependencyObject
{
public static string GetVisualStateProperty(DependencyObject obj)
{
return (string)obj.GetValue(VisualStatePropertyProperty);
}
public static void SetVisualStateProperty(DependencyObject obj, string value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
public static readonly DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(StateManager),
new PropertyMetadata((s, e) => //this throws the error
{
var propertyName = (string)e.NewValue;
var ctrl = s as Control;
if (ctrl == null)
throw new InvalidOperationException("This attached property only supports types derived from Control.");
System.Windows.VisualStateManager.GoToState(ctrl, (string)e.NewValue, true);
}));
}
ERROR: Cannot convert lambda expression to type 'object' because it is
not a delegate type
Does anyone know how to get the linked solution to work? Or is there a simpler method that I am completely missing (I'm a XAML newbie!)?
I'm not even sure if the listed solution will work because the "Snapped" vs "Full" states are managed by the base LayoutAwarePage class included with the template.
why not simply use a datatrigger bind to a viewmodel property like IsBusy {get;set;} to enable your Progressring?