【问题标题】:Cant Bind UIButton and UITextField in ViewModel with Xamarin-iOS and ReactiveUI无法使用 Xamarin-iOS 和 ReactiveUI 在 ViewModel 中绑定 UIButton 和 UITextField
【发布时间】:2018-07-04 17:54:58
【问题描述】:

我有 LoginViewController 类

// This file has been autogenerated from a class added in the UI designer.

using System;
using System.Collections.Generic;
using Foundation;
using UIKit;
using System.Linq;
using System.Reactive;
using ReactiveUI;
using GoBatumi.IOS.ViewModels;

namespace GoBatumi.IOS
{
    public partial class LogInViewController : ReactiveViewController,IViewFor<LoginViewModel>
    {      
        UITapGestureRecognizer SingUpGesture;
        public LogInViewController (IntPtr handle) : base (handle)
        {
            this.WhenActivated(d =>
            {
                d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
                d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
                d(this.Bind(ViewModel, vm => vm.LoginButton, vm => vm.logInButton));
                d(this.Bind(ViewModel, vm => vm.PasswordTextField, vm => vm.passwordTextField));
            });
        }

        private void MakeViewModelBinding(){
        }

        public override void ViewDidLayoutSubviews(){
            base.ViewWillLayoutSubviews();   
        }

        LoginViewModel _viewModel;
        public LoginViewModel ViewModel 
        {
            get => _viewModel ?? new LoginViewModel();
            set => _viewModel = value;
        }
        object IViewFor.ViewModel 
        {
            get => ViewModel;
            set => ViewModel = (LoginViewModel)value; 
        }

        public override void ViewDidLoad(){
            base.ViewDidLoad();
        }

        private void ShouldChangeViewSettings(bool enable){
            passwordTextField.Enabled = enable;
            logInButton.Enabled = enable;         
            if (enable)
                logInButton.Alpha = 0.99f;
            else
                logInButton.Alpha = 0.4f;
        }
    }

    public class TestUser
    {
        public string UserName{
            get;
            set;
        }

        public string Password{
            get;
            set;
        }
    }
}

另外,我有 LoginViewModel 类

using System;
using System.Diagnostics;
using ReactiveUI;
using UIKit;

namespace GoBatumi.IOS.ViewModels
{
    public class LoginViewModel : ReactiveObject
    {
        public LoginViewModel()
        {
        }

        private string _userName;
        public string UserName
        {
            get => _userName;
            set
            {
                this.RaiseAndSetIfChanged(ref _userName, value);
                var result = string.IsNullOrEmpty(_userName);

                ShouldChangeViewSettings(result);
            }
        }


        private string _password;
        public string Password
        {
            get => _password;
            set
            {
                this.RaiseAndSetIfChanged(ref _password, value);
            }    

        }

        private UIButton _loginButton;
        public UIButton LoginButton
        {
            get => _loginButton;
            set => this.RaiseAndSetIfChanged(ref _loginButton, value);
        }

        private UITextField _passwordTextField;
        public UITextField PasswordTextField
        {
            get => _passwordTextField;
            set => this.RaiseAndSetIfChanged(ref _passwordTextField, 
        }

    }
}

我的问题是字符串用户名和字符串密码可以绑定, usernameTextField.Texts 和 passwordTextField.Text

但它 UIButton 和 UITextField 始终为 null,它们没有绑定。

我的任务是,每当用户在 textField 中键入字符时,我必须启用按钮,并且每当用户删除整个文本字段并且字符串为空时,我必须再次禁用按钮,因此我需要 UiButton 来更改背景颜色从 ViewModel 但 UIButtonProperty 总是返回 null。

问题出在哪里?

如果有人能给我一些建议,我会很高兴。 我对 MVVM 和 ReactiveUi 有点陌生。

谢谢。

【问题讨论】:

