MvvmCross Monotouch C# - Binding Int Property - Mo

2020-04-11 05:18发布

I am new to MvvmCross and I have a question.

I noticed that the following binding code works in one way only:

{ this, "{'CurrentIndex':{'Path':'CurrentIndex','Mode':'TwoWay'}}" }
  • CurrentIndex is an Int Property in the View
  • CurrentIndex is also an Int Property in the ViewModel

This way works!

  • ViewModel => View

But not this way!

  • View => ViewModel

I have a collection of ViewControllers and my goal was to call a DeleteCommand for the CurrentIndex in the viewModel.

However,

"Android and Touch 2 way bindings are incomplete"

Reference: MvvmCross experiences, hindsight, limitations?

My guess is the TwoWay mode only works for Controls (UILabel, UITextfield, ...) but not for Properties.

So, is there a good way to make it works in both ways? Or Are there any alternatives to my problem?

Patrick

1条回答
乱世女痞
2楼-- · 2020-04-11 05:55

In order for a binding to transfer any value between a View to a ViewModel, then it needs to hook into some event when the value changes.

In the ViewModel, this event is always the event in the INotifyProperty interface.

In the View/Activity, there is one single pattern employed - so each binding has to hook into a separate event. For example, the Text on EditText is hooked up using the TextChanged event (see MvxEditTextTextTargetBinding.cs) while the value in a SeekBar is hooked up using a Listener object rather than an event (see MvxSeekBarProgressTargetBinging.cs).

So if you wanted to implement this two-way binding for your activity, then you could do this by:

  • declaring an event - CurrentIndexChanged - in your activity (MyActivity) which is fired whenever CurrentIndex changes
  • declare a custom binding for your MyActivity which programmatically links CurrentIndex and CurrentIndexChanged
  • adding the custom binding to the binding registry during Setup

For example, your activity might include:

public event EventHandler CurrentIndexChanged;

private int _currentIndex;
public int CurrentIndex
{ 
   get { return _currentIndex; } 
   set { _currentIndex = value; if (CurrentIndexChanged != null) CurrentIndexChanged(this, EventArgs.Empty); } 
}

And you might then declare a binding class like:

public class MyBinding : MvxPropertyInfoTargetBinding<MyActivity>
{        
    public MyBinding (object target, PropertyInfo targetPropertyInfo) 
        : base(target, targetPropertyInfo)
    {
        View.CurrentIndexChanged += OnCurrentIndexChanged;
    }

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

    private void OnCurrentIndexChanged(object sender, EventArgs ignored)
    {
        FireValueChanged(View.CurrentIndex);
    }

    protected override void Dispose(bool isDisposing)
    {
        base.Dispose(isDisposing);
        if (isDisposing)
        {
            View.CurrentIndexChanged -= OnCurrentIndexChanged;
        }
    }
}

And you'd need to tell the binding system about this binding in setup like:

       registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(MyBinding), typeof(MyActivity), "CurrentIndex"));

However... at a practical level, if you are operating in C# rather than in XML, then you might be better off in this case using C# to simply update the ViewModel rather than using declarative binding in this case.

To be clear... in this case, I would most probably just write the Activity property as:

public int CurrentIndex
{ 
   get { return _currentIndex; } 
   set { _currentIndex = value; ViewModel.CurrentIndex = value; } 
}

Or... I'd consider not having this property in the Activity at all.


If it helps, there's some more information on custom bindings in:


Hope this helps! IMHO the bindings are there to help you when you're working in XML - you don't have to use them...

Stuart


UPDATE If you are going to do lots of these and follow the same name pattern - using property named X with changed EventHandler event named XChanged then something like this might work - it uses reflection to find the event automagically:

public class MyBinding<T> : MvxPropertyInfoTargetBinding<T>
    where T : class
{
    private readonly PropertyInfo _propertyInfo;
    private readonly EventInfo _eventInfo;

    public MyBinding(object target, PropertyInfo targetPropertyInfo)
        : base(target, targetPropertyInfo)
    {
        _propertyInfo = targetPropertyInfo;
        var eventName = _propertyInfo.Name + "Changed";
        _eventInfo = View.GetType().GetEvent(eventName);
        if (_eventInfo == null)
        {
            throw new MvxException("Event missing " + eventName);
        }

        if (_eventInfo.EventHandlerType != typeof(EventHandler))
        {
            throw new MvxException("Event type mismatch for " + eventName);
        }

        var addMethod = _eventInfo.GetAddMethod();
        addMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
    }

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

    private void OnChanged(object sender, EventArgs ignored)
    {
        var value = _propertyInfo.GetValue(View, null);
        FireValueChanged(value);
    }

    protected override void Dispose(bool isDisposing)
    {
        base.Dispose(isDisposing);
        if (isDisposing)
        {
            var removeMethod = _eventInfo.GetRemoveMethod();
            removeMethod.Invoke(View, new object[] { new EventHandler(OnChanged) });
        }
    }
}
查看更多
登录 后发表回答