最近研究Xamarin.Forms,发现它的MVVM的命令绑定很是让我郁闷,和标准WPF也不一样。
我要做一个简单的登录界面,在用户名和密码都输入的时候,才能点击登录,当有一个输入时可以点击清除按钮。XAML文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:MVVMDemo"
x:Class="MVVMDemo.MainPage">
<ContentPage.Resources>
<local:MainPageViewModel x:Key="vm"></local:MainPageViewModel>
<local:ValueConverter x:Key="valuecheck"></local:ValueConverter>
</ContentPage.Resources>
<Grid HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" BindingContext="{StaticResource vm}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="Welcome to Xamarin.Forms!" Grid.ColumnSpan="2"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label Text="UserName:" Grid.Row="1"></Label>
<Label Text="Password:" Grid.Row="2"></Label>
<Button Text="Login" Grid.Row="3" Command="{Binding LoginCommand}">
</Button>
<Entry Text="{Binding UserName,Mode=TwoWay,UpdateSourceEventName=TextChanged}" Grid.Row="1" Grid.Column="1" x:Name="EntryName"></Entry>
<Entry Text="{Binding Password,Mode=TwoWay,UpdateSourceEventName=TextChanged}" Grid.Row="2" Grid.Column="1" x:Name="EntryPass" IsPassword="True"></Entry>
<Button Text="Clear" Grid.Row="3" Grid.Column="1" Command="{Binding ClearCommand}">
</Button>
</Grid>
</ContentPage>
后台的Viewmodel如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Windows.Input;
using Xamarin.Forms;
namespace MVVMDemo
{
public class MainPageViewModel : INotifyPropertyChanged
{
private string _name;
private string _pass;
public string UserName
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(UserName));
}
}
}
public string Password
{
get { return _pass; }
set
{
if (value != _pass)
{
_pass = value;
OnPropertyChanged(nameof(Password));
}
}
}
public Command LoginCommand { get; set; }
public Command ClearCommand { get; set; }
public MainPageViewModel()
{
this.LoginCommand = new Command(DoLogin, () => { return CanDoLogin(); });
this.ClearCommand = new Command(DoClear, () => { return CanDoClear(); });
}
private void LoginCommand_CanExecuteChanged(object sender, EventArgs e)
{
}
private void DoLogin()
{
}
private bool CanDoLogin()
{
return !(string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(Password));
}
private void DoClear()
{
this.UserName = string.Empty;
this.Password = string.Empty;
}
private bool CanDoClear()
{
return !(string.IsNullOrEmpty(UserName) && string.IsNullOrEmpty(Password));
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
internal class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return false;
}
return !string.IsNullOrEmpty(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
不管我在界面怎么输入,按钮的状态就不像我想要的那样(和标准WPF不一样)。通过查看Xamarin.Forms的官方API,发现它的Command实现多了一个ChangeCanExecute方法,官方的说明是(在Command构造函数中):每当 canExecute 返回的值已更改,调用ChangeCanExecute()需要触发CanExecuteChanged。
也就是说需要我们手动告诉按钮(可能是手机的性能比PC要低得多,所以Xamarin.Forms没有实现WPF中的自动轮询吧)。
改造一个我的代码,在Viewmodel的构造函数中添加注册属性变更事件:
public MainPageViewModel()
{
this.LoginCommand = new Command(DoLogin, () => { return CanDoLogin(); });
this.ClearCommand = new Command(DoClear, () => { return CanDoClear(); });
this.PropertyChanged += MainPageViewModel_PropertyChanged;
}
然后在发生发生变化时通知更新按钮的CanExecute状态。代码如下:
private void MainPageViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
LoginCommand.ChangeCanExecute();
ClearCommand.ChangeCanExecute();
}
通过这样的处理,按钮的状态终于按照我的想法发生变化了。
看来学习Xamarin.Forms的MVVM时,并不能完全照搬WPF中的思路,还得多动手多研究。