5
votes

I would like to bind a property in my viewmodel to a ComboBox in a Windows Forms application, using ReactiveUI.

I found several examples with WPF but no examples with Windows Forms.

EDIT: Part 1: Bind the selected value to Following example from comment:

this.Bind(ViewModel, vm => vm.ViewModelProperty, v => v.comboBox.SelectedValue, comboBox.Events().SelectedValueChanged);

I get the error: CS1955 Non-invocable member 'Component.Events' cannot be used like a method.

Part 2: Bind the items in the ComboBox to a collection in the viewmodel ? Don't know how to do

5

5 Answers

4
votes

First, your view should implement IViewFor<YourViewModel> interface and then

this.Bind(ViewModel, vm => vm.PropertyToBind, x => comboBox.SelectedValue, comboBox.Events().SelectedValueChanged) 

EDIT: I have create a demo project:

using System;
using System.Reactive.Linq;
using System.Windows.Forms;
using ReactiveUI;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form, IViewFor<MyViewModel>
    {
        public Form1()
        {
            InitializeComponent();

            ViewModel = new MyViewModel();
            comboBox1.DataSource = ViewModel.Items;

            var selectionChanged = Observable.FromEvent<EventHandler, EventArgs>(
                h => (_, e) => h(e),
                ev => comboBox1.SelectedIndexChanged += ev,
                ev => comboBox1.SelectedIndexChanged += ev);
            this.Bind(ViewModel, vm => vm.SelectedItem, x => x.comboBox1.SelectedItem, selectionChanged);
        }

        public MyViewModel ViewModel { get; set; }

        object IViewFor.ViewModel
        {
            get { return ViewModel; }
            set { ViewModel = (MyViewModel)value; }
        }
    }

    public class MyItem
    {
        private readonly string _text;

        public MyItem(string text)
        {
            _text = text;
        }

        public override string ToString()
        {
            return _text;
        }
    }

    public class MyViewModel : ReactiveObject
    {
        private MyItem _selectedItem;

        public MyViewModel()
        {
            Items = new ReactiveList<MyItem> {new MyItem("test1"), new MyItem("test2")};
        }

        public MyItem SelectedItem
        {
            get { return _selectedItem; }
            set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
        }

        public ReactiveList<MyItem> Items { get; private set; }
    }
}
3
votes

You can use the Observable.FromEventPattern method to bind the firing of the SelectedIndexChanged event to your view model property.

comboBoxWithItems.DataSource = ViewModel.ListOfPossibleItemsProperty;
comboBoxWithItems.DisplayMember = "Name";

Observable.FromEventPattern<EventHandler, EventArgs>(
    ev => comboBoxWithItems.SelectedIndexChanged += ev,
    ev => comboBoxWithItems.SelectedIndexChanged -= ev)
    .Select(x => comboBoxWithItems.SelectedItem)
    .BindTo(this, x => x.ViewModel.SelectedItemProperty);
0
votes

Your initial vm.SelectedItem is null and there is no change yet to update the VM from the view. Set an initial selection in the VM constructor.

0
votes

A couple of ideas for improvement relating to the list of values:

  1. Replace the direct set of comboBox1.DataSource = ViewModel.Items; with a bind OneWayBind(ViewModel, vm => vm.Items, v => v.comboBox1.DataSource); so that it isn't necessary for ViewModel to exist inside the view constructor and ViewModel can be dynamically changed.
  2. Use ReactiveBindingList instead of ReactiveList so that WinForms binding can react to changes in the value list (though I haven't tried this for this exact scenario).
0
votes

Since the other solutions did not work for me in UWP applications, there is a proper way that works in WinForms, WPF and UWP applications: use the Bind methods in the constructor of the view. Example for WPF/UWP:

using ReactiveUI;
using System.Reactive.Disposables;

    public sealed partial class MyView : Page, IViewFor<MyViewModel>
    {

        public MyView()
        {
            InitializeComponent();

            this.WhenActivated(d =>
            {
                this.OneWayBind(ViewModel, vm => vm.Items, v => v.DropDownControl.ItemsSource)
                    .DisposeWith(d);

                this.Bind(ViewModel, vm => vm.SelectedItem, v => v.DropDownControl.SelectedItem)
                    .DisposeWith(d);
            });
        }


        public MyViewModel ViewModel
        {
            get => DataContext as MyViewModel;
            set => DataContext = value;
        }

        object IViewFor.ViewModel
        {
            get => ViewModel;
            set => ViewModel = value as MyViewModel;
        }
    }

In ViewModel:

using ReactiveUI.Fody.Helpers;

    public sealed class MyViewModel : ReactiveObject
    {
        public void MyViewModel()
        {
            // Todo: Load items
        }

        [Reactive] public IList<MyItem> Items { get; set; } = new List<MyItem>();
        [Reactive] public MyItem? SelectedItem { get; set; }
    }