Xamarin Forms ListView SelectedItem 绑定问题 [英] Xamarin Forms ListView SelectedItem Binding Issue

查看:38
本文介绍了Xamarin Forms ListView SelectedItem 绑定问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ListView 遵循 UI 控件的 ItemPicker/Selector 模式.一般来说,这些类型的控件,无论其平台如何,都会有一个 SelectedItem 和一个 ItemsSource.基本思想是 ItemsSource 中有一个项目列表,SelectedItem 可以设置为这些项目之一.其他一些示例是 ComboBoxes (Silverlight/UWP/WPF) 和 Pickers (Xamarin Forms).

ListViews follow the ItemPicker/Selector pattern of UI controls. Generally speaking, these types of controls, regardless of their platform will have a SelectedItem, and an ItemsSource. The basic idea is that there is a list of items in the ItemsSource, and the SelectedItem can be set to one of those items. Some other examples are ComboBoxes (Silverlight/UWP/WPF), and Pickers (Xamarin Forms).

在某些情况下,这些控件已准备好异步,而在其他情况下,需要编写代码以处理 ItemsSource 晚于 SelectedItem 填充的情况.在我们的例子中,大多数情况下,BindingContext(包含绑定到 SelectedItem 的属性)将在 ItemsSource 之前设置.因此,我们需要编写代码以使其正常运行.例如,我们已经为 Silverlight 中的 ComboBox 完成了这项工作.

In some cases, these controls are async ready, and in other cases, code needs to be written in order to handle scenarios where the ItemsSource is populated later than the SelectedItem. In our case, most of the time, the BindingContext (which contains the property bound to SelectedItem) will be set before the ItemsSource. So, we need to write code to allow this to function correctly. We have done this for ComboBoxes in Silverlight for example.

在 Xamarin Forms 中,ListView 控件未准备好异步,即如果在设置 SelectedItem 之前未填充 ItemsSource,则所选项目将永远不会在控件上突出显示.这可能是设计使然,这是可以的.该线程的目的是找到一种方法使 ListView 异步准备好,以便可以在设置 SelectedItem 之后 填充 ItemsSource.

In Xamarin Forms, the ListView control is not async ready, i.e. if the ItemsSource is not populated before the SelectedItem is set, the selected item will never be highlighted on the control. This is probably by design and this is OK. The point of this thread is to find a way make the ListView async ready so that the ItemsSource can be populated after the SelectedItem is set.

应该有可以在其他平台上实现的直接变通方法来实现这一点,但 Xamarin 表单列表视图中存在一些错误,这使得似乎无法解决该问题.我创建的示例应用程序在 WPF 和 Xamarin Forms 之间共享,以显示 ListView 在每个平台上的行为方式不同.例如,WPF ListView 是异步就绪的.如果在 WPF ListView 上设置 DataContext 之后填充 ItemsSource,则 SelectedItem 将绑定到列表中的项目.

There should be straight forward work arounds that can be implemented on other platforms to achieve this, but there are a few bugs in the Xamarin Forms list view that make it seemingly impossible to work around the issue. The sample app I have created is shared between WPF and Xamarin Forms in order to show how the ListView behaves differently on each platform. The WPF ListView for example, is async ready. If the ItemsSource is populated after the DataContext is set on a WPF ListView, the SelectedItem will bind to the item in the list.

在 Xamarin Forms 中,我无法始终如一地使 ListView 上的 SelectedItem 双向绑定工作.如果我在 ListView 中选择一个项目,它会在我的模型上设置属性,但是如果我在我的模型上设置属性,则应该选择的项目不会反映为在 ListView 中被选择.加载项目后,当我在模型上设置属性时,不会显示 SelectedItem.这发生在 UWP 和 Android 上.iOS 尚未经过测试.

In Xamarin Forms, I cannot consistently get SelectedItem two way binding on ListView to work. If I select an item in the ListView, it sets the property on my model, but if I set the property on my model, the item that should be selected is not reflected as being selected in the ListView. After the items have been loaded, when I set the property on my model, no SelectedItem is displayed. This is happening on UWP and Android. iOS remains untested.

你可以在这个 Git repo 中看到示例问题:https://ChristianFindlay@bitbucket.org/ChristianFindlay/xamarin-forms-scratch.混蛋.只需运行 UWP 或 Android 示例,然后单击 Async ListView.您还可以运行 XamarinFormsWPFComparison 示例以查看 WPF 版本的行为有何不同.

You can see the sample problem in this Git repo: https://ChristianFindlay@bitbucket.org/ChristianFindlay/xamarin-forms-scratch.git . Simply run the UWP, or Android sample, and click Async ListView. You can also run the XamarinFormsWPFComparison sample to see how the WPF version behaves differently.

当您运行 Xamarin Forms 示例时,您将看到在项目加载后没有选择任何项目.但是在 WPF 版本中,它被选中.注意:它不是突出显示为蓝色,而是略呈灰色表示它已被选中.这就是我的问题所在,也是我无法解决异步问题的原因.

When you run the Xamarin Forms sample, you will see that there is no item selected after the items load. However in the WPF version, it is selected. Note: It's not highlighted blue, but it is slightly grey indicating that it is selected. This is where my problem is, and the reason I can't work around the async issue.

这是我的代码(绝对最新代码的克隆存储库):

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 存在几个错误.我在这里记录了重现步骤:https://bugzilla.xamarin.com/show_bug.cgi?id=58451

推荐答案

Xamarin Forms 团队创建了一个拉取请求来解决这里的一些问题:https://github.com/xamarin/Xamarin.Forms/pull/1152

The Xamarin Forms team created a pull request to solve some of the issues here: https://github.com/xamarin/Xamarin.Forms/pull/1152

但是,我认为 Xamarin Forms 的主分支从未接受过此拉取请求.

But, I don't believe this pull request was ever accepted in to the master branch of Xamarin Forms.

这篇关于Xamarin Forms ListView SelectedItem 绑定问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