[英]Xamarin Forms ListView SelectedItem Binding Issue
ListView遵循UI控件的ItemPicker / Selector模式。 一般來說,這些類型的控件(無論其平台如何)將具有SelectedItem和ItemsSource。 基本思想是,ItemsSource中有一個項目列表,並且SelectedItem可以設置為這些項目之一。 其他一些示例是ComboBoxes(Silverlight / UWP / WPF)和Pickers(Xamarin表單)。
在某些情況下,這些控件已准備好異步,而在其他情況下,則需要編寫代碼以處理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中,我無法始終使ListItem的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
AdaptListView是ListView控件的合適替代方法,並且不受這些問題的影響。
Xamarin Forms團隊創建了一個pull請求來解決此處的一些問題: https : //github.com/xamarin/Xamarin.Forms/pull/1152
但是,我認為這個拉取請求從未被Xamarin Forms的主分支接受。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.