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

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

问题描述

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

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中的ComboBoxes进行了此操作.

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 Forms列表视图中存在一些错误,因此似乎无法解决该问题.我创建的示例应用程序在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回购中看到示例问题: https://ChristianFindlay@bitbucket.org/ChristianFindlay/xamarin-forms-scratch. git .只需运行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存在一些错误.我已经在此处记录了repro步骤: https://bugzilla.xamarin.com/show_bug.cgi?id=58451

It is now clear that there are several bugs around this is Xamarin Forms. I have documented the repro steps here: https://bugzilla.xamarin.com/show_bug.cgi?id=58451

推荐答案

Xamarin Forms团队创建了一个pull请求来解决此处的一些问题: 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表单ListView SelectedItem绑定问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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