Winforms:当 ViewModel 更改时重新绑定

Winforms: re-binding when the ViewModel changes

我正在开发 Winforms ReactiveUI 应用程序,我有一个实现 IViewFor:

的 UserControl
public partial class CustomView : UserControl, IViewFor<CustomViewModel>
{
    public CustomViewModel ViewModel { get; set; }
    object IViewFor.ViewModel {
        get { return ViewModel; }
        set { ViewModel = value as CustomViewModel; }
    }

    public CustomView()
    {
        InitializeComponent();

        this.Bind(ViewModel, x => x.SomeBindingList, x => x.DataGridBindingSource.DataSource);
    }
}

在调用控件中,我将 ViewModel 设置为:

customView.ViewModel = new CustomViewModel(model)

然而,当数据改变时,customView.ViewModel被重新分配(使用上面相同的代码)但它不会自动重新绑定。我假设那是因为 ViewModel 没有 PropertyChanged 事件。

我可以在 CustomView 上实施 INotifyPropertyChanged,但我想知道 - 是否有一种方便的 method/ReactiveUI 方法来做到这一点?

我认为您在传递新模型而不是替换 ViewModel 方面走在正确的轨道上。我不确定您的确切要求,但这里有一个可能有帮助的示例。选择新用户会更改 CustomView 的 DataGridView 中的联系人列表。

项目

public class Item
    {
        public string Name { get; set; }
        public int Value { get; set; }
        public Item(string name, int value)
        {
            Name = name; Value = value;
        }            
    }

MainViewModel

public class MainViewModel : ReactiveObject
{

    int _userId;
    public int UserId
    {
        get { return _userId; }
        set { this.RaiseAndSetIfChanged(ref _userId, value); }
    }

    ObservableAsPropertyHelper<User> _user;
    public User User => _user.Value;

    ObservableAsPropertyHelper<List<Item>> _userList;
    public List<Item> UserList => _userList.Value;

    public ReactiveCommand<User> LoadUser { get; protected set; }
    public ReactiveCommand<List<Item>> LoadUserList { get; protected set; }

    public MainViewModel()
    {

        LoadUser = ReactiveCommand.CreateAsyncObservable(_ => LoadUserImp(UserId));
        _user = LoadUser.ToProperty(this, x => x.User, null);

        LoadUserList = ReactiveCommand.CreateAsyncObservable(_ => LoadUserListImp());
        _userList = LoadUserList.ToProperty(this, x => x.UserList, new List<Item>());

        // Listens for change to UserId and loads new User.
        this.WhenAnyValue(x => x.UserId).Where(id => id > 0).InvokeCommand(LoadUser);

    }

    private IObservable<User> LoadUserImp(int userId)
    {
        User user;

        if (userId == 1)
        {
            user = new User { Id = 1, Name = "Bob" };
        }
        else
        {
            user = new User { Id = 2, Name = "Jane" };
        }

        return Observable.Return(user);

    }

    private IObservable<List<Item>> LoadUserListImp()
    {

        Item item1 = new Item("Bob", 1);
        Item item2 = new Item("Jane", 2);
        List<Item> items = new List<Item> { item1, item2 };

        return Observable.Return(items);

    }

}

主视图

public partial class MainView : Form, IViewFor<MainViewModel>
{        
    public MainViewModel ViewModel { get; set; }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }

        set { ViewModel = value as MainViewModel; }
    }

    public MainView()
    {
        InitializeComponent();

        List<Item> items = new List<Item>();

        UserComboBox.DataSource = items;
        UserComboBox.DisplayMember = "Name";
        UserComboBox.ValueMember = "Value";

        // Two way binding.
        this.Bind(ViewModel, vm => vm.UserId, v => v.UserComboBox.SelectedValue);

        // One way binding.
        this.OneWayBind(ViewModel, vm => vm.UserList, v => v.UserComboBox.DataSource);

        // Per Paul Betts: Invoking this in the VM constructor means that your VM class becomes more difficult to test, 
        // because you always have to mock out the effects of calling [LoadUserList], 
        // even if the thing you are testing is unrelated.
        // Instead, I always call these commands in the View.
        this.WhenAnyValue(v => v.ViewModel.LoadUserList)
            .SelectMany(x => x.ExecuteAsync())
            .Subscribe();

        // This is where I would change your model, in this case the user in the CustomView.
        this.WhenAnyObservable(v => v.ViewModel.LoadUser)
            .Subscribe(user => customView1.ViewModel.User = user);

        ViewModel = new MainViewModel();

        customView1.ViewModel = new CustomViewModel();

    }

}

CustomViewModel

public class CustomViewModel : ReactiveObject
{

    ObservableAsPropertyHelper<List<Person>> _contacts;
    public List<Person> Contacts => _contacts.Value;

    User _user;
    public User User
    {
        get { return _user; }
        set { this.RaiseAndSetIfChanged(ref _user, value); }
    }

    public ReactiveCommand<List<Person>> LoadContacts { get; protected set; }

    public CustomViewModel()
    {

        LoadContacts = ReactiveCommand.CreateAsyncObservable(_ => LoadContactsImp(User.Id));

        _contacts = LoadContacts.ToProperty(this, x => x.Contacts, new List<Person>());

        this.WhenAnyValue(vm => vm.User.Id).InvokeCommand(LoadContacts);

    }

    private IObservable<List<Person>> LoadContactsImp(int userId)
    {
        List<Person> contacts;

        if (userId == 1)
        {
            contacts = new List<Person>()
            {
                new Person() { Id = 1, FirstName = "John", LastName = "Jones" },
                new Person() { Id = 2, FirstName = "Beth", LastName = "Johnson" },
            };
        }
        else
        {
            contacts = new List<Person>()
            {
                new Person() { Id = 1, FirstName = "Dave", LastName = "Smith" },
                new Person() { Id = 2, FirstName = "Elizabeth", LastName = "Bretfield" },
            };
        }

        return Observable.Return(contacts);

    }
}

自定义视图

public partial class CustomView : UserControl, IViewFor<CustomViewModel>
{

    public CustomViewModel ViewModel { get; set; }


    object IViewFor.ViewModel
    {
        get { return ViewModel; }

        set { ViewModel = value as CustomViewModel; }

    }

    public CustomView()
    {
        InitializeComponent();

        this.OneWayBind(ViewModel, vm => vm.Contacts, v => v.Contacts.DataSource);

    }

}

为什么调用 CustomView 构造函数 "BridgeGeometryView"?

您是否在 ViewModel 中将 ReactiveList 或 ReactiveBindingList 用于 SomeBindingList?

此外,我建议不要直接设置绑定,在 InitializeComponent() 方法之后使用 WhenActivated:

public BridgeGeometryView()
    {
        InitializeComponent();
        this.WhenActivated(() =>
        {
           this.Bind(ViewModel, x => x.SomeBindingList, x => x.DataGridBindingSource.DataSource);  
        });

    }