首页 文章

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

提问于
浏览
6

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

第一次打开View时,没有任何交互使列表加载,并且禁用了一个绑定到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;
        }
    }
}

2 回答

  • 0

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

  • 5

    这似乎是Xamarin Forms或ReactiveUI的错误 . 这里记录了这个问题:https://github.com/reactiveui/ReactiveUI/issues/806

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

相关问题