Я использую 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.
Спасибо!
Ошибка, которую вы получаете о 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; }
Если это не работает, вы должны добавить вызов также в основном потоке (я советую вам посмотреть в отладке, в каком потоке выполняется метод, чтобы узнать, нужен ли вам вызов в основном потоке).
Е.И.В.
Свойства, которые вы используете, не уведомляют об изменениях для просмотра, для этого вы должны использовать:
string _phonePrefix; public string PhonePrefix { get => _phonePrefix; set => SetProperty(ref _phonePrefix, value); }
Хотя это странное решение и до сих пор не совсем понятно, почему, я нашел способ заставить его работать. У меня такое ощущение, что это как-то связано с асинхронизмом.
Проблема заключалась в назначении свойства 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.
На самом деле я не могу действительно объяснить, почему это происходит, поэтому было бы неплохо, если бы кто-то мог объяснить это поведение.
GetUserData()
и ProcessFormData()
?
Это работает, так как PhonePrefix установлен в GetUserData, также вы должны установить Country. Из вашего кода Страна пуста, поэтому вы получаете первый элемент из выбранного списка или также ошибку.
GetUserData()
я предполагал, что это было сделано в потоке backgorund. Что касается того, что вы никогда не говорили, что хотите отобразить пустой элемент в решении, мне нравится давать больше информации в ответе, поэтому я даю это объяснение, потому что, возможно, вам это не полезно, но кому-то еще это может быть. Я постараюсь увидеть в вашем решении, почему это работает