Binding to ComboBox using ReactiveUI and Windows Forms - winforms

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

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; }
}
}

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);

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.

A couple of ideas for improvement relating to the list of values:
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.
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).

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; }
}

Related

Reload Json content in MVVM and WPF

I have a Model called FieldModel. In ViewModel I am setting its properties through a json file parsing like this:
foreach (var field in innerClass.Item2.Properties)
{
FieldView fieldView = new FieldView(field);
fieldView.ClassName = classView.ClassName;
fieldView.IsAbstract = classView.IsAbstract;
FieldViewItems.Add(fieldView);
}
My question is: how to make the binding properly with the reload button in order to reload the content of json file when it's being modified ?
First implement a Command class, I prefer something like this :
public class GeneralCommand : ICommand
{
private Action ToBeExecutedAction;
private Func<bool> ExecutionValidatorFunc;
public GeneralCommand(Action toBeExecutedAction, Func<bool> executionValidatorFunc)
{
ToBeExecutedAction = toBeExecutedAction;
ExecutionValidatorFunc = executionValidatorFunc;
}
public bool CanExecute(object parameter)
{
return ExecutionValidatorFunc();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ToBeExecutedAction();
}
}
Now inside your ViewModel class, implement something like the following :
public class FieldModel : INotifyPropertyChanged
{
private GeneralCommand _generalCommand;
public FieldModel()
{
Action action = new Action(ChangeValue);
_generalCommand = new GeneralCommand(action, new Func<bool>(() => true));
}
public ICommand ReloadValues
{
get
{
return _generalCommand;
}
}
string _jsonText;
public string JsonText
{
get
{
return _jsonText;
}
}
private void ChangeValue()
{
//Change JsonText here
//Then raise event change to be updated
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TextJson"));//Here fill property name
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then from the Xaml bind your Reload button into command property ReloadValues inside your ViewModel object, and bind the JsonText property with a WPF control for example a Textbox.
Hope this is useful.

How to use ReactiveList so UI is updated when items are added/removed/modified

I'm creating a WinForms application with a DataGridView. The DataSource is a ReactiveList. Adding new items to the list however does not update the UI.
ViewModel
public class HomeViewModel: ReactiveObject
{
public ReactiveCommand<object> AddCmd { get; private set; }
ReactiveList<Model> _models;
public ReactiveList<Model> Models
{
get { return _models; }
set { this.RaiseAndSetIfChanged(ref _models, value); }
}
public HomeViewModel()
{
Models = new ReactiveList<Model>() { new Model { Name = "John" } };
AddCmd = ReactiveCommand.Create();
AddCmd.ObserveOn(RxApp.MainThreadScheduler);
AddCmd.Subscribe( _ =>
{
Models.Add(new Model { Name = "Martha" });
});
}
}
public class Model
{
public string Name { get; set; }
}
View
public partial class HomeView : Form, IViewFor<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
VM = new HomeViewModel();
this.OneWayBind(VM, x => x.Models, x => x.gvData.DataSource);
this.BindCommand(VM, x => x.AddCmd, x => x.cmdAdd);
}
public HomeViewModel VM { get; set; }
object IViewFor.ViewModel
{
get { return VM; }
set { VM = (HomeViewModel)value; }
}
HomeViewModel IViewFor<HomeViewModel>.ViewModel
{
get { return VM; }
set { VM = value; }
}
}
The view always show "John".
Debugging Subscribe show added items.
Tried it with ObservableCollection same result.How to use ReactiveList so UI is updated when new items are added
Tried it with IReactiveDerivedList same result. Does ReactiveUI RaiseAndSetIfChanged fire for List<T> Add, Delete, Modify?
I think what you want is a ReactiveBindingList rather than a ReactiveList. This is a WinForms specific version of the ReactiveList for binding purposes.
You should use BindingList.
reference :
"If you are bound to a data source that does not implement the IBindingList interface, such as an ArrayList, the bound control's data will not be updated when the data source is updated. For example, if you have a combo box bound to an ArrayList and data is added to the ArrayList, these new items will not appear in the combo box. However, you can force the combo box to be updated by calling the SuspendBinding and ResumeBinding methods on the instance of the BindingContext class to which the control is bound."
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-bind-a-windows-forms-combobox-or-listbox-control-to-data?view=netframeworkdesktop-4.8
Or
ReactiveBindingList
It work fine for me. !!!

How to inform a ViewModel that its collectionSource was changed(add, update or delete) from another viewModel in WPF MVVM?

