ListViews遵循UI控件的ItemPicker / Selector模式。一般来说,这些类型的控件,无论其平台如何,都将具有SelectedItem和ItemsSource。基本思想是ItemsSource中有一个项目列表,SelectedItem可以设置为其中一个项目。其他一些例子是ComboBoxes(Silverlight / UWP / WPF)和Pickers(Xamarin Forms)。
在某些情况下,这些控件是异步准备的,在其他情况下,需要编写代码以处理ItemsSource的填充晚于SelectedItem的情况。在我们的例子中,大多数情况下,BindingContext(包含绑定到SelectedItem的属性)将在ItemsSource之前设置。因此,我们需要编写代码以使其正常运行。例如,我们已经为Silverlight中的ComboBoxes做了这个。
在Xamarin Forms中,ListView控件不是异步准备好的,即如果在设置SelectedItem之前未填充ItemsSource,则所选项目将永远不会在控件上突出显示。这可能是设计的,这是可以的。 此线程的目的是找到一种方法使ListView异步准备就绪,以便在设置SelectedItem之后填充ItemsSource。
应该有可以在其他平台上实现的直接解决方案来实现这一点,但是Xamarin Forms列表视图中存在一些错误,使得它似乎无法解决该问题。我创建的示例应用程序在WPF和Xamarin Forms之间共享,以显示ListView在每个平台上的行为方式。例如,WPF ListView是异步准备好的。如果在WPF ListView上设置DataContext后填充ItemsSource,则SelectedItem将绑定到列表中的项目。
在Xamarin Forms中,我不能始终如一地在ListView上使用SelectedItem双向绑定来工作。如果我在ListView中选择一个项目,它会在我的模型上设置属性,但如果我在模型上设置属性,则应该选择的项目不会反映为在ListView中被选中。加载项目后,当我在模型上设置属性时,不会显示SelectedItem。这是在UWP和Android上发生的。 iOS仍未经过测试。
您可以在此Git仓库中看到示例问题: https://ChristianFindlay@bitbucket.org/ChristianFindlay/xamarin-forms-scratch.git。只需运行UWP或Android示例,然后单击Async ListView。您还可以运行XamarinFormsWPFComparison示例以查看WPF版本的行为方式如何。
运行Xamarin Forms示例时,您会看到在加载项目后没有选择任何项目。但是在WPF版本中,它被选中。注意:它没有突出显示为蓝色,但略呈灰色,表示已选中。这就是我的问题所在,以及我可以解决异步问题的原因。
这是我的代码(绝对最新代码的克隆代表):
public class AsyncListViewModel : INotifyPropertyChanged
{
#region Fields
private ItemModel _ItemModel;
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Public Properties
public ItemModel ItemModel
{
get
{
return _ItemModel;
}
set
{
_ItemModel = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemModel)));
}
}
#endregion
}
public class ItemModel : INotifyPropertyChanged
{
#region Fields
private int _Name;
private string _Description;
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Public Properties
public int Name
{
get
{
return _Name;
}
set
{
_Name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Description)));
}
}
#endregion
#region Public Methods
public override bool Equals(object obj)
{
var itemModel = obj as ItemModel;
if (itemModel == null)
{
return false;
}
var returnValue = Name.Equals(itemModel.Name);
Debug.WriteLine($"An {nameof(ItemModel)} was tested for equality. Equal: {returnValue}");
return returnValue;
}
public override int GetHashCode()
{
Debug.WriteLine($"{nameof(GetHashCode)} was called on an {nameof(ItemModel)}");
return Name;
}
#endregion
}
public class ItemModelProvider : ObservableCollection<ItemModel>
{
#region Events
public event EventHandler ItemsLoaded;
#endregion
#region Constructor
public ItemModelProvider()
{
var timer = new Timer(TimerCallback, null, 3000, 0);
}
#endregion
#region Private Methods
private void TimerCallback(object state)
{
Device.BeginInvokeOnMainThread(() =>
{
Add(new ItemModel { Name = 1, Description = "First" });
Add(new ItemModel { Name = 2, Description = "Second" });
Add(new ItemModel { Name = 3, Description = "Third" });
ItemsLoaded?.Invoke(this, new EventArgs());
});
}
#endregion
}
这是XAML:
<Grid x:Name="TheGrid">
<Grid.Resources>
<ResourceDictionary>
<local:ItemModelProvider x:Key="items" />
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<ListView x:Name="TheListView" Margin="4" SelectedItem="{Binding ItemModel, Mode=TwoWay}" ItemsSource="{StaticResource items}" HorizontalOptions="Center" VerticalOptions="Center" BackgroundColor="#EEEEEE" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Label Text="{Binding Name}" TextColor="#FF0000EE" VerticalOptions="Center" />
<Label Text="{Binding Description}" Grid.Row="1" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ActivityIndicator x:Name="TheActivityIndicator" IsRunning="True" IsVisible="True" Margin="100" />
<StackLayout Grid.Row="1" Orientation="Horizontal">
<Label Text="Name: " />
<Label Text="{Binding ItemModel.Name}" />
<Label Text="Description: " />
<Label Text="{Binding ItemModel.Description}" />
<Button Text="New Model" x:Name="NewModelButton" />
<Button Text="Set To 2" x:Name="SetToTwoButton" />
</StackLayout>
</Grid>
代码背后:
public partial class AsyncListViewPage : ContentPage
{
ItemModelProvider items;
ItemModel two;
private AsyncListViewModel CurrentAsyncListViewModel => BindingContext as AsyncListViewModel;
public AsyncListViewPage()
{
InitializeComponent();
CreateNewModel();
items = (ItemModelProvider)TheGrid.Resources["items"];
items.ItemsLoaded += Items_ItemsLoaded;
NewModelButton.Clicked += NewModelButton_Clicked;
SetToTwoButton.Clicked += SetToTwoButton_Clicked;
}
private void SetToTwoButton_Clicked(object sender, System.EventArgs e)
{
if (two == null)
{
DisplayAlert("Wait for the items to load", "Wait for the items to load", "OK");
return;
}
CurrentAsyncListViewModel.ItemModel = two;
}
private void NewModelButton_Clicked(object sender, System.EventArgs e)
{
CreateNewModel();
}
private void CreateNewModel()
{
//Note: if you replace the line below with this, the behaviour works:
//BindingContext = new AsyncListViewModel { ItemModel = two };
BindingContext = new AsyncListViewModel { ItemModel = GetNewTwo() };
}
private static ItemModel GetNewTwo()
{
return new ItemModel { Name = 2, Description = "Second" };
}
private void Items_ItemsLoaded(object sender, System.EventArgs e)
{
TheActivityIndicator.IsRunning = false;
TheActivityIndicator.IsVisible = false;
two = items[1];
}
}
注意:如果我将方法CreateNewModel更改为:
private void CreateNewModel()
{
BindingContext = new AsyncListViewModel { ItemModel = two };
}
SelectedItem会反映在屏幕上。这似乎表明ListView正在基于对象引用比较项目,而不是在对象上使用Equals方法。我倾向于认为这是一个错误。但是,这不是唯一的问题,因为如果这是唯一的问题,那么单击SetToTwoButton应该会产生相同的结果。
现在很明显,有几个错误是Xamarin Forms。我在这里记录了repro步骤: https://bugzilla.xamarin.com/show_bug.cgi?id=58451
答案 0 :(得分:0)
AdaptListView是ListView控件的合适替代方案,不受这些问题的影响。
答案 1 :(得分:0)
Xamarin Forms团队创建了一个拉取请求来解决这里的一些问题: https://github.com/xamarin/Xamarin.Forms/pull/1152
但是,我不相信这个拉取请求曾被接受过Xamarin Forms的主分支。