绑定到MvxSpinner SelectedItem属性不起作用



我使用MvxSpinner在MvvmCross for Xamarin应用程序的组合框中显示国家/地区电话前缀。我可以正确地绑定到ItemsSource属性,这样我就可以看到前缀的列表,但当我在视图模型中分配绑定到MvxSpinner的SelectedItem属性的属性时,它将不起作用,并且总是将列表中的第一个元素显示为所选项。

我的做法如下。在我的ViewModel中,我从服务器获取用户数据,并为Country和PhonePrefix分配属性。然后,我也从服务器获得所有国家/地区和前缀的列表,并将它们绑定到列表属性,该列表属性绑定到相应MvxSpinners的ItemSource属性(简化):

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

我进行了调试,并且在列表中或绑定到MvxSpinner的ItemSource和SelectedItem属性的属性中从未有任何Null值。

事实上,Countrys ItemSource和SelectedItem工作正常,所以如果用户将其国家保存为阿根廷,当我加载其数据时,微调器中的选定项目将为阿根廷。请注意,我使用这样的国家实体:

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中,你可以将第一个设置为默认值,或者创建一个名为None的存根Country并将其增加到国家列表中。

需要考虑的另一点是,当您想要更新视图时,必须确保在主线程中通知它。您正在尝试更新另一个线程的Task中的PhonePrefix,这样视图就不会被注意到。

您应该通过以下操作更新PhonePrefix

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

这将负责直接在主线程上执行PhonePrefix集,以便正确地通知您的视图。


更新

在更好地研究了你的问题和自己的答案,并看到你使用了PropertyChanged.Fody之后,我可以猜测问题实际上是你如何分配PhonePrefix

PropertyChanged.Fody默认行为是添加Equality Checking,它将替换您的属性代码

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;

并且在CCD_ 19 中

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

如果它不起作用,你也应该在主线程上添加调用(我建议你在调试中看看在哪个线程上执行的方法,看看你是否需要在主线程中调用)。

HIH-

它确实有效,因为PhonePrefix是在GetUserData中设置的,您还应该设置Country。根据您的代码,Country为null,这就是为什么您从所选列表中获得第一个项目或出错的原因。

虽然这是一个奇怪的解决方案,但我仍然不明白为什么,我找到了一种方法来让它发挥作用。我觉得这与异步有关。

问题是在GetuserData()方法中的PhonePrefix属性中分配的,然后在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。

事实上,我真的无法解释为什么会发生这种情况,所以如果有人能解释这种行为就太好了。

您正在使用的属性不会通知视图的更改,为此您必须使用:

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

相关内容

  • 没有找到相关文章

最新更新