  • 您的 ViewModel 上不需要 UITextField 和 UIButton。您需要一个 ReactiveCommand,ReactiveCommand 定义了一个名为 CanExecute 的 IObservable 并通过将其与 WhenAnyValue 运算符结合,您可以实现目标
  • 但是我怎样才能从 viewmodel 到 ViewController 的对象,比如那些 button 和 uitextfield 呢?你能写一个小例子吗?

标签: c# xamarin xamarin.ios reactive-programming reactiveui


【解决方案1】:

一些建议:

  • 您的视图模型不应引用任何与平台/视图相关的内容(摆脱 UITextField 和 UIButton 成员)。视图模型旨在独立于平台,因此可以重复使用和测试。

  • 使用ReactiveCommands。他们自动处理启用/禁用按钮。如果您查看此文档链接,您会发现基本上完全相同的示例代码/场景。

  • 使用 ReactiveViewController 的 generic version,这样您就不必担心自己实现 ViewModel 属性(您的版本应该使用 RaiseAndSetIfChanged,您将在链接中看到)。

...

public class LoginViewModel : ReactiveObject
{
    public LoginViewModel()
    {
        var canLogin = this.WhenAnyValue(
            x => x.UserName,
            x => x.Password,
            (userName, password) => !string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password));
        LoginCommand = ReactiveCommand.CreateFromObservable(
            LoginAsync, // A method that returns IObservable<Unit>
            canLogin);
    }

    public ReactiveCommand<Unit, Unit> LoginCommand { get; }

    private string _userName;
    public string UserName
    {
        get { return _userName; }
        set { this.RaiseAndSetIfChanged(ref _userName, value); }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { this.RaiseAndSetIfChanged(ref _password, value); }
    }
}

...

public partial class LogInViewController : ReactiveViewController<LoginViewModel>
{      
    UITapGestureRecognizer SingUpGesture;

    public LogInViewController (IntPtr handle) : base (handle)
    {
        this.WhenActivated(d =>
        {
            d(this.Bind(ViewModel, vm => vm.UserName, v => v.userNameTextField.Text));
            d(this.Bind(ViewModel, vm => vm.Password, v => v.passwordTextField.Text));
            d(this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.logInButton));
        });
    }
}

这是一个heavily documented ViewModel,以及相应的示例项目,可帮助您朝着正确的方向前进。如果你真的想精通,我强烈推荐这本书,“You, I, and ReactiveUI”。希望这会有所帮助。

【讨论】:

  • 对不起我的新手问题,但是我怎样才能让 LoginCommand Observable 可以禁用或启用按钮?
  • LoginCommand 是一个可观察的,所以它会在上面的代码中自动启用/禁用按钮。它根据 canLogin 变量知道何时启用/禁用按钮。我使用的条件是!string.IsNullOrEmpty(userName)!string.IsNullOrEmpty(password)
  • 当我尝试将我的按钮绑定到命令时,会发生此异常。 [无法在 ReactiveUI.ReactiveCommand`2[System.Reactive.Unit,System.Reactive.Unit] 和 UIKit.UIButton 之间进行双向转换。要解决此问题,请注册 IBindingTypeConverter 或使用转换器 Funcs 调用版本。]
  • 在您的示例中,LoginCommand 是只读的,因此我无法从我的视图中绑定它。
  • 如果我会像 loginComand = value 一样为它写设置器,这会导致异常
【解决方案2】:

好的朋友,如果我理解正确,你可以这样做:

在您的 ViewModel 中:

public ReactiveCommand<Unit,Unit> LoginCommand { get; set; }

    public LoginViewModel()
    {
        //this operator does the magic, when UserName and Password be different than empty your button
        //will be enabled
        var canLogin = this.WhenAnyValue(x => x.UserName, x=> x.Password, 
                                        (user,password) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(password));

        LoginCommand = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
        {
            //Your login logic goes here..
            return Unit.Default;
        }, canLogin);

    }

在你的视野中

 public LogInViewController (IntPtr handle) : base (handle)
    {
        this.WhenActivated(d =>
        {
            d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
            d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
            d(this.BindCommand(this.ViewModel,vm => vm.LoginCommand,v => v.LoginButton));
        });
    }

您的视图和视图模型通过命令绑定进行交互。

我希望这对你有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-27
    • 2015-11-05
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多