Не работает привязка к свойству MvxSpinner SelectedItem

2

Я использую MvxSpinner для отображения префиксов телефонов страны в комбинированном списке в приложении MvvmCross for Xamarin. Я могу правильно связать свойство ItemsSource, чтобы видеть список моих префиксов, но когда я назначаю свойство в моей модели представления, которое связывается со свойством SelectedItem MvxSpinner, оно не будет работать и всегда будет отображать первый элемент в списке в качестве выбранного элемента.

То, как я это делаю, заключается в следующем. В моей ViewModel я получаю пользовательские данные с сервера и назначаю свойства для Country и PhonePrefix. Затем я получаю список всех стран и префиксов также с сервера и связываю их со свойствами списка, которые связаны со свойствами ItemSource ответного MvxSpinners (упрощенно):

    public string PhonePrefix { get; set; }
    public string PhoneNumber { get; set; }
    public Country Country { get; set; } = new Country();

    public List<Country> Countries { get; set; } = new List<Country>();
    public List<string> Prefixes { get; set; } = new List<string>();

    private async Task GetUserData()
    {
        try
        {
            var userDataResult = await _registrationService.GetLoggedInUserData();

            if (userDataResult != null)
            {
                if (!userDataResult.HTTPStatusCode.Equals(HttpStatusCode.OK))
                {
                    Mvx.IoCProvider.Resolve<IUserDialogs>().Alert(userDataResult.Error?.Message);
                }
                else
                {

                        PhonePrefix = userDataResult.user.country_code_phone;
                        PhoneNumber = userDataResult.user.phone;
                        Country.id = userDataResult.user.person.addresses[0].country_id;
                        Country.name = userDataResult.user.person.addresses[0].country_name;
                }
            }
        }
        catch (Exception e)
        {
            Log.Error<RegistrationViewModel>("GetUserData", e);
        }
    }

    /// <summary>
    /// Populates the view model properties for Countries and Prefixes with information retrieved from the server
    /// </summary>
    private void ProcessFormData()
    {
        if (_registrationFormData != null)
        {
            Countries = _registrationFormData.Countries?.ToList();
            var userCountry = Countries?.Where(c => c.id == Country?.id).FirstOrDefault();
            Country = IsUserLogedIn && Country != null ? userCountry : Countries?[0];

            var prefixes = new List<string>();
            if (Countries != null)
            {
                foreach (var country in Countries)
                {
                    //spinner binding doesn't allow null values
                    if (country.phone_prefix != null)
                    {
                        prefixes.Add(country.phone_prefix);
                    }
                }
            }

            //we need to assign the binded Prefixes at once otherwise the binding for the ItemSource fails
            Prefixes = prefixes;
            PhonePrefix = Prefixes.Count > 0 && !IsUserLogedIn && string.IsNullOrWhiteSpace(PhonePrefix) ? Prefixes?[0] : PhonePrefix;
        }
    }

И в макете axml:

    <MvxSpinner
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="25dp"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="10dp"
            android:id="@+id/spnrCountry"                   
            local:MvxBind="ItemsSource Countries; SelectedItem Country"/>
    <MvxSpinner
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="25dp"
            android:layout_marginBottom="10dp"
            android:id="@+id/spnrPrefix"
            local:MvxBind="ItemsSource Prefixes; SelectedItem PhonePrefix"/>

В окне вывода я вижу следующую ошибку относительно MvxBinding:

(MvxBind) Null values not permitted in spinner SelectedItem binding currently

Я отладил, и у меня никогда нет пустых значений в списках или в свойствах, которые я связываю со свойствами ItemSource и SelectedItem MvxSpinner.

Фактически страны ItemSource и SelectedItem работают должным образом, поэтому, если пользователь сохранил страну в Аргентине, при загрузке данных выбранным элементом в счетчике будет Аргентина. Обратите внимание, что я использую сущность Country следующим образом:

public class Country
{
    public int id { get; set; }
    public bool favorite { get; set; }
    public string name { get; set; }
    public string name_de { get; set; }
    public string code { get; set; }
    public int rzl_code { get; set; }
    public string phone_prefix { get; set; }
    public string updated_at { get; set; }
    public string created_at { get; set; }

    public override string ToString()
    {
        return name;
    }
}

