MVVMCross Binding decimal to UITextField removes d

2019-03-02 22:28发布

问题:

Binding a decimal to a UITextField does not let you put a "." in, as you type "1.", the push to source strips it out. I get why it happens, MakeSafeValue converts it to a decimal, which gets read back out without the ".", overwriting the Text entered. This seems like a keyup vs onblur issue, which a lot of binding frameworks let you override.

I know I could bind a string on the view model instead, but handling the EditingDidEnd instead of EditingChanged seems better. That way I could intercept ShouldEndEditing to check validity.

Where would I register my binding to override the MvvmCross implementation? I tried adding in my Setup.FillTargetFactories, but it is called before MVXTouchBindingBuilder.FillTargetFactories where the MvvmCross one is.

I tried a different property name, in FillTargetFactories I have:

registry.RegisterPropertyInfoBindingFactory(typeof (UITextFieldTextDidEndTargetBinding), typeof (UITextField), "DidEndText");

but it does not work when I bind.

set.Bind(textField).For("DidEndText").To(vm => vm.GMoney);

looks like MvxPropertyInfoTargetBindingFactory.CreateBinding does not work if the target does not have that property = makes sense. Does this leave me with starting at MvxTargetBinding and creating a custom binding? I hate to recreate most of the code in MvxPropertyInfoTargetBinding.

So, I guess the question(s) are...

  • what is the syntax/location to override the default UITextField binding with mine?
  • how can I specify a different binding for Text with the Bind Syntax?

Thanks in advance.

回答1:

what is the syntax/location to override the default UITextField binding with mine?

If you want to simply replace the existing binding, then you can do this via the RegisterPropertyInfoBindingFactory extension method.

MvvmCross operates a simple 'last registered wins' system.

The easiest place your registration in order that happens last is to place it at the end of InitializeLastChance in your Setup:

 protected override void InitializeLastChance()
 {
     base.InitializeLastChance();

     var factory = base.Resolve<IMvxTargetBindingFactoryRegistry>();
     factory.RegisterPropertyInfoBindingFactory(
                     typeof(MyBindingType),
                     typeof(UITextField),
                     "Text");
 }

There's some more info on order of initialization/setup in https://github.com/slodge/MvvmCross/wiki/Customising-using-App-and-Setup (work in progress).

how can I specify a different binding for Text with the Bind Syntax?

The line:

     registry.RegisterPropertyInfoBindingFactory(
            typeof (UITextFieldTextDidEndTargetBinding), 
            typeof (UITextField), 
            "DidEndText");

doesn't work because it tries to use a property called DidEndText - which doesn't exist.

Two possible ways around this are:

1 Subclass UITextField to provide a real property

   public class MyTextField : UITextField
   {
        // ctors

        public string MySpecialText
        {
            get { return base.Text; }
            set { base.Text = value; }
        }
   }

An example of UITextField subclassing is shown in the N=33 video

2 Or use a non-propertyInfo-based binding

For known properties and event pairs this is pretty simple to do:

public class MySpecialBinding : MvxTargetBinding
{
    public MySpecialBinding (UIEditField edit)
        : base(edit)
    {
        edit.SpecialEvent += Handler;
    }

    protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            var text = (UITextField)Target;
            if (text != null)
            {
                text.SpecialEvent -= Handler;
            }
        }
        base.Dispose(isDisposing);
    }

    protected override void Handler(object s, EventArgs e)
    {
        var text = (UITextField)target;
        if (text == null)
            return;
        FireValueChanged(text.Text);
    }

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

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

    public override void SetValue(object value)
    {
        var text = (UITextField)target;
        if (text == null)
            return;
        text.Text = value;
    }
}

This special binding can be registered using RegisterCustomBindingFactory

registry.RegisterCustomBindingFactory<UITextField>(
        "FooFlipper", 
        textField => new MySpecialBinding(textField));

After which bindings like:

set.Bind(textField).For("FooFlipper").To(vm => vm.GMoney);

will work.


As well as your 'use a string in the ViewModel' suggestion, another possible workaround for this double-text conversion, might be to use a two-way value converter. If your UI is handling money, this could insist on displaying decimal places for all values.