Xamarin组件BindableProperty无法正确初始化

时间:2018-12-20 22:34:27

标签: c# xaml xamarin xamarin.forms

我有一个带有Required属性的自定义Xamarin Forms组件,该属性在我的视图模型中设置为True。在构造函数中,我调用方法CheckValidity(),该方法检查是否需要该条目。由于某些原因,在我键入条目之前(触发Required属性进行更新),或者单击条目的内部或外部(触发Text事件),Unfocused一直显示为假。

您知道为什么True的初始Required值在组件中发生某些活动后才生效吗?谢谢!

在视图中使用

<ui:ValidatingEntry Text="{Binding MyText}" Required="True" />

组件XAML

 <?xml version="1.0" encoding="UTF-8"?>
 <ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyPackage.ValidatingEntry">
     <ContentView.Content>
         <StackLayout x:Name="entryContainer">

             <Entry x:Name="entry" />

             <Label x:Name="message" />
         </StackLayout>
     </ContentView.Content>
 </ContentView>

组件C#

public partial class ValidatingEntry : ContentView
{
    private enum ValidationErrorType
    {
        NONE,
        REQUIRED,
        CUSTOM
    }

    private ValidationErrorType validationErrorType;

    public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(ValidatingEntry), default(string), BindingMode.TwoWay);

    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }

        set
        {
            SetValue(TextProperty, value);

            Debug.WriteLine("set text to: " + value);

            CheckValidity();
            UpdateMessage();
        }
    }

    public static readonly BindableProperty RequiredProperty = BindableProperty.Create("Required", typeof(bool), typeof(ValidatingEntry), false);

    public bool Required
    {
        get
        {
            Debug.WriteLine("getting required property: " + (bool)GetValue(RequiredProperty));
            return (bool)GetValue(RequiredProperty);
        }

        set
        {
            SetValue(RequiredProperty, value);

            //THIS NEVER PRINTS
            Debug.WriteLine("set required property to: " + value);

            CheckValidity();
        }
    }

    public static readonly BindableProperty IsValidProperty = BindableProperty.Create("IsValid", typeof(bool), typeof(ValidatingEntry), true, BindingMode.OneWayToSource);

    public bool IsValid
    {
        get
        {
            return (bool)GetValue(IsValidProperty);
        }

        set
        {
            SetValue(IsValidProperty, value);
        }
    }

    private void CheckValidity()
    {
        Debug.WriteLine("checking validity");
        Debug.WriteLine("required? " + Required); //prints False until Entry is unfocused or user types in Entry
        Debug.WriteLine("string empty? " + string.IsNullOrEmpty(Text));

        if (Required && string.IsNullOrEmpty(Text))
        {
            Debug.WriteLine("required but not provided");
            IsValid = false;
            validationErrorType = ValidationErrorType.REQUIRED;
        }
        else
        {
            IsValid = true;
            validationErrorType = ValidationErrorType.NONE;
        }
    }

    private void UpdateMessage()
    {
        switch (validationErrorType)
        {
            case ValidationErrorType.NONE:
                message.Text = "";
                break;
            case ValidationErrorType.REQUIRED:
                message.Text = "This field is required.";
                break;
        }
    }

    public ValidatingEntry()
    {
        InitializeComponent();

        entry.SetBinding(Entry.TextProperty, new Binding("Text", source: this));

        CheckValidity(); //at this point, Required is always false

        entry.Unfocused += (sender, e) =>
        {
            CheckValidity();
            UpdateMessage();
        };
    }
}

2 个答案:

答案 0 :(得分:1)

在返回类型的构造函数之前,不会使用XAML中的值更新类型的属性,因此您要在构造函数返回后运行CheckValidity

最简单,最快的方法是启动后台线程以运行CheckValidity,因为这将允许构造方法返回并使用XAML中设置的值填充属性。所以试试这个:

public ValidatingEntry()
{
    InitializeComponent();

    entry.SetBinding(Entry.TextProperty, new Binding("Text", source: this));

    Task.Run(() => { 
        CheckValidity();
        UpdateMessage();
    });

    entry.Unfocused += (sender, e) =>
    {
        CheckValidity();
        UpdateMessage();
    };
}

值得注意的是,这并不是Forms独有的。在默认(无参数)构造函数中,构造函数在运行时仅会设置默认值。因此,如果您希望默认值为true,请在BindableProperty.Create(...)方法调用的Required属性中设置默认值,例如:

public static readonly BindableProperty RequiredProperty = 
              BindableProperty.Create(
                  "Required", 
                  typeof(bool), 
                  typeof(ValidatingEntry), 
                  true);

举个例子,当您这样做时,可能会以为:

 var x = new MyType { MyString = "New text" };

将在构造函数中设置MyString,但这不是事实。上面是语法糖,在编译时更改为:

 var x = new MyType();
 x.MyString = "New text";

因此,构造函数完成,然后设置了属性。

但是,如果您有默认值,例如:

public class MyType
{
    public string MyString { get; set; } = "Default text";
}

MyString将设置为“默认文本”,并在构造函数中可用。

一个示例控制台应用程序来演示:

class MainClass
{
    public static void Main(string[] args)
    {

        var x = new MyType { MyString = "New text" };

        var y = Console.ReadKey();
    }
}

public class MyType
{
    public MyType()
    {
        Console.WriteLine($"Constructor: {MyString}");
        Task.Run(() => Console.WriteLine($"Task: {MyString}"));

    }

    public string MyString { get; set; } = "Default text";
}

输出将是:

  

构造函数:默认文本

     

任务:新文本

答案 1 :(得分:1)

尝试将CheckValidity()放在构造函数完成后引发的事件处理程序中,例如BindingContextChanged

public ValidatingEntry()
{
    InitializeComponent();
    ...
    BindingContextChanged += (sender, e) =>
    {
        CheckValidity();
    };
}