将 ReactiveList<T> 绑定到 Xamarin Forms 中的 ListView

Binding ReactiveList<T> to ListView in Xamarin Forms

我觉得我这样做是正确的,但我不确定。我正在将对象异步加载到我的 ViewModel 中的 ReactiveList 中。通过 Xamarin Forms,我已将 List 绑定到 Xamarin Forms 中 ListView 的 ItemSource 属性。我还有一个搜索框,它将清除 ReactiveList 并将新项目添加到 ReactiveList。

我第一次打开视图时,没有交互使列表加载,并且绑定到 ReactiveCommand 以将更多项目加载到 ReactiveList 的按钮被禁用。

我第二次打开 View 时,ListView 几乎立即呈现新 ViewModel 中的项目,但同样,交互似乎不起作用。但是,更改搜索框实际上会从 ListView 中清除项目,但不会添加项目。

此行为在 iOS。我是 ReactiveUI 6.3.1 和 Xamarin Forms 1.3.2.6316.

这里是简化的问题:

public class MyViewModel : ReactiveObject, IRoutableViewModel
{
    public MyViewModel(IScreen hostScreen = null)
    {
        HostScreen = hostScreen ?? Locator.Current.GetService<IScreen>();

        List = new ReactiveList<ItemViewModel>()
        {
            ChangeTrackingEnabled = true
        };

        //This never gets shown
        List.Add(new ItemViewModel()
        {
            Name = "TEST"
        });

        //Tried doing this on the main thread: same behavior
        LoadItemsCommand = ReactiveCommand.CreateAsyncTask(_ => GetItems()), RxApp.TaskpoolScheduler);

        LoadItemsCommand
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(results =>
            {
                //debugger breaks here in EVERY case and successfully changes List but doesn't necessarily affect the view

                try
                {
                    List.AddRange(results.Select(e => new ItemViewModel()
                    {
                        Name = e.Name
                    }));
                }
                catch (Exception ex)
                {
                    //breakpoint does not break here.
                    Debug.WriteLine(ex.Message);
                    throw;
                }

            });

        //No exceptions here either
        LoadItemsCommand.ThrownExceptions
            .Select(ex => new UserError("Error", "Please check your Internet connection"))
            .Subscribe(Observer.Create<UserError>(x => UserError.Throw(x)));

        this.WhenAnyValue(e => e.SearchText).Subscribe(e => ResetPage());
    }

    private Task<IEnumerable<Item>> GetItems()
    {
        //asynchronously get items
        return ...;
    }

    private int ResetPage()
    {
        List.Clear();
        return 0;
    }

    [DataMember]
    public ReactiveList<ItemViewModel> List { get; private set; }

    private string _searchText;
    [DataMember]
    public string SearchText
    {
        get { return _searchText; }
        set { this.RaiseAndSetIfChanged(ref _searchText, value); }
    }

    public ReactiveCommand<IEnumerable<Item>> LoadItems { get; protected set; }

    public class ItemViewModel : ReactiveObject
    {
        public string Name { get; set; }
    }

    public string UrlPathSegment
    {
        get { return "Page"; }
    }

    public IScreen HostScreen { get; protected set; }


}

我的xaml:

  <StackLayout VerticalOptions="FillAndExpand" Orientation="Vertical">
    <Entry x:Name="_searchEntry" Placeholder="Search"></Entry>
    <ListView x:Name="_myListView" RowHeight="80">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout Orientation="Vertical" >
              <Label Text="{Binding Name}"></Label>
              <Label Text="{Binding Address}"></Label>
              <Label Text="{Binding PhoneNumber}"></Label>
            </StackLayout>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <Button x:Name="_loadMoreButton" Text="Load More" TextColor="White" BackgroundColor="#77D065"></Button>  </StackLayout>
</XamForms:ReactiveContentPage>

我的视图代码隐藏:

using System;
using System.Reactive.Concurrency;
using System.Threading.Tasks;

using ReactiveUI;
using ReactiveUI.XamForms;

namespace views
{
    public partial class MyView : ReactiveContentPage<MyViewModel>
    {
        private IDisposable _disconnectHandler;

        public NearbyPlacesView()
        {
            InitializeComponent();

            this.Bind(this.ViewModel, model => model.SearchText, view => view._searchEntry.Text);
            this.OneWayBind(this.ViewModel, model => model.List, view => view._myListView.ItemsSource);
            this.BindCommand(this.ViewModel, model => model.LoadItemsCommand, view => view._loadMoreButton);
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            //Is this the proper way to do this? seems
            _disconnectHandler = UserError.RegisterHandler(async error =>
            {
                RxApp.MainThreadScheduler.ScheduleAsync(async (scheduler, token) =>
                {
                    await DisplayAlert("Error", error.ErrorMessage, "OK");
                });

                return RecoveryOptionResult.CancelOperation;
            });

            //Load the items when the view appears. This doesn't feel right though.
            ViewModel.LoadItemsCommand.Execute(null);
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();

            _disconnectHandler.Dispose();
            _disconnectHandler = null;
        }
    }
}

这似乎是 Xamarin Forms 或 ReactiveUI 的错误。该问题记录在此处:https://github.com/reactiveui/ReactiveUI/issues/806

我将 public ReactiveList<ItemViewModel> List 的类型更改为 public ObservableCollection<ItemViewModel> List,这解决了问题。

您的 ItemViewModel 是一个 ReactiveObject,但是,您没有正确编码 setter。记住,你应该在setter中使用this.RaiseAndSetIfChanged来提升NPC。