【问题标题】:Xamarin Component BindableProperty not initializing properlyXamarin 组件 BindableProperty 未正确初始化
【发布时间】:2018-12-22 00:01:32
【问题描述】:

我有一个带有Required 属性的自定义Xamarin Forms 组件,我在我的视图模型中将其设置为True。在构造函数中,我调用了一个方法CheckValidity(),它检查是否需要该条目。出于某种原因,Required 显示为 false,直到我输入条目(触发 Text 属性更新)或单击进入或退出条目(触发 Unfocused 事件)。

知道为什么 Required 的初始 True 值在我的组件中发生某些活动之前不会生效吗?谢谢!

在视图中使用

<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();
        };
    }
}

【问题讨论】:

    标签: c# xaml xamarin xamarin.forms


    【解决方案1】:

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

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

    【讨论】:

    • 谢谢!这是拼图中缺失的一块!现在可以完美运行,无需添加任意延迟。 :-)
    【解决方案2】:

    在类型的构造函数返回之前,不会使用 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";
    }
    

    输出将是:

    构造函数:默认文本

    任务:新文本

    【讨论】:

    • 哇 - 感谢您提供非常详细和有用的答案!我尝试按照您的建议将CheckValidity() 放入Task.Run() 以在后台线程上运行,但它仍然不起作用,但我在调用CheckValidity() 之前添加了await Task.Delay(1000),现在它似乎可以正常工作。知道为什么我需要额外的延迟吗?我验证了后台线程在构造函数完成后运行,但也许我的新后台线程在设置 XAML 绑定的代码之前仍在运行?
    • 是的,可能存在潜在的竞争条件,因为无法保证任务何时执行。它可能执行得非常快,可能在属性设置之前。您需要多长时间取决于设置类型的属性需要完成多少工作。最好的办法是从使用自定义视图的页面的 OnAppearing 方法运行 CheckConfiguration 和 UpdateMessage 代码。
    猜你喜欢
    • 2020-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-21
    • 2012-06-17
    • 2022-01-22
    相关资源
    最近更新 更多