无法使用 Xamarin-iOS 和 ReactiveUI 在 ViewModel 中绑定 UIButton 和 UITextField
Cant Bind UIButton and UITextField in ViewModel with Xamarin-iOS and ReactiveUI
我有 LoginViewController class
// 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 class
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 始终 returns null.
问题出在哪里?
如果有人能给我一些建议,我将非常高兴。
我对 MVVM 和 ReactiveUi 有点陌生。
谢谢。
几点建议:
您的视图模型不应引用任何 platform/view 相关内容(删除 UITextField 和 UIButton 成员)。视图模型旨在独立于平台,因此它们可以重复使用和测试。
使用ReactiveCommands。他们自动处理 enabling/disabling 按钮。如果您查看此文档 link,您会发现本质上完全相同的示例 code/scenario.
使用 ReactiveViewController 的 generic version 这样您就不必担心自己实现 ViewModel 属性(您的版本应该使用 RaiseAndSetIfChanged,正如您将在 link).
...
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, along with the corresponding sample project to help you get headed in the right direction. And I highly recommend the book, "You, I, and ReactiveUI"。希望这对您有所帮助。
好的朋友,如果我没理解错的话,你可以这样做:
在您的 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));
});
}
您的视图和视图模型通过命令绑定进行交互。
希望对您有所帮助。
我有 LoginViewController class
// 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 class
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 始终 returns null.
问题出在哪里?
如果有人能给我一些建议,我将非常高兴。 我对 MVVM 和 ReactiveUi 有点陌生。
谢谢。
几点建议:
您的视图模型不应引用任何 platform/view 相关内容(删除 UITextField 和 UIButton 成员)。视图模型旨在独立于平台,因此它们可以重复使用和测试。
使用ReactiveCommands。他们自动处理 enabling/disabling 按钮。如果您查看此文档 link,您会发现本质上完全相同的示例 code/scenario.
使用 ReactiveViewController 的 generic version 这样您就不必担心自己实现 ViewModel 属性(您的版本应该使用 RaiseAndSetIfChanged,正如您将在 link).
...
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, along with the corresponding sample project to help you get headed in the right direction. And I highly recommend the book, "You, I, and ReactiveUI"。希望这对您有所帮助。
好的朋友,如果我没理解错的话,你可以这样做:
在您的 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));
});
}
您的视图和视图模型通过命令绑定进行交互。
希望对您有所帮助。