Question: I have two viewmodels that share a service with a list. My question is how to setup the notification so that both viewmodels know when this list is changed. Description below and code of where i'm at.
I found this example HERE that looks right for what I'm trying to do, but I have a question regarding how to be notified in my viewmodels of a change in my service. I'll add some code I've mocked up to see if I'm on the right track. I'm using WPF/MVVM Light.
First part is a service with a interface that will have a list of data, in this example I'm using a string list. I want a property in both viewmodels to have access to this list of data, and be notified when it's changed. I think what's throwing me is the interface IOC into my viewmodels. I'm understanding more and more why this is good, but I'm still wrapping my mind around it and I'm not sure how to setup the notification when the list is changed in the service. If my service was not injected I might have setup a event or property that my viewmodel property would access get/set but injecting my service does not expose my public fields, just methods. This is new to me so it's very likely i'm not understanding this correctly or missing something.
I used a List in my service instead of a ObservableCollection based on some reading I've done warning against using the ObservableCollection here. Thanks you for any help.
public class MyService : IMyService
{
private List<string> myList = new List<string>();
public List<string> getMyList()
{
return this.myList;
}
public void setMyList(List<string> value)
{
this.myList = value;
}
public void addValue(string value)
{
this.myList.Add(value);
}
public void insertValue(int index, string value)
{
this.myList.Insert(index, value);
}
}
public class MyViewModelOne : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelOne(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
public class MyViewModelTwo : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelTwo(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
From what I understood about your problem, what you need essentially is that your INotifyPropertyChanged implementation at your service level and the list my list to be an ObservableCollection being injected from the service.
Now if there is a notification change it would be directly on the service and hence no explicit need of handling.
Your binding could look like "{Binding MyService.MyProperty}"
I got this working two different ways, I went with the first example because I think it's easier to follow in the code.
This came up because I had a control in my mainview with related code that was growing and I realized I wanted the same control/behavior in a separate view that would use the same data/control for a different purpose.
I did not want to duplicate this control/template/code in two places so I made it into a User Control. I then nest the user control in my views. The user control has it's own VM. The main view updates the service with new data, and the nested control listens on a event to know when there is new data.
Still very new to MVVM thinking so please feel free to point out in issues with either of these examples.
Example using a service with eventhandler.
public interface IMyInterface
{
event EventHandler OnSomeEvent;
void addSomeData(string value);
void getSomeData();
}
public class MyInterface: IMyInterface
{
public event EventHandler OnSomeEvent = delegate { };
public void addSomeData(string value)
{
// do stuff
OnSomeEvent();
}
public string getSomeData()
{
return "some data";
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void testEvent()
{
this.myInterface.addSomeData("test data");
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void myInterface_OnSomeEvent(object sender, System.EventArgs e)
{
// do stuff
}
}
Example using MVVM Light Messaging
public class EventDataSource
{
public string MyItemSource { get; set; }
public EventDataSource()
{
MyItemSource = string.Empty;
}
}
// Message class
public class MyDataSourceMessage : MessageBase
{
public EventDataSource MyItemSource { get; set; }
public MyDataSourceMessage(EventDataSource myItemSource)
{
MyItemSource = myItemSource;
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
public NotifyViewModel() {}
void testMessage()
{
EventDataSource msg = new EventDataSource() { MyItemSource = "magic message!"};
Messenger.Default.Send(new MyDataSourceMessage(msg as EventDataSource));
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
public NotifyViewModel()
{
Messenger.Default.Register<MyDataSourceMessage>(this, (action) => ReceiveMessage(action));
}
private ObservableCollection<string> myProperty = new ObservableCollection<string>();
public ObservableCollection<string> MyProperty
{
get { return myProperty; }
set
{
myProperty: = value;
RaisePropertyChanged(() => MyProperty);
}
}
void ReceiveMessage(MyDataSourceMessage action)
{
// do something with the data
MyProperty.Add(action.DGItemSource.ItemSource);
}
}
Ok let me try to shed some light on this. First of all, change notification is not meant to pass information between view models, it is meant to notify the view itself that the a property of the view model has changed.
There are a few methods for view models to issue change notifications to views:
INotifyPropertyChanged interface
INotifyCollectionChanged interface
A custom event with the name of the property suffixed with Changed (e.g. an event called MyPropChanged for a property called MyProp)
Having said all that, it is still possible for one view model to subscribe to the events generated by the above methods, and if you really need to, you may of course.
EDIT:
Check this link for a description on item number 3 above.
Related
Still trying to learn MVVM and WPF here.
I'm trying to create a complex view model EditArticleViewModel. It has some code that is repeated for similar controls and so I've moved the repeating code into another class. Then, I've added several instances of that other class into EditArticleViewModel.
I will set an instance of EditArticleViewModel as my window's DataContext. And I will bind to things like Categories.Items and Subcategories.SelectedItem.
public class CategoryView
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class SubcategoryView
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class EditArticleViewModel : INotifyPropertyChanged
{
public CategoryView Categories { get; private set; }
public SubcategoryView Subcategories { get; private set; }
public EditArticleViewModel()
{
Categories = new CategoryView();
SubcategoryView Subcategories new SubcategoryView();
}
// Additional properties and methods here
}
As you can see, my EditArticleViewModel class implements INotifyPropertyChanged so that I can notify the visual elements when something has changed.
My question is about how I notify visual elements about changes within CategoryView and SubcategoryView. Is there a way to notify the window about changes within these classes directly? Or must I raise an event from each class and have EditArticleViewModel handle that event in order to send the appropriate notification?
Any tips appreciated.
There should only be one ViewModel per View, with an extend that primary ViewModel can contain other "ViewModels".
So when you set DataContext to your primary ViewModel all the content of it will be have a subscription to NotifyPropertyChanged event, thus implementing INotifyPropertyChanged interface in other derived ViewModel will be notified.
I would suggest implementing a base class with INotifyPropertyChanged interface which you could derive from in your other ViewModels.
By having this alteration you should solve the problem you are having:
public class ObservableViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class CategoryView : ObservableViewModelBase
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class SubcategoryView : ObservableViewModelBase
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class EditArticleView : ObservableViewModelBase
{
public CategoryView Categories { get; set; } = new CategoryView();
public SubcategoryView Subcategories { get; set; } = new SubcategoryView();
}
Regarding ObservableCollection. It will notify view to change only when you add/remove items but it does not notify when content is changed. To update view on item content change you should have something like that:
public class GridRowItemViewModel : ObservableViewModelBase // From previous example.
{
private string _sampleProp;
public string SampleProp
{
get
{
return _sampleProp;
}
set
{
_sampleProp = value;
OnPropertyChanged();
}
}
}
And thus your Main ViewModel should look something like this:
public class MainViewModel : ObservableViewModelBase // This is your DataContext.
{
public ObservableCollection<GridRowItemViewModel> GridCollection { get; set; }
}
EDIT: You cannot bind to fields, WPF does not resolve fields. It can only handle properties. So by creating plain fields of child ViewModels you are getting no where. Change these into properties and you will be able to access its content in the View by the property name.
I was given this sample code when creating a new MVVM light(WPF451) project and it made me confusing.
DataItem.cs:
public class DataItem
{
public string Title { get; private set; }
public DataItem(string title)
{
Title = title;
}
}
This class declares a set of properties that is needed in the ViewModel. It's used in the Model layer DataService, which provides data to the VM in its constructor.
DataService.cs
public class DataService : IDataService
{
public void GetData(Action<DataItem, Exception> callback)
{
// Use this to connect to the actual data service
var item = new DataItem("Welcome to MVVM Light");
callback(item, null);
}
}
I thought it would be used in the VM as well to hold properties, like this:
public DataItem Data { get; set; }
but instead, the MVVM light developer decided to re-declare the properties in the VM.
MainViewModel.cs:
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get{ return _welcomeTitle; }
set{ Set(ref _welcomeTitle, value); }
}
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
WelcomeTitle = item.Title;
});
}
}
I couldn't understand why they implemented like this. Yes, it reduces redundant INotifyPropertyChanged implemented object so it uses less resource. However, if I had to implement tons of properties to the VM, I'll have to write properties on both VM and DataItem, and also when I want to add or delete properties I'll have to edit both of them.
Couldn't I just hold a DataItem property in the VM? Or am I missing something?
DataItem simply represents the Model in this case. If the Model is an entity that cannot be modified (database auto-generated POCO), this scenario would work.
Yes, you will have to have each applicable Model property in your ViewModel so that it can RaisePropertyChanged, and yes, this is more 'work', but it provides an abstraction between the two.
Some people are okay with modifying the Model to have it implement INotiftyPropertyChanged, others believe the Model shouldn't and all the work should be done in the ViewModel (which is what is being done in this case).
I have a BaseFormViewModel class which some of ViewModels inherit from. It inherits from another base class called BaseViewModel which just contains implementation of INotifyPropertyChanged interface.
The BaseFormViewModel contains several methods which I intend to be overriden in the derived classes:
public class BaseFormViewModel : BaseViewModel {
public BaseFormViewModel() {
_InitiateParameterAnswer = new Command(param => RaiseInitiateParameterAnswer(param));
}
protected Command _InitiateParameterAnswer;
public Command InitiateParameterAnswer {
get {
return _InitiateParameterAnswer;
}
}
protected virtual void RaiseInitiateParameterAnswer(object values) {
//Implementation
}
protected virtual Parameter SetAuditParameter(ParameterOption parameterOption, Guid parameterId, string remarks, string description, int weight) {
//Implementation
}
}
Here's one of the derived classes:
public class FSCGeneralProductionProcessMainViewModel : BaseFormViewModel {
public FSCGeneralProductionProcessMainViewModel() {
}
protected override void RaiseInitiateParameterAnswer(object values) {
//Implementation
}
protected override Parameter SetAuditParameter(ParameterOption parameterOption, Guid parameterId, string remarks, string description, int weight) {
//Implementation
}
However, whenever I invoke the InitiateParameterAnswer Command, the base class methods are being called. What am I missing here?
UPDATE:
Thanks for the feedbacks. With #Muds comment, I realised that I am calling each form (View) via Reflection and downcasting its DataContext (ViewModel) to BaseFormViewModel:
//Create an instance of the corresponding form using reflection
var instance = Activator.CreateInstance(Type.GetType(formName, true));
//Set the FormId
var viewModel = ((UserControl)instance).DataContext as BaseFormViewModel;
viewModel.FormId = formId;
viewModel..AuditId = auditId;
(The reason I have to is to set the value of base properties FormId and AuditId as illustrated above).
UPDATE 2:
The Command class is implemented as follows:
public class Command : ICommand {
public Command(Action action, bool canExecute = true) {
this.action = action;
this.canExecute = canExecute;
}
//ICommand Interface implementation here
}
UPDATE 3:
I instantiate the ViewModel inside the Form's constructor:
public partial class FSCStationProductionProcessMainView : UserControl {
public FSCStationProductionProcessMainView () {
InitializeComponent();
InitializeViewModel();
}
private void InitializeViewModel () {
this.DataContext = new FSCStationProductionProcessMainViewModel();
}
}
The simple answer would be to mark your BaseFormViewModel RaiseInitiateParameterAnswer method implementation as 'abstract' and force all derived classes to provide an implementation.
But in general this design seems overly complex:
Do you really need to so many levels of inheritence - it will be confusing and difficult in the long term to support and maintain.
Inheritence in my opinion should be used sparingly in object orientiated design.
I'm currently converting the SideBarDemo to C# and MonoMac in combination with ReactiveUI.
I have two subclasses of NSTextField and NSTableCellView that are views for a common view model class.
My problem is that I do not know how to implement these sub classes so that data binding works.
How does a good implementation of such subclasses looks like?
In the following you can see my current state. I know that the binding, that is created in the constructor won't work, because ViewModel is an ordinary property. However, I could not figure out which interfaces I should implement best.
[Register("MainCellView")]
public class MainCellView : NSTableCellView, IViewFor<TreeItemViewModel>
{
public MainCellView ()
{
this.OneWayBind (ViewModel, x => x.Name, x => x.TextField.StringValue);
}
public MainCellView(IntPtr ptr) : base(ptr) { }
public TreeItemViewModel ViewModel { get; set; }
object IViewFor.ViewModel
{
get { return this.ViewModel; }
set { this.ViewModel = (TreeItemViewModel)value; }
}
}
[Register("HeaderCellView")]
public class HeaderCellView : NSTextField, IViewFor<TreeItemViewModel>
{
public HeaderCellView ()
{
this.OneWayBind (ViewModel, x => x.Name, x => x.StringValue);
}
public HeaderCellView(IntPtr ptr) : base(ptr) { }
TreeItemViewModel _vm;
public TreeItemViewModel ViewModel { get; set }
object IViewFor.ViewModel
{
get { return this.ViewModel; }
set { this.ViewModel = (TreeItemViewModel)value; }
}
}
Thx a lot in advance,
Jens
However, I could not figure out which interfaces I should implement best.
If ReactiveUI doesn't have a built-in subclass that helps you out for a class, you should implement INotifyPropertyChanged for your class, and signal when ViewModel changes. That should be enough to get bindings working!
I'm playing around with Caliburn.Micro and have a very simple application now.
It has an AppView, which actually has a ContentControl for a NavigationBar, an InnerView and a StatusBar.
Now I want to handle Navigation between different inner views.
Right now I use the eventaggregator to publish a NavigationEvent, which should
switch the innerview of the mainwindow to another view.
Here is my call to Publish (all InnerViews have the same baseclass which has an IEventAggregator)
public void NavigateOverview()
{
base._eventAggregator.Publish(new NavigateEvent("OverviewViewModel"));
}
Right now I pass a string to the AppViewModel, which handles the NavigateEvent:
public void Handle(NavigateEvent navigate)
{
InnerViewModel target;
switch (navigate.TargetViewModel)
{
case "SelectProjectViewModel":
{
target = new SelectProjectViewModel(_eventAggregator);
break;
}
case "OverviewViewModel":
{
target = new OverviewViewModel(_eventAggregator);
break;
}
default:
{
throw new InvalidOperationException("no target type found");
}
}
this.CurrentInnerViewModel = target;
}
Passing strings works, but is errorprone and not very clean.
What is the Caliburn way of handling that?
Is that what a Conductor should do?
Why not just pass a type instead? That way there are no magic strings
e.g.
public void NavigateOverview()
{
base._eventAggregator.Publish(new NavigateEvent(typeof(OverviewViewModel)));
}
then:
public void Handle(NavigateEvent navigate)
{
InnerViewModel target;
// EDIT: Remove the case (only works with integral types so you can't use typeof etc)
// but you could do this with standard conditional logic
this.CurrentInnerViewModel = target;
}
Edit 2:
Ok since you asked about building into CMs IoC, here is an example for using the IoC with Castle Windsor and a solution for passing additional parameters to navigation (borrowing from EventAggregator)
The bootstrapper just needs a few bits and pieces to config the container:
public class AppBootstrapper : Bootstrapper<ShellViewModel>
{
// The Castle Windsor container
private IWindsorContainer _container;
protected override void Configure()
{
base.Configure();
// Create the container, install from the current assembly (installer code shown in next section below)
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
}
// Matches up with Windsors ResolveAll nicely
protected override IEnumerable<object> GetAllInstances(Type service)
{
return (IEnumerable<object>)_container.ResolveAll(service);
}
// Matches up with Windsors Resolve
protected override object GetInstance(Type service, string key)
{
return string.IsNullOrEmpty(key) ? _container.Resolve(service) : _container.Resolve(key, service);
}
// Windsor doesn't do property injection by default, but it's easy enough to get working:
protected override void BuildUp(object instance)
{
// Get all writable public properties on the instance we will inject into
instance.GetType().GetProperties().Where(property => property.CanWrite && property.PropertyType.IsPublic)
// Make sure we have a matching service type to inject by looking at what's registered in the container
.Where(property => _container.Kernel.HasComponent(property.PropertyType))
// ...and for each one inject the instance
.ForEach(property => property.SetValue(instance, _container.Resolve(property.PropertyType), null));
}
}
The Windsor Installer for CM will probably be as simple as:
public class CaliburnMicroInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
// Register the window manager
container.Register(Component.For<IWindowManager>().ImplementedBy<WindowManager>());
// Register the event aggregator
container.Register(Component.For<IEventAggregator>().ImplementedBy<EventAggregator>());
}
}
I also have a navigation service interface to aid with application navigation:
public interface INavigationService
{
void Navigate(Type viewModelType, object modelParams);
}
Which is implemented by NavigationService (show you that in a sec)
That needs a Windsor installer too:
public class NavigationInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<INavigationService>().ImplementedBy<NavigationService>());
}
}
The NavigationService works much like EventAggregator in that the type that exposes navigation arguments should implement a generic interface for each argument class that it can receive...
The interface looks like this (borrowing heavily from EventAggregator):
// This is just to help with some reflection stuff
public interface IViewModelParams { }
public interface IViewModelParams<T> : IViewModelParams
{
// It contains a single method which will pass arguments to the viewmodel after the nav service has instantiated it from the container
void ProcessParameters(T modelParams);
}
example:
public class ExampleViewModel : Screen,
// We can navigate to this using DefaultNavigationArgs...
IViewModelParams<DefaultNavigationArgs>,
// or SomeNavigationArgs, both of which are nested classes...
IViewModelParams<SomeOtherNavigationArgs>
{
public class DefaultNavigationArgs
{
public string Value { get; private set; }
public DefaultNavigationArgs(string value)
{
Value = value;
}
}
public class OtherNavigationArgs
{
public int Value { get; private set; }
public DefaultNavigationArgs(int value)
{
Value = value;
}
}
public void ProcessParameters(DefaultNavigationArgs modelParams)
{
// Do something with args
DisplayName = modelParams.Value;
}
public void ProcessParameters(OtherNavigationArgs modelParams)
{
// Do something with args. this time they are int!
DisplayName = modelParams.Value.ToString();
}
}
This leads to some strongly typed navigation (e.g. refactor friendly!)
NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.DefaultNavigationArgs("hello"));
or
NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.OtherNavigationArgs(15));
It also means that the ViewModel is still in control of it's own navigation parameters
Ok back to Windsor for a sec; obviously we need to install any views from our views namespace - Windsors fluent API makes this pretty easy:
public class ViewInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
// The 'true' here on the InSameNamespaceAs causes windsor to look in all sub namespaces too
container.Register(Classes.FromThisAssembly().InSameNamespaceAs<ShellViewModel>(true));
}
}
Ok now the NavigationService implementation:
public class NavigationService : INavigationService
{
// Depends on the aggregator - this is how the shell or any interested VMs will receive
// notifications that the user wants to navigate to someplace else
private IEventAggregator _aggregator;
public NavigationService(IEventAggregator aggregator)
{
_aggregator = aggregator;
}
// And the navigate method goes:
public void Navigate(Type viewModelType, object modelParams)
{
// Resolve the viewmodel type from the container
var viewModel = IoC.GetInstance(viewModelType, null);
// Inject any props by passing through IoC buildup
IoC.BuildUp(viewModel);
// Check if the viewmodel implements IViewModelParams and call accordingly
var interfaces = viewModel.GetType().GetInterfaces()
.Where(x => typeof(IViewModelParams).IsAssignableFrom(x) && x.IsGenericType);
// Loop through interfaces and find one that matches the generic signature based on modelParams...
foreach (var #interface in interfaces)
{
var type = #interface.GetGenericArguments()[0];
var method = #interface.GetMethod("ProcessParameters");
if (type.IsAssignableFrom(modelParams.GetType()))
{
// If we found one, invoke the method to run ProcessParameters(modelParams)
method.Invoke(viewModel, new object[] { modelParams });
}
}
// Publish an aggregator event to let the shell/other VMs know to change their active view
_aggregator.Publish(new NavigationEventMessage(viewModel));
}
}
Now the shell can just handle the aggregator message and activate the new injected and additionally configured VM
public class ShellViewModel : Conductor<IScreen>, IHandle<NavigationEventMessage>
{
private IEventAggregator _aggregator;
private INavigationService _navigationService;
public ShellViewModel(IEventAggregator aggregator, INavigationService _navigationService)
{
_aggregator = aggregator;
_aggregator.Subscribe(this);
_navigationService.Navigate(typeof (OneSubViewModel), null);
}
public void Handle(NavigationEventMessage message)
{
ActivateItem(message.ViewModel);
}
}
Actually I constrain the navigation to just IScreen implementations so my NavigationEventMessage actually looks like this:
public class NavigationEventMessage
{
public IScreen ViewModel { get; private set; }
public NavigationEventMessage(IScreen viewModel)
{
ViewModel = viewModel;
}
}
This is because I always want lifecycle for my child viewmodels