关于Xamarin.Forms中MVVM命令绑定时状态更新机制

最近研究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中的思路,还得多动手多研究。

上一篇:事实表明糟糕的数据将会扼杀优秀的人工智能


下一篇:Keysight Technologies CIO:科技公司也需要实现IT现代化