Компонент Xamarin BindableProperty не инициализируется должным образом - PullRequest
0 голосов
/ 21 декабря 2018

У меня есть пользовательский компонент Xamarin Forms со свойством Required, которое я устанавливаю на 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 голосов
/ 21 декабря 2018

Попробуйте поместить CheckValidity() в обработчик событий, который вызывается после завершения конструктора, например BindingContextChanged:

public ValidatingEntry()
{
    InitializeComponent();
    ...
    BindingContextChanged += (sender, e) =>
    {
        CheckValidity();
    };
}
0 голосов
/ 21 декабря 2018

Свойства типа не будут обновляться значениями из 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();
    };
}

Стоит отметить, что это не только для форм.В конструкторе по умолчанию (без параметров) только свойства, для которых заданы значения по умолчанию, будут уже установлены при работе конструктора.Поэтому, если вы хотите, чтобы значением по умолчанию было 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";
}

Вывод будет:

Конструктор: Текст по умолчанию

Задача: Новый текст

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...