簡體   English   中英

Xamarin表單ListView SelectedItem綁定問題

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM