C# MVVM - shared object - NotifyOfPropertyChange - c#

I'm doing a intro c# MVVM project (Caliburn.Micro) and I'm injecting a "job" object into each viewmodel so that the job information is accessible everywhere.
I was hoping that changes to the injected object, which might be affected by a method in one viewmodel, would be reflected in all viewmodels, and this appears to be the case, however how would the bound properties associated with that injected object be updated?
For eg. below: The instance of JobClass is passed around and is changed by the MenuBarViewModel. How would one trigger the NotifyOnPropertyChange() in the property in the MainPanelViewModel so a bound xaml control in the associated view would pick up on the changes to the underlying member.
Or is this not how is should work. Is this what the event aggregator is supposed to manage? I was hoping that passing around the job would simplify things, or is that the global variable issue... Any tips would be welcome!
Hanuman....
// MainWindowViewModel
public class MainWindowViewModel
{
private JobClass _jobClass;
private readonly IEventAggregator _eventAggregator;
public MenuBarViewModel MenuBarViewModel { get; set; }
public MainPanelViewModel MainPanelViewModel { get; set; }
public MainWindowViewModel()
{
_eventAggregator = new EventAggregator();
_jobClass= new JobClass ();
this.MenuBarViewModel = new MenuBarViewModel(_eventAggregator, _jobClass);
this.MainPanelViewModel = new MainPanelViewModel(_eventAggregator, _jobClass);
}
}
// MenuBarViewModel
public class MenuBarViewModel: PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public MenuBarViewModel(IEventAggregator eventAggregator, JobClass jobClass)
{
_events = eventAggregator;
_jobClass = JobClass;
}
public SomeMethod()
{
_jobClass.MethodToAddSomedata();
}
// MainPanelViewModel
public class MainPanelViewModel : PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public JobClass JobCase
{
get { return _jobClass; }
set
{
_jobClass= value;
NotifyOfPropertyChange(() => JobCase);
}
}
}

Related

Shared Child ViewModel To ShellViewmodel on caliburn.Micro

How does a child ViewModel share DataGrid.selectedItem information to parent ViewModel in Caliburn.Micro?
You can make use of Event Aggregators for the purpose. In your current Scenario, you could start by declaring a Message object which would be used to pass the information between the Child and Parent View Models.
public class SelectedItemChangedMessage<T>
{
public T SelectedItem;
}
To publish the message, you can use EventAggregator class. For example, you could do the following from ChildViewModel.
public class ChildViewModel
{
private IEventAggregator _eventAggregator;
public ChildViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public void Change()
{
_eventAggregator.PublishOnUIThread(new SelectedItemChangedMessage<string>(){ SelectedItem = selectedItem });
}
}
And then, in your ParentViewModel, you would need to subscribe to EventAggregator
public class ShellViewModel:Screen, IHandle<SelectedItemChangedMessage<string>>
{
private IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void Handle(SelectedItemChangedMessage<string> message)
{
Debug.WriteLine($"Item Changed, current selection : {message}");
}
}

Why my Subscribe method is not called when using Prism EventAggregator?

I am learning Prism. Few hours already I am facing a problem, when subscribing to the event, the subscription method is not called. I am using Prism and Autofac.
In the simplified example below, in MainViewModel Publish("dupa"); event is called in the ctor. And on button click UpdateWindow is opened. In backend of the window is created instance of UpdateViewModel.
Inside of update VM ctor is ran, but after Subscribe(UpdateName); the UpdateName is not executed, because of some reason that I do not understand.
Complete code:
public class MainViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator; //Prism
_eventAggregator.GetEvent<UpdateNameEvent>().Publish("dupa");
OnOpenCommand = new DelegateCommand(OnOpenWin);
}
public void OnOpenWin(object obj)
{
UpdateWindow win = new UpdateWindow();
win.Show();
}
public ICommand OnOpenCommand { get; private set; }
}
public class UpdateViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public UpdateViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator; //Prism
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
}
private void UpdateName(string name)
{
this.Name = name; //is not called at all
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
}
public partial class UpdateWindow : Window
{
public UpdateWindow()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
UpdateViewModel vm = container.Resolve<UpdateViewModel>();
InitializeComponent();
DataContext = vm;
}
}
UPDATE
After investigating, I notied, that when subscribing to the events like this, it works fine:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
When subscribing with used injected eventAggregator, it does not work:
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
And EventAggregator is registered by Autofac as follows:
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>().SingleInstance();
I do not understand why this dependency does not work?
I do not understand why this dependency does not work?
Because you create a new EventAggregator for the UpdateViewModel.
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
UpdateViewModel vm = container.Resolve<UpdateViewModel>();
This looks as if a new container is created for the UpdateWindow, and the new container will have a new - that is, a different - EventAggregator. Those two will not send events to each other, of course.
So the solution is to use one single container to resolve all your stuff. This is what happens when you use the static Utility. You should avoid using a service-locator like this. Have a look at the ViewModelLocator, which makes it really easy to create the view models for a given view, for example, or pass the container to the UpdateWindow when it is created (somewhat ugly, too, though).

How does a child ViewModel prompt the parent ViewModel to navigate away in Caliburn.Micro?

In Caliburn.Micro I have a Shell ViewModel that has 3 IShell properties corresponding to 3 content controls in the associated View. They are 'Full', 'List' and 'Detail'. 'Full' sits above the other two and is as wide as the host Form. 'List' is on the left hand 1 row down and 'Detail' is in the same row as 'List' 1 column to the right.
When the app starts, a Login ViewModel is bound to 'Full' and nothing is bound to the other two. The screen shows only the Login screen. The user should login, and when complete the 'Full' content control should switch from displaying the Login ViewModel, to an AccountViewModel.
For that to work I need the LoginViewModel to tell the ShellViewModel (its parent) to navigate to AccountViewModel.
How do I do that?
public class ShellViewModel : Screen
{
#region Fields
private string _title = "License Manager";
private Conductor<IScreen> _fullFrameConductor;
private Conductor<IScreen> _listFrameConductor;
private Conductor<IScreen> _detailFrameConductor;
#endregion
public ShellViewModel()
{
_fullFrameConductor = new Conductor<IScreen>();
_listFrameConductor = new Conductor<IScreen>();
_detailFrameConductor = new Conductor<IScreen>();
FullFrame = Framework.GetContainer().Resolve<LoginViewModel>();
}
#region Properties
public string Title { get => _title; set => _title = value; }
public IScreen FullFrame
{
get { return _fullFrameConductor.ActiveItem; }
set {
_fullFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(FullFrame));
}
}
public IScreen ListFrame
{
get { return _listFrameConductor.ActiveItem; }
set {
_listFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(ListFrame));
}
}
public IScreen DetailFrame
{
get { return _detailFrameConductor.ActiveItem; }
set {
_detailFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(DetailFrame));
}
}
#endregion
#region Commands
public void ShowProducts()
{
ListFrame = Framework.GetContainer().Resolve<ProductListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<ProductViewModel>();
}
public void ShowLicenses()
{
ListFrame = Framework.GetContainer().Resolve<LicenseListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<LicenseViewModel>();
}
#endregion
}
public class LicenseViewModel : Screen
{
public void Login()
{
// This should process the login and then tell the Shell it is done
// then the shell should navigate to the Account ViewModel sharing
// the user info with the AccountViewModel via a memory cache
// How do I alert the screen ViewModel causing it to close this viewmodel
// without causing a threading problem?
}
}
You can make use of Event Aggregator to communicate between LoginViewModel and ShellViewModel. You can read more on Event Aggregator here.
First, you need to create a Message Class
public class AuthenticationSuccessMessage
{
public bool IsValidLogin{get;set;}
}
Then next step is to use EventAggregator to notify the ShellViewModel from the LicenseViewModel .
private IEventAggregator _eventAggregator;
public LicenseViewModel (IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public void Login()
{
_eventAggregator.PublishOnUIThread(new AuthenticationSuccessMessage{IsValidLogin=true});
}
The final step is to subscribe to the Events in ShellViewModel.
public class ShellViewModel:Screen, IHandle<AuthenticationSuccessMessage>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel:Screen(IEventAggregator eventAggregator) {
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
void Handle<AuthenticationSuccessMessage>(AuthenticationSuccessMessage message)
{
if(message.IsValidLogin)
{
// Do Task
}
}
}
You can read more on Event Aggregators here.
Update : Do not forget to subscribe to Event Aggregator in ShellViewModel.

Is this a correct usage of Event Aggregation?

Dear Stackoverflow users,
I am currently developing a application that uses the MVVM pattern together with Caliburn micro.
I faced my first situation where I needed a dialog to be opened which would do some modifications to a ObservableCollection that needed to be visible on my main viewmodel.
I found out about event aggregation for my first time and thought that it would suite my situation. I am however not quite sure if event aggregation is the best solution or if I am using correctly... Therefore my question.
Please keep in mind that the namespaces are left there intentionally incase I got it all wrong.
The setup looks like this:
namespace Company.Product.Presentation.Core.Events
{
public class PersonCollectionChanged
{
[CanBeNull]
public ObservableCollection<Person> PersonCollection { get; set; }
public PersonCollectionChanged(ObservableCollection<Person> personCollection)
{
PersonCollection = personCollection;
}
}
}
ViewModel for the dialog
namespace Company.Product.Presentation.Modules.Selection
{
public class SelectionViewModel : Screen
{
private readonly IViewModelLoader _viewModelLoader;
private readonly IEventAggregator _eventAggregator;
private readonly ObservableCollection<Person> _tempPersonCollection;
private readonly PersonCollectionChanged _personCollectionChanged;
public SelectionViewModel(IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
PersonCollectionChanged personCollectionChanged,
ObservableCollection<Person> tempPersonCollection)
{
_viewModelLoader = viewModelLoader;
_eventAggregator = eventAggregator;
_personCollectionChanged = personCollectionChanged;
_tempPersonCollection = tempPersonCollection;
AddPerson(new Person{Name = "Zatixiz"});
_personCollectionChanged.PersonCollection = _tempPersonCollection;
_eventAggregator.PublishOnUIThread(_personCollectionChanged);
}
internal void AddPerson(Person person)
{
_tempPersonCollection.Add(person);
}
}
}
Main viewmodel subscribing to the event:
namespace Company.Product.Presentation.Modules.Main
{
public class MainViewModel : BaseViewModel, IHandle<PersonCollectionChanged>
{
private ObservableCollection<Person> _mainPersonCollection;
public MainViewModel(
IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
ObservableCollection<Person> mainPersonCollection) : base(viewModelLoader)
{
_mainPersonCOllection = mainPersonCollection;
eventAggregator.Subscribe(this);
}
public void Handle(PersonCollectionChanged message)
{
_mainPersonCollection = message;
}
}
}

ICommand with ViewModel dependency

I am searching a pattern to keep SOLID principles in my application when I use ICommand. Basically my problem is the command execution has a dependency with the view model but at the same time the view model has a dependency with the command (I inject them by constructor). I would like to keep the viewmodel with properties only, so this is an example of my current implementation:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel()
{
}
public void SetCommand(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readOnly MyViewModel myViewModel;
public MyViewModelCommandManager(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public ExectueMyCommand()
{
MessageBox.Show(this.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModel viewModel = new MyViewModel();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
viewModel.SetCommand(myCommand);
return viewModel;
}
}
So, to avoid use the SetCommand method. I have thought two solutions but I don't know if they are elegant.
The first one is to move the viewmodel dependency from the constructor to the method updating the code in this way:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand(MyViewModel viewModel);
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
public MyViewModelCommandManager()
{
....
}
public ExectueMyCommand(MyViewModel viewModel)
{
MessageBox.Show(myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);
ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
return viewModel;
}
}
Of course, the xaml code will use CommandParameter:
<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />
Other solution I have thought is to use a trick creating a Wrapper of the viewModel and the commandManager have a dependency with the Wrapper instead of the viewModel:
internal class MyViewModelCommandContext
{
public MyViewModel ViewModel { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyViewModelCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModelCommandContext context = new MyViewModelCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
context.ViewModel = viewModel;
return viewModel;
}
}
In my opinion the first one is the best solution for this problem, what do you think is the best solution. Would you apply another solution?
IMHO both solutions are overly complicated. SOLID is great, KISS is better.
Your MyViewModelCommandManager is currently directly coupled to MyViewModel since it needs the latter's Message, so what is the advantage of having them separate? Why not simply implement the command inside MyViewModel?
If this would entail injecting too many dependencies into MyViewModel, then think about what you actually need the command to do, and abstract away everything else that isn't needed.
The command displays a message.
The message is held by MyViewModel
You want to display the message outside MyViewModel (maybe other viewmodels also need to display a message and you want to reuse the code?)
So all you really need is some kind of notification from MyViewModel that it wants to display a message, or that something has occured that would result in a message being displayed.
Possible solutions:
Inject an IMessageDisplayService to MyViewModel. MyViewModel calls it with the message.
Inject a callback to MyViewModel similar to above.
Have MyViewModel raise an event with the message as an EventArg.
The inferred responsibilities of the above solutions are subtly different.
means that MyViewModel is in charge. It wants to display a message.
is less explicit. MyViewModel knows it needs to call the callback, but doesn't really know or care what it does.
is like 2 but even more decoupled. Multiple things can subscribe or unsubscribe to the event but MyViewModel remains blissfully ignorant.
All three of these mean that the thing displaying the message has no need to know about MyViewModel. You have decoupled them. It's MyViewModelFactory that does any wiring up required.
Thanks for your opinions.
I understand you when you say I am creating a complex pattern, but in a big project with a big developers team, if there is not clear patterns with split responsabilities, the code maintenance could be impossible to perform.
Reading you and your third solution I have thought one possible solution. It seems complexity but, in my opinion, improves the code quality. I will create a commandContext, which only have the viewmodel properties needed for the code, avoiding to have all viewmodel in the command manager. Also I will create a class whose responsability is to mantain the context updated when the viewmodel changes. This is the possible code:
internal class MyCommandContext
{
public string Message { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public string OtherProperty { get; set; }
public ObservableCollection<MyChildViewModel> Childs { get; set; }
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.Message);
}
}
internal interface IMyViewModelCommandSynchronizer
{
void Initialize();
}
internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
private readOnly MyViewModel viewModel;
private readOnly MyCommandContext context;
MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
{
this.viewModel = viewModel;
this.context = context;
}
public void Initialize()
{
this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Message")
{
this.context.Message = this.viewModel.Message;
}
}
// Dispose code to deattach the events.
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyCommandContext context = new MyCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
synchronizer.Initialize();
return viewModel;
}
}

Categories

Resources