If i have a viewmodel for adding new item and another viewmodel for displaying all or filtered items (the second viewmodel's view must always reflect any changes in the collection source), should the change be communicated from viewmodel to viewmodel directly, or viewmodel to repository to second viewmodel?
I tried the following
public class DataAccess //my repository
{
public DataAccess()
{
var ctx = new MyDbContext();
}
public void AddNewItem(Item item)
{
ctx.items.Add(item);
ctx.SaveChanges();
}
public ObservableCollection<Item> GetAllItems()
{
return new ObservableCollection<Item>(ctx.items.ToList());
}
Here's my first ViewModel
public class ItemsViewModel : ObservableObject
{
public ItemsViewModel()
{
DataAccess dt = new DataAccess();
AllItems = new ObservableCollection<Item>(dt.GetAllItems());
}
//AllItems is bound to datagrid
private ObservableCollection<Item> _allItems;
public ObservableCollection<Item> AllItems
{
get {return _allItems;}
set {_allItems = value; RaisePropertyChanged();}
}
//command to load the form for adding new item then the method below
//I passed AllItems into the constructor
//I think I can also pass AllItems using Messenger, but I haven't tried it yet
void LoadNewItemForm()
{
NewItemView view = new NewItemView(){DataContext = new NewItemViewModel(AllItems)};
view.ShowDialog();
}
Here's the second viewmodel
public class AddNewItemViewModel : ObservableObject
{
public AddNewItemViewModel(ObservableCollection<Item> allItems)
{
DataAccess dt = new DataAccess();
_allItems = allItems;
}
private ObservableCollection<Item> _allItems;
public ObservableCollection<Item> AllItems
{
get {return _allItems;}
set {_allItems = value; RaisePropertyChanged();}
}
//Here's the AddNewItem method
public void SaveNewItem()
{
Item newitem = new Item(){ ..... };
dt.AddNewItem(item);
//Now is this change in AllItems here supposed to reflect in the allItems passed via constructor (will this reflect in the first ViewModel)
AllItems.Add(newitem);
}
I've tried creating the AllItems property in the repository (DataAccess class) so that this AllItems property will be the one to be returned in the call for GetAllItems() and also it is in the repository where I'll call AllItems.Add(something) or AllItems.Remove(something). Still, change in this does not reflect in the first viewModel.
Here's an example of using events to inform the view models when the data changes.
The data layer:
public class DataAccess
{
private readonly MyDbContext ctx = new MyDbContext();
public event EventHandler<DataChangedEventArgs> ItemsChanged;
public void AddNewItem(Item item)
{
ctx.Items.Add(item);
ctx.SaveChanges();
RaiseItemsChanged(new DataChangedEventArgs(DataAction.Added, item));
}
public List<Item> GetAllItems()
{
return ctx.Items.ToList();
}
public void RaiseItemsChanged(DataChangedEventArgs eventArgs)
{
ItemsChanged?.Invoke(this, eventArgs);
}
}
The event args:
public enum DataAction
{
Added,
Deleted
}
public class DataChangedEventArgs
{
public DataAction DataAction { get; set; }
public Item Item { get; set; }
public DataChangedEventArgs(DataAction dataAction, Item item)
{
DataAction = dataAction;
Item = item;
}
}
The first view model:
public class ItemsViewModel
{
private readonly DataAccess dataAccess = new DataAccess();
public ObservableCollection<Item> AllItems { get; set; }
public ItemsViewModel()
{
AllItems = new ObservableCollection<Item>(dataAccess.GetAllItems());
dataAccess.ItemsChanged += (sender, eventArgs) =>
{
if (eventArgs.DataAction == DataAction.Added)
AllItems.Add(eventArgs.Item);
else
AllItems.Remove(eventArgs.Item);
};
}
}
This helps separate concerns because the AddNewItemViewModel can now add an item without worrying about updating ItemsViewModel. You could also take a lazier approach and handle any event by clearing and repopulating the ObservableCollection<Item>.

Update viewmodel based on MainWindow event

I have a UdpClient, firing off a DataRecevied event on my MainWindow:
public partial class MainWindow : Window
{
public static YakUdpClient ClientConnection = new YakUdpClient();
public ClientData;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ClientData = new ClientData();
ClientConnection.OnDataReceived += ClientConnectionOnDataReceived;
}
private void ClientConnectionOnDataReceived(object sender, MessageEventArgs messageEventArgs)
{
ClientData.Users = messageEvenArgs.ConnectedUsers;
}
}
My ClientData and User classes look as follow:
public class ClientData
{
public List<User> Users {get;set;)
}
public class User
{
public string Name {get;set;}
}
On my MainWindow, I have a UserControl called UserListView which has a ViewModel called UserListViewModel
The ViewModel looks as follow:
public class UserListViewModel: BindableBase
{
public UserListViewModel()
{
//I am sure there are better ways of doing this :(
Users = new ObservableCollection<User>((MainWindow)Application.Current.MainWindow).ClientData.Users
});
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get{ return _users;}
set { this.SetProperty(ref this._users, value); }
}
}
The difficulty I have here, is when the ClientConnectionOnDataReceived event on the MainWindow gets fired, I would like to update my ClientData class, My Viewmodel should then somehow be notified that the list changed, and subsequently update my UI.
Can anyone give me a solid example of how to achieve this using MVVM (Prism) in WPF?
I am new to MVVM, so i am still trying to figure this out.
First of all, there's no obvious reason why the main window should do the subscription.
I'd go for something like this:
create a service that encapsulates the subscription (and subscribes in its constructor)
register that as a singleton
have it implement INotifyPropertyChanged (to notify consumers of a change to Users)
inject the service into UserListViewModel and observe the Users property (see PropertyObserver)
when Users in the service changes, update Users in the user list view model
and best of all, no need for ObservableCollection here :-)
EDIT: example:
interface IUserService : INotifyPropertyChanged
{
IReadOnlyCollection<User> Users
{
get;
}
}
class YakUdpService : BindableBase, IUserService
{
private readonly YakUdpClient _yakUdpClient;
private IReadOnlyCollection<User> _users;
public YakUdpService()
{
_yakUdpClient = new YakUdpClient();
_yakUdpClient.OnDataReceived += ( s, e ) => Users = e.ConnectedUsers;
}
public IReadOnlyCollection<User> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
class UserListViewModel : BindableBase
{
private IReadOnlyCollection<UserViewModel> _users;
private readonly IUserService _userService;
private readonly PropertyObserver<IUserService> _userServiceObserver;
public UserListViewModel( IUserService userService )
{
_userService = userService;
_userServiceObserver = new PropertyObserver<IUserService>( userService );
_userServiceObserver.RegisterHandler( x => x.Users, () => Users = _userService.Users.Select( x => new UserViewModel( x ) ).ToList() );
// ^^^ should use factory in real code
}
public IReadOnlyCollection<UserViewModel> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
and then register the service
Container.RegisterType<IUserService, YakUdpService>( new ContainerControlledLifetimeManager() );
in your bootstrapper or your module's initialization.

RaisePropertyChanged for Property of an Object

i have a model user:
public class User
{
public string Name { get; set; }
public int Level { get; set; }
}
in the view:
<TextBox Text="{Binding NewUser.Name}"/>
<TextBox Text="{Binding NewUser.Level}"/>
and the property in the VM:
public User NewUser
{
get { return _newUser; }
set
{
if (_newUser == value)
return;
_newUser = value;
RaisePropertyChanged("NewUser");
}
}
this code does update the property:
NewUser = new User() { Name = "test", Level = 1 };
this code does not:
NewUser.Name = "test";
what am i doing wrong? i'm using mvvm light.
When setting NewUser.Name, the RaisePropertyChanged on the ViewModel is not called and therefore no PropertyChangedEvent is fired.
In general you should have a good reason to expose model classes directly in your ViewModel, as you do here (Expose a User model as a public property in your ViewModel). This basically violates the separation of concerns between Models and ViewModels, for which MVVM is designed. Though it seems academic, my experience is that it is really worth it to stay clean here, as in most real-world cases the ViewModels tend to become more complex over time and contain functionality that you don't want to have in your model (like INPC implementations, btw).
Although it involves a bit more coding, you should implement a nested ViewModel here. Here's a bit of code to get you started:
public class ParentViewModel : NotifyingObject
{
private UserViewModel _user;
// This is the property to bind to
public UserViewModel User
{
get { return _user; }
private set
{
_user = value;
RaisePropertyChanged(() => User);
}
}
public ParentViewModel()
{
// Wrap the new instance in a ViewModel
var newUser = new User {Name = "Test"};
User = new UserViewModel(newUser);
}
}
This is the extra ViewModel in which the User model class is wrapped:
public class UserViewModel : NotifyingObject
{
/// <summary>
/// The model is private here and not exposed to the view
/// </summary>
private readonly User _model;
public string Name
{
get { return _model.Name; }
set
{
_model.Name = value;
RaisePropertyChanged(() => Name);
}
}
public UserViewModel(User model)
{
_model = model;
}
}
This is your model class. There is no need to implement INotifyPropertyChanged.
public class User
{
public string Name { get; set; }
}
You did not implement INotifyPropertyChanged for your User class. So changing the property NewUser by assignment will trigger the UI, setting the property Name by assignment will not.
If you follow your pattern, this:
public string Name { get; set; }
should in the end look like this:
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged("Name");
}
}

Resources