Я также попытался сделать префикс телефона в его собственной сущности, заключив в него строковое значение, но он тоже не сработал.

Кто-нибудь знает, что я делаю не так? Почему для стран это работает, а для префиксов нет?

Я использую PropertyChanged.Fody.

Спасибо!

Теги:
mvvmcross
selecteditem

4 ответа

1
Лучший ответ

Ошибка, которую вы получаете о null заключается в том, что SelectedItem в счетчике не допускает null в качестве выбранного элемента. Поэтому, если вы хотите отобразить пустой элемент, одним из решений является добавление другого фиксированного элемента с пустым значением, то есть в случае PhonePrefix вы можете установить string.Empty и добавить его в список PhonePrefixes а в вашей Country вы можете установить первый по умолчанию или создайте заглушку Country с именем None например, и добавьте ее в список стран.

Еще один момент, который необходимо учитывать, это то, что когда вы хотите обновить представление, вы должны быть уверены, что уведомляете его в Главной теме. Вы пытаетесь обновить PhonePrefix в Task другого потока, чтобы представление не было замечено.

Вы должны обновить PhonePrefix, выполнив:

this.InvokeOnMainThread(() => PhonePrefix = userDataResult.user.country_code_phone; );

Это позаботится о том, чтобы набор PhonePrefix непосредственно в главном потоке, поэтому ваше представление будет уведомлено правильно.


Обновить

После того, как вы лучше посмотрите на свой вопрос и собственный ответ и увидите, что вы используете PropertyChanged.Fody я могу догадаться, что проблема была в том, как вы назначаете PhonePrefix.

PropertyChanged.Fody умолчанию добавлена проверка равенства, которая заменяет код вашего свойства.

public string PhonePrefix { get; set; }

за что-то вроде

private string _phonePrefix;
public string PhonePrefix
{
    get
    {
        return _phonePrefix;
    }
    set
    {
        if (!String.Equals(_phonePrefix, value))
        {
            _phonePrefix = value;
            OnPropertyChanged("PhonePrefix");
        }
    }
}

поэтому, когда вы делаете в GetUserData():

PhonePrefix = userDataResult.user.country_code_phone;

и в ProcessFormData()

PhonePrefix = Prefixes.Count > 0 && !IsUserLogedIn && string.IsNullOrWhiteSpace(PhonePrefix) ? Prefixes?[0] : PhonePrefix;

PhonePrefix не имеет значения null или пробел, поэтому он пытается переназначить одно и то же значение, но, поскольку fody добавляет проверку на равенство, ему не присваивается снова и, следовательно, он не вызывает изменения значения, поэтому представление не получает уведомления.

Назначение в GetUserData() Я думаю, что это может быть сделано в другом потоке, и поэтому представление не получает уведомления. В соответствии с тем, что вы сказали, Country обновляется в ProcessFormData() поэтому для того, чтобы PhonePrefix тоже обновлялся в этом месте, вам нужно только добавить [DoNotCheckEquality] к свойству, чтобы избежать проверки на равенство, и это все.

[DoNotCheckEquality]
public string PhonePrefix { get; set; }

Если это не работает, вы должны добавить вызов также в основном потоке (я советую вам посмотреть в отладке, в каком потоке выполняется метод, чтобы узнать, нужен ли вам вызов в основном потоке).

Е.И.В.

  • 0
    Я понимаю сообщение об ошибке. Я никогда не говорил, что хочу отобразить пустой элемент в решении. Что касается основной точки резьбы, то это не так, поскольку свойства «Страна» и «Страны» правильно связаны в другом блесне.
  • 0
    Чтобы обновление обновлялось, вы должны сделать уведомление в главном потоке, так как я не знаю, как вы вызывали GetUserData() я предполагал, что это было сделано в потоке backgorund. Что касается того, что вы никогда не говорили, что хотите отобразить пустой элемент в решении, мне нравится давать больше информации в ответе, поэтому я даю это объяснение, потому что, возможно, вам это не полезно, но кому-то еще это может быть. Я постараюсь увидеть в вашем решении, почему это работает
Показать ещё 3 комментария
0

Свойства, которые вы используете, не уведомляют об изменениях для просмотра, для этого вы должны использовать:

string _phonePrefix; public string PhonePrefix { get => _phonePrefix; set => SetProperty(ref _phonePrefix, value); }

  • 0
    когда вы используете github.com/Fody/PropertyChanged, вам не нужно этого делать. Это автоматически делает это для вас
0

Хотя это странное решение и до сих пор не совсем понятно, почему, я нашел способ заставить его работать. У меня такое ощущение, что это как-то связано с асинхронизмом.

Проблема заключалась в назначении свойства PhonePrefix в методе GetuserData(), а затем переназначении его в ProcessFormData(). Итак, теперь работающий код выглядит так:

private string _phonePrefix;
public string PhonePrefix { get; set; }
public string PhoneNumber { get; set; }
public Country Country { get; set; } = new Country();

public List<Country> Countries { get; set; } = new List<Country>();
public List<string> Prefixes { get; set; } = new List<string>();

public override async Task Initialize()
{
    await base.Initialize();
    IsUserLogedIn = await _authService.IsUserLoggedIn();
    if (IsUserLogedIn)
    {
        //get user data from server, show user data
        await GetUserData();
    }

    //get countries, prefixes
    if (Countries.Count <= 0 || Prefixes.Count <= 0)
    {
        _registrationFormData = await _registrationService.GetRegistrationFormData();
        ProcessFormData();
    }

    AddValidationRules();
}

private async Task GetUserData()
{
    try
    {
        var userDataResult = await _registrationService.GetLoggedInUserData();

        if (userDataResult != null)
        {
            if (!userDataResult.HTTPStatusCode.Equals(HttpStatusCode.OK))
            {
                Mvx.IoCProvider.Resolve<IUserDialogs>().Alert(userDataResult.Error?.Message);
            }
            else
            {

                    //PhonePrefix = userDataResult.user.country_code_phone;
                    //can't bind it here to the public binded property because the SelectedItem binding fails. 
                    //I first assign it to a private field and then use it in the ProcessFormData method
                    _phonePrefix = userDataResult.user.country_code_phone;
                    PhoneNumber = userDataResult.user.phone;
                    Country.id = userDataResult.user.person.addresses[0].country_id;
                    Country.name = userDataResult.user.person.addresses[0].country_name;
            }
        }
    }
    catch (Exception e)
    {
        Log.Error<RegistrationViewModel>("GetUserData", e);
    }
}



    private void ProcessFormData()
    {
        if (_registrationFormData != null)
        {
            Countries = _registrationFormData.Countries?.ToList();
            var userCountry = Countries?.Where(c => c.id == Country?.id).FirstOrDefault();
            Country = IsUserLogedIn && Country != null ? userCountry : Countries?[0];

            var prefixes = new List<string>();
            if (Countries != null)
            {
                foreach (var country in Countries)
                {
                    //spinner binding doesn't allow null values
                    if (country.phone_prefix != null)
                    {
                        prefixes.Add(country.phone_prefix);
                    }
                }
            }

            //we need to assign the binded Prefixes at once otherwise the binding for the SelectedItem fails
            Prefixes = prefixes;
            if (Prefixes.Count > 0 && !IsUserLogedIn && string.IsNullOrWhiteSpace(_phonePrefix))
            {
                PhonePrefix = Prefixes?[0];
            }
            else
            {
                PhonePrefix = _phonePrefix;
            }
        }
    }

Обратите внимание на добавление частного свойства _phonePrefix для хранения значения префикса телефона в GetUserData(), а затем назначьте его значение свойству, которое оно связывает в представлении, PhonePrefix.

На самом деле я не могу действительно объяснить, почему это происходит, поэтому было бы неплохо, если бы кто-то мог объяснить это поведение.

  • 0
    Не могли бы вы добавить, где вы вызываете GetUserData() и ProcessFormData() ?
  • 0
    @fmaccaroni Я обновил ответ
Показать ещё 3 комментария
0

Это работает, так как PhonePrefix установлен в GetUserData, также вы должны установить Country. Из вашего кода Страна пуста, поэтому вы получаете первый элемент из выбранного списка или также ошибку.

  • 0
    Спасибо за ответ, но это не связано с тем, что Страна является нулевой. Я просто забыл поместить его в свой упрощенный фрагмент. Свойства Страны и Страны привязаны правильно и не показывают ошибок

Ещё вопросы

Сообщество Overcoder
Наверх
Меню