In Mvvmcross How to Do Custom Bind Properties

In MvvmCross how do I do custom bind properties

There's an example of adding a custom 2-way binding for "IsFavorite" in the Conference sample - see:

  • the binding - https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Droid/Bindings/FavoritesButtonBinding.cs
  • the binding setup in FillTargetFactories in https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Droid/Setup.cs

This example is explained a bit further in: MVVMCross Bindings in Android

For a one-way "source-to-target" custom binding, the code should be a bit simpler - you only need to handle the SetValue - and don't need to invoke FireValueChanged in any event handling code.


For textColor, I'd imagine the binding would look a bit like:

public class MyCustomBinding
: MvxBaseAndroidTargetBinding
{
private readonly TextView _textView;

public MyCustomBinding(TextView textView)
{
_textView = textView;
}

public override void SetValue(object value)
{
var colorValue = (Color)value;
_textView.SetTextColor(colorValue);
}

public override Type TargetType
{
get { return typeof(Color); }
}

public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}

and would be setup with:

    protected override void FillTargetFactories(MvvmCross.Binding.Interfaces.Bindings.Target.Construction.IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);

registry.RegisterFactory(new MvxCustomBindingFactory<TextView>("TextColor", (textView) => new MyCustomBinding(textView)));
}

Note: I've not compiled this example code - when you do get it working, please come back and correct this pseudo-code :)

Mvvmcross Two-way binding between custom properties

Per default MvvmCross can bind any public properties, in OneWay mode. To get TwoWay mode working you need to create at target binding which allows to set from Target to Source. These are called TargetBindings.

Lets say your view has a property called Hello and an event called HelloChanged. With these two in hand you can create a simple TargetBinding:

public class MyViewHelloTargetBinding
: MvxConvertingTargetBinding
{
protected MyView View => Target as MyView;

private bool _subscribed;

public MyViewHelloTargetBinding(MyView target)
: base(target)
{
}

private void HandleHelloChanged(object sender, EventArgs e)
{
var view = View;
if (view == null) return;

FireValueChanged(view.Hello);
}

public override MvxBindingMode DefaultMode = MvxBindingMode.TwoWay;

public override void SubscribeToEvents()
{
var target = View;
if (target == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Error,
"Error - MyView is null in MyViewHelloTargetBinding");
return;
}

target.HelloChanged += HandleHelloChanged;
_subscribed = true;
}

public override Type TargetType => typeof(string);

protected override void SetValueImpl(object target, object value)
{
var view = (MyView)target;
if (view == null) return;

view.Hellp = (string)value;
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (isDisposing)
{
var target = View;
if (target != null && _subscribed)
{
target.HelloChanged -= HandleHelloChanged;
_subscribed = false;
}
}
}
}

Then you just need to register your target binding in your Setup.cs file in FillTargetFactories:

registry.RegisterCustomBindingFactory<MyView>(
"Hello", view => new MyViewHelloTargetBinding(view));

MvvmCross binding property of custom type to custom view

Solved with Stuart's comment in two ways update ViewModel code. First I changed custom property declaration like:

private SearchField _dishField;
public SearchField DishField {
get
{
return _dishField;
}
set
{
_dishField = value;
RaisePropertyChanged (() => DishField);
}
}

Second I initialize my properties in ViewModel constructor before UpdateSearchFields () execution:

public RecipesFiltersVM(IFiltersService filtersService)
{
_filtersService = filtersService;

DishField = new SearchField (new List<Filter> ());
CuisineField = new SearchField (new List<Filter> ());
IngredientField = new SearchField (new List<Filter> ());

UpdateSearchFields ();
}

MvvmCross bind Viewmodel property to view property

The easiest way would be inheriting from MvxActivity<TViewModel>

public class UserProfileView : MvxActivity<UserProfileViewModel>

and then simply set

ViewModel.CurrentToken = account.Properties["access_token"];

This answers your "Is it possible to change viewmodel property from view?". But this doesn't use databinding. If you really want to use data binding, you have to write a custom Binding for it, what may be too much effort in this case.

Binding sub properties MvvmCross

Does mvvmcross natively support binding to nested properties?

Yes

Should this work?

Yes

For example, this line in ApiExamples for Xamarin.iOS is working:

        set.Bind(errorLabel2).To(vm => vm.Errors.Count);

https://github.com/MvvmCross/MvvmCross-Tutorials/blob/master/ApiExamples/ApiExamples.Touch/Views/FirstView.cs#L361

The set of supported functionality is described in https://github.com/MvvmCross/MvvmCross/wiki/Databinding#fluent - but admittedly this fluent binding is more established/used in Xamarin.iOS than it is in Wpf.


To try to debug why your current binding might not be working try adding a simple property to your view which provides debug output

private Visibility _mockVisibility;
public Visibility MockVisibility
{
get
{
return _mockVisibility;
}
set
{
Debug.WriteLine("MockVisibility called with " + value);
_mockVisibility = value;
}
}

and binding this as:

  this.CreateBinding(this)
.For(view=> view.MockVisibility)
.To<MainViewModel>(vm => vm.SubViewModel.ShowIndeterminantProgress)
.WithConversion("Visibility")
.Apply();

This should give you debug/trace output and should give you somewhere to put a breakpoint to understand the call stack a little too (although expect this to contain lots of reflection which can be hard to read through).

Beyond this:

  • you could also try binding a label's text to see what that displays.
  • you can also try setting the binding trace level to Diagnostic (using the static field MvxBindingTrace.TraceBindingLevel https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding/MvxBindingTrace.cs#L14)
  • try isolating the problem piece by piece (isolating the converter, isolating the nested property, etc, etc) - I guess this is what you are already doing in asking this question.

MVVMCross default binding mode used in a project

In MvvmCross the default binding mode is usual Two-Way when using bindings defined by MvvmCross (Virtual property bindings/Custom bindings). Native properties will usual be One-Way as by default there is no return mechanism (View to ViewModel).

Notes from MvvmCross Custom binding:

Where MvvmCross had created new bindings, then this [Two-Way binding] is very often the
default binding mode MvvmCross tries to use.

With the exception being Windows and Xaml:

In Windows/Xaml, this [One-Way binding] is very often the default binding mode - so it
is the mode used when no other is selected.

Swiss binding syntax

, Mode=$WhichMode$

where $WhichMode$ is one of:

  • OneWay
  • OneWay
  • ToSource
  • TwoWay
  • OneTime
  • Default

Example using Android AXML

local:MvxBind="Text UserName, Mode=OneWay"

Fluent binding syntax

Using code base binding you can use:

OneWay()
TwoWay()
OneWayToSource()
OneTime()

Example:

var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(cardLabel)
.For(v => v.Text)
.To(vm => vm.UserName)
.OneWay();
set.Apply();

MvvmCross UITextField custom binding

1. What is special about PropertyInfoTargetBinding?

The question you reference - MVVMCross Binding decimal to UITextField removes decimal point - gives the key to the difference between MvxTargetBinding and MvxPropertyInfoTargetBinding:

  • TargetBinding can be used for any arbitrary binding - e.g. for a non-propertyInfo-based binding
  • PropertyInfoTargetBinding inherits from TargetBinding and can only be used with actual C# Properties - because it uses PropertyInfo via Reflection.

In your case, since you aren't actually using the Text property via Reflection, then I'd be tempted not to use a PropertyInfoTargetBinding and to steer clear of the Text name as well - instead just write a custom TargetBinding.

the former is used when replacing an existing binding

This is definitely not true - instead any binding can be used to replace another binding - as the answer on the other question says

MvvmCross operates a simple 'last registered wins' system

For more on custom bindings, take a look at:

  • the N=28 video in http://mvvmcross.blogspot.co.uk/
  • take a look through some of the "standard" bindings that ship with MvvmCross
    • Droid - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/Target
    • iOS - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Target
    • note that most of these use either MvxPropertyInfoTargetBinding or MvxConvertingTargetBinding as a base class


2. Why is my SetValue getting null?

Your current binding code is asking for an ICommand:

public override Type TargetType
{
get { return typeof(ICommand); }
}

But your View code is currently binding the View to a string:

// View
set.Bind(usernameTextField).To(vm => vm.Username);

// ViewModel
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}

To solve this...

  1. Work out what you want to bind to - is it an ICommand (e.g. and MvxCommand) or is it a string?
  2. Change the View and the Binding to reflect this.


Related Topics



Leave a reply



Submit