Как использовать ReactiveList, чтобы пользовательский интерфейс обновлялся при добавлении новых элементов

Я создаю приложение Xamarin.Forms со списком. ItemSource — это реактивный список. Однако добавление новых элементов в список не обновляет пользовательский интерфейс. Каков правильный способ сделать это?

Определение списка

_listView = new ListView();
var cell = new DataTemplate(typeof(TextCell));
cell.SetBinding(TextCell.TextProperty, "name");
cell.SetBinding(TextCell.DetailProperty, "location");
_listView.ItemTemplate = cell;

Связывание

this.OneWayBind(ViewModel, x => x.monkeys, x => x._listView.ItemsSource);
this.OneWayBind(ViewModel, x => x.save, x => x._button.Command); //save adds new items

Посмотреть модель

public class MyPageModel : ReactiveObject
{
    public MyPageModel()
    {
        var canSave = this.WhenAny(x => x.username, x => !String.IsNullOrWhiteSpace(x.Value) && x.Value.Length>5);
        save = ReactiveCommand.CreateAsyncTask(canSave, async _ =>
        {
            var monkey = new Monkey { name = username, location = "@ " + DateTime.Now.Ticks.ToString("X"), details = "More here" };
            monkeys.Add(monkey);
            username = "";
        });
        monkeys = new ReactiveList<Monkey>{
            new Monkey { name="Baboon", location="Africa & Asia", details = "Baboons are Africian and Arabian Old World..." }
        };
        _monkeys.ChangeTrackingEnabled = true;
    }
    private string _username = "";
    public string username
    {
        get { return _username; }
        set { this.RaiseAndSetIfChanged(ref _username, value); }
    }
    private double _value = 0;
    public double value
    {
        get { return _value; }
        set { this.RaiseAndSetIfChanged(ref _value, value); }
    }
    public ReactiveCommand<Unit> save { get; set; }
    public ReactiveList<Monkey> _monkeys;
    public ReactiveList<Monkey> monkeys
    {
        get { return _monkeys; }
        set { this.RaiseAndSetIfChanged(ref _monkeys, value); }
    }
}
public class Monkey
{
    public string name { get; set; }
    public string location { get; set; }
    public string details { get; set; }
}

Попробовал это со свойством ReactiveList, являющимся обычным автоматическим свойством, а также со свойством в приведенном выше коде, где я использовал метод RaiseAndSetIfChanged.


person ritcoder    schedule 22.12.2014    source источник


Ответы (2)


Ваша проблема в том, что вы меняете monkeys в потоке, отличном от пользовательского интерфейса. В других фреймворках это вызовет исключение, но в AppKit/UIKit это просто делает странные вещи (обычно ничего).

    save = ReactiveCommand.Create(canSave);
    save.Subscribe(_ =>
    {
        // Do the part that modifies UI elements (or things bound to them)
        // in the RxCmd's Subscribe. This is guaranteed to run on the UI thread
        var monkey = new Monkey { name = username, location = "@ " + DateTime.Now.Ticks.ToString("X"), details = "More here" };
        monkeys.Add(monkey);
        username = "";
    });
person Ana Betts    schedule 22.12.2014
comment
Я сделал вышеуказанное изменение с двумя замечаниями. Мне пришлось изменить ReactiveCommand<Unit> save на ReactiveCommand<object> save, чтобы код скомпилировался. Когда я запускаю, список по-прежнему не обновляется, хотя текстовое поле очищается, когда для свойства имени пользователя задана пустая строка. Изменил определение списка на public ReactiveList<Monkey> monkeys { get; set; } и имел тот же эффект. - person ritcoder; 22.12.2014
comment
Изменение на public ObservableCollection<Monkey> monkeys { get; set; } работает, и пользовательский интерфейс обновляется. Я неправильно использую реактивный список? - person ritcoder; 22.12.2014
comment
@ritcoder - ты продвинулся в этом? Я все еще сталкиваюсь с тем же самым с проектом Xam Forms. - person detroitpro; 04.02.2015
comment
Я не мог использовать ReactiveList, но для тестового проекта, над которым я работал, было достаточно ObservableCollection. - person ritcoder; 04.02.2015

Эта техника кажется ужасно сложной. Это заставляет меня думать, что вы хотите сделать что-то более сложное, чем я думаю.

Вот чистое решение для добавления объекта в список путем привязки к ReactiveList<T> в чистом виде MVVM.

Во-первых, xaml

<Window x:Class="ReactiveUIListBox.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:ReactiveUIListBox.ViewModel"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <viewModel:MainWindowViewModel x:Key="MainWindowViewModel"/>    
    </Window.Resources>

    <Window.DataContext>
        <StaticResource ResourceKey="MainWindowViewModel"/>
    </Window.DataContext>

    <Grid DataContext="{StaticResource MainWindowViewModel}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Grid Grid.Column="0" Background="Azure">
            <ListBox ItemsSource="{Binding Model.TestReactiveList}"></ListBox>
        </Grid>

        <Grid Grid.Column="1">
            <Button x:Name="button" Command="{Binding TestCommand}" Content="Button" HorizontalAlignment="Left" Margin="78,89,0,0" VerticalAlignment="Top" Width="75"/>
        </Grid>

    </Grid>
</Window>

Вот ViewModel

using System.Windows.Input;
using ReactiveUIListBox.Model;
using SecretSauce.Mvvm.ViewModelBase;

namespace ReactiveUIListBox.ViewModel
{
    public class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
        {
            Model = new ReactiveModel<string>();
        }
        public ReactiveModel<string> Model
        {
            get;
            set;
        }

        public ICommand TestCommand
        {
            get { return new RelayCommand(ExecuteTestCommand); }
        }

        private void ExecuteTestCommand(object obj)
        {
            Model.TestReactiveList.Add("test string");
        }
    }
}

и, наконец, вот Модель.

using ReactiveUI;

namespace ReactiveUIListBox.Model
{
    public class ReactiveModel<T> : ReactiveObject
    {
        public ReactiveModel()
        {
            TestReactiveList= new ReactiveList<T>();
        }

        public ReactiveList<T> TestReactiveList
        {
            get;
            set;
        }
    }
}

Нажатие кнопки заполнит ListBox. Надеюсь, я не слишком упростил то, что вы пытаетесь сделать, но ваш код кажется повсюду.

Я не могу различить ваш код, который подходит для View, Model или ViewModel.

Ваше здоровье.

person guerrillacodester    schedule 20.08.2015
comment
Отмеченный. Приведенный выше код действительно хорошо структурирован. С вышесказанным, к чему я стремился, была возможность генерировать пользовательский интерфейс на лету. Это был один из образцов. Я заставил его работать, хотя. Когда получу сдачу, проверю. - person ritcoder; 21.08.2015