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}");
}
}
Related
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);
}
}
}
I'm getting started with MVVM using Caliburn.Micro and the following tutorial: https://www.youtube.com/watch?v=laPFq3Fhs8k
The tutorial shows how to set up a ShellView and open one 'child' View at a time in a <ContentControl>. I have all of this working fine, but now I want to pass some parameters from one child view to the constructor of a new child view. My first child view is called FindQuotaView and the second is called QuotaView. Basically I want the FindQuotaView to take input and display the result on QuotaView. As the <ContentControl> is in the ShellView, I figure I need FindQuotaView to "ask" the ShellView to load QuotaView.
After some searching I discovered the EventAggregator and I'm using that to notify the ShellView when I want to change the loaded view. At first I thought of raising two events, the first telling ShellView to load the QuotaView and the second to pass the parameters to the QuotaView. But my understanding from the video is that once the new view model is instantiated the existing view model is destroyed, so the second event wouldn't be raised. Instead I figured I would need to pass the parameters to the ShellView first. Here is what I have at the moment:
Models
public class LaunchRequest
{
public LaunchRequest(Type viewModel, List<object> parameters)
{
ViewModel = (IScreen)viewModel;
Parameters = parameters;
}
public IScreen ViewModel { get; set; }
public List<object> Parameters { get; set; }
}
ViewModels
ShellViewModel
public class ShellViewModel : Conductor<object>, IHandle<object>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel()
{
_eventAggregator = new EventAggregator();
_eventAggregator.Subscribe(this);
LoadViewModel(new LaunchRequest(typeof(FindQuotaViewModel), new List<object> { _eventAggregator }));
}
public void LoadViewModel(LaunchRequest launchRequest)
{
object[] args = { _eventAggregator, launchRequest.Parameters };
ActivateItem(Activator.CreateInstance(launchRequest.ViewModel.GetType(), args));
//Previous Method
//switch (viewModel)
//{
// case "FindQuota":
// ActivateItem(new FindQuotaViewModel(_eventAggregator));
// break;
// case "Quota":
// ActivateItem(new QuotaViewModel(_eventAggregator));
// break;
//}
}
//Event Aggregator message handler
public void Handle(object message)
{
LaunchRequest launchRequest = message as LaunchRequest;
if (launchRequest != null)
{
LoadViewModel(launchRequest);
}
}
}
FindQuotaViewModel
class FindQuotaViewModel : Screen
{
private IEventAggregator _eventAggregator;
public FindQuotaViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
private void FindUsersHomeShare(string username)
{
using (PowerShell cs = PowerShell.Create())
{
//Unrelated code removed to shorten
//string path = result from PowerShell
//TODO: Pass the path to the QuotaViewModel
_eventAggregator.PublishOnCurrentThread(new LaunchRequest(typeof(QuotaViewModel), new List<object> {path}));
}
}
}
QuotaViewModel
class QuotaViewModel : Screen
{
private IEventAggregator _eventAggregator;
public QuotaViewModel(IEventAggregator eventAggregator, object[] args)
{
_eventAggregator = eventAggregator;
}
}
I'm currently getting an error casting QuotaViewModel to IScreen in the LaunchRequest model. I'm not quite sure why as the QuotaViewModel is derived from Screen which implements IScreen. I've also tried passing the parameter as Screen and IScreen but I can't get it working.
Could you please help me understand how to fix this, but also let me know if I'm going about this the right way? Am I over complicating things? Is there a simpler or better method to do this?
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.
I understood how does EventAggregator work in Caliburn Micro but i don't understand this:
Let's take this as an example:
ViewModel A
private IEventAggregator _eventAggregator;
public ViewModelA(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator
_eventAggregator.PublishOnUIThread("Hello");
}
ViewModel B
private IEventAggregator _eventAggregator;
public ViewModelA(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator
_eventAggregator.Subscribe(this)
}
public void Handle(string message)
{
MessageBox.Show(message + " From ViewModel B")
}
ViewModel C
private IEventAggregator _eventAggregator;
public ViewModelA(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator
_eventAggregator.Subscribe(this)
}
public void Handle(string message)
{
MessageBox.Show(message + " From ViewModel C")
}
So how can i select which viewmodel handle to use when i publish events?
So how can i select which viewmodel handle to use when i publish events?
Several view models may subscribe to the same message. This is how event aggregation works. The publisher publishes a single message without any knowledge of the number of receivers.
If you want a particular view model to be able to subscribe to an event, it should implement the IHandle<T> intertface and call the Subscribe method on the event aggregator:
public class ViewModelA : IHandle<string>
{
private readonly IEventAggregator _eventAggregator;
public ViewModelA(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void Handle(string message)
{
MessageBox.Show(message);
}
}
All view models that does this will receive the message.
I managed to send messages to particular viewmodels via Message class:
public class Message<TTarget, TValue>
{
public TValue Value { get; set; }
public Message(TValue value)
{
Value = value;
}
}
Then add to a class
IHandle<Message<ViewModel, string>>
Usage:
_eventAggregator.PublishOnUIThread(new Message<ViewModel, string>("Hello from ViewModel"));
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;
}
}
}