I have two ViewModel Classes, One of them is main ViewMode(A) and other one is dialog ViewModel(B).
So when I close B, I need to catch the event in A.
I made the event to B like below code.
public event EventHandler OnSelectEmployee;
public void SelectedEmployee(long employeeId)
{
foreach (EmployeeModel item in Employees)
if (item.id == employeeId)
{
Employee = item;
break;
}
if (OnSelectEmployee != null)
OnSelectEmployee(Employee, EventArgs.Empty);
}
and It's opened by this code from A.
private void AttemptSelectEmployee()
{
ShowViewModel<SelectEmployeeViewModel>(new { key = Customer.id });
}
I need to catch the OnSelectEmployee event in A.
How can catch the event?
In MVVMCross to communicate between viewmodels you have to use the Messenger plugin:
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=959
Basically on view model A you subscribe to a message then in model B you send that message when you want to notify A that something has happened.
Not familiar with MVVMCross, but most MVM patterns rely on a Queue to pass information between ViewModels. A simple Singleton pattern that also exposes events like a Observer pattern. Then you can subscribe and publish between each.
It looks a little backwards. The way you are trying seems like you would want a dependency injection, but i doubt you want to do that. You could however subscribe your Event in ViewModel A to a method in ViewModel B and invoke it this way.
Related
In my project I have three ViewModels (for example, ViewModelA, ViewModelB and ViewModelC).
I need to write the following logic.
The ViewModelA sends the value to ViewModelB using the EventAggregator from Prism.
The ViewModelB receives the value and sends it to ViewModelC.
The ViewModelC receives the value and doing something.
Here is the code:
// The data that will be send using the event aggregator.
class EventData : PubSubEvent<int>
{
}
class ViewModelA
{
IEventAggregator m_eventAggregator;
public ViewModelA(IEventAggregator eventAggregator)
{
m_eventAggregator = eventAggregator;
// Publish some value.
eventAggregator.GetEvent<EventData>().Publish(10);
}
}
class ViewModelB
{
IEventAggregator m_eventAggregator;
public ViewModelB(IEventAggregator eventAggregator)
{
m_eventAggregator = eventAggregator;
eventAggregator.GetEvent<EventData>().Subscribe(OnDataReceived);
}
void OnDataReceived(int value)
{
// Here I want to send the value to the ViewModelC. How can I do it?
}
}
PS: it is the part of the big project. So, please don’t suggest sending from ViewModelA to ViewModelC directly, without the ViewModelB.
The event aggregator works in a way that everybody can listen to every event. So, if you want to send from A to B (and then from B to C), you need two distinct "private" events.
A:
eventAggregator.Publish<AtoB>( value );
B:
eventAggregator.Subscribe<AtoB>( x => {
var y = process(x);
eventAggregator.Publish<BtoC>( y );
} );
C:
eventAggregator.Subscribe<BtoC>( x => whatever( x ) );
Side-note: what you publish and subscribe is the event type, not the payload type. The naming of your event (EventData) is a bit awkward, because one will confuse it with the payload. A better name would be DataEvent... or, here, RawDataEvent and ProcessedDataEvent.
Side-note 2: obviously, the event aggregator is not intended for non-broadcast messages. You can force it (either using distinct event types or, for example, by including a destination-id in the payload), but I'd prefer a messaging system that's designed for point-to-point messages.
This question is build on top of a previously asked question: MVP Communication between presenters?
I am implementing the pattern outlined in the previous post. However I've run into an issue when trying to retrieve state from Presenter B based on some event in Presenter A.
For example, Presenter A responds to a view event, SaveButtonClicked.
Presenter A needs to get some state from Presenter B's view, View B.
So far what I've tried is:
class PresenterA
{
void PresenterA()
{
EventHub.Register(EventType.SendStateToPresenterA, HandleSendStateToPresenterA);
}
void HandleSaveClick(int productId)
{
EventHub.Publish(EventType.GetStateFromPresenterB, productId);
}
void HandleSendStateToPresenterA(string state)
{
// save to db
}
}
class PresenterB
{
void PresenterB
{
EventHub.Register(EventType.GetStateFromPresenterB, HandleGetStateFromPresenterB);
}
public void HandleProductChanged(int state)
{
EventHub.Publish(EventType.SendStateToPresenterA, "I am state!");
}
}
The problem with this approach is that it is overly complex if I have more than 2 presenters.
What are some ways to handle this?
I have found that the situation doesn't actually warrant a separate view / presenter for the Presenter B. It belongs in View A / Presenter A.
My view has a control inside of it that is capable of generating an image that is saved at a path I can specify (along with some other data). I don't own this control and can't get the interface to generate an image changed. I'm not quite sure how to handle this with MVVM.
The quick and dirty way would be for my view to define a method that takes the desired path, and have the viewmodel call that method.
View:
public void GenerateImage(string path) {
_control.SaveImage(path);
}
ViewModel:
(actually this is the body of a Command) {
var path = GeneratePath();
_view.GenerateImage(path);
...
}
I don't like this because I get the feeling that viewmodels are not meant to directly reference the view, instead they represent the view's state and communicate via property bindings. It works, and I'm doing this while waiting on answers. I'd like to find a way around it.
I could get cute and have the view pass a reference to the control to a Command (I'm in Xamarin Forms) via the Execute() parameter, and have the command cast and make the call. This seems like lipstick on a pig since it makes the viewmodel still aware of a particular class inside the view. But in writing this paragraph I think I came up with a solution I like.
I /could/ create:
interface IGenerateImage {
void GenerateImage(string path);
}
The obvious implementation would delegate the call to an encapsulated control. I feel like if the view passes an IGenerateImage then I'm not creating the viewmodel-to-view dependency that I'm trying to avoid, and I can test the logic without needing to instantiate expensive UI classes.
I like that answer, but I'm pretty sure there's an obvious solution I'm missing. Is there some other useful pattern for handling it? Or is it not a big deal if the viewmodel references the view?
You never want the View Model to know anything about the View.
It's a little unclear what you can and can't change in your post, so I'm assuming you can change the V/VM, but not _control.
The easiest way is to create an event in the View Model that the View can subscribe to.
Something like this:
View:
// Constructor
public View()
{
// However you're setting your VM, i.e. DI or new-ing up the VM
// Subscribe to the event
vm.ImageGeneratedEvent += this.OnImageGeneratedEvent;
}
private void OnImageGeneratedEvent(object sender, ImageGeneratedEventArgs args)
{
// Call your SaveImage in the event handler
_control.SaveImage(args.Path);
}
View Model:
public event EventHandler<ImageGeneratedEventArgs> ImageGeneratedEvent;
// Command body
{
var path = GeneratePath();
// Send event to the View
this.NotifyImageGeneratedEvent(path)
}
private void NotifyImageGeneratedEvent(string path)
{
ImageGeneratedEventArgs args = new ImageGeneratedEventArgs(path);
if (this.ImageGeneratedEvent!= null)
{
this.ImageGeneratedEvent(this, args);
}
}
ImageGeneratedEventArgs:
public class ImageGeneratedEventArgs : EventArgs
{
public string Path { get; set; }
public ImageGeneratedEventArgs(string path)
{
this.Path = path;
}
}
I have a program that connects to a server and sends commands to it.
in my program I have 2 windows, one of them is a toolbar with a textbox that shows current status (we'll call that "mainviewmodel") and the other is a login window which receives username and password and logs me into the server (we'll call that "loginviewmodel")
now, in order for the mainviewmodel to know the loginviewmodel I use this:
[Import]
Private LoginViewModel loginViewModel;
lunch the login window from the mainviewmodel I have the following function:
public void Login()
{
if (!loginViewModel.CanInvokeLogin)
return;
if (loginViewModel.IsActive)
{
loginViewModel.Focus();
}
else
{
windowManager.ShowWindow(loginViewModel);
}
}
as you can see - I have in loginviewmodel a property named CanInvokeLogin which indicates if login is in progress or not.
on mainviewmodel I have a property that shows me current client status (binded to the view's textbox)
public string TextboxDescription
{
get
{
switch (AvailabilityStatus.Type)
{
case AvailabilityStatusType.READY:
return ("Ready");
case AvailabilityStatusType.BREAK:
return (AvailabilityStatus.Reason);
case AvailabilityStatusType.DISCONNECTED:
if (!loginViewModel.CanInvokeLogin)
{
return ("Conencting");
}
return ("connected");
default:
return ("Please wait...");
}
}
}
}
My problem is - the status would not be updated on the view unless
NotifyOfPropertyChange(() => TextboxDescription);
is being called, so I need to call it whenever
NotifyOfPropertyChange(() => CanInvokeLogin);
is being called, but that happens on a different viewmodel.
so, how can I notify the mainviewmodel that caninvokelogin have been changed?
I know I could use eventAggregator and send a message from one viewmodel to another, but it sounds like killing a fly with a cannon and I bet there's a simpler way,
any suggestions?
Handle The Property Changed Event
The PropertyChanged event is simply an event so there is nothing stopping you from listening to that event from another view model if that is what you need.
this.loginViewModel.PropertyChanged += this.OnLoginPropertyChanged;
The event handler method would look something like this...
private void OnLoginPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "TextboxDescription") {
// Do something.
}
}
Raise StatusChanged Events:
To be honest if I was implementing this myself I would simply be firing events from the LoginViewModel when the status changed and then handling those events instead, seems like a cleaner solution to this.
this.loginViewModel.StatusChanged += this.OnLoginStatusChanged;
private void OnLoginStatusChanged(object sender, LoginStatusChangedEventArgs e)
{
// Do something.
switch (e.StatusType)
{
...
}
}
I would have custom event args like so...
public class LoginStatusChangedEventArgs : EventArgs
{
public AvailabilityStatusType StatusType { get; set; }
}
Just fire this event when the status changes and listeners can handle that.
Event Aggregator:
You could also use the event aggregator however unless you have lots of disconnected classes that need to listen to this I would probably feel it was overkill.
this.eventAggregator.Publish(new LoginStatusChangedMessage(AvailabilityStatusType.Disconnected));
I have a very frustratig problem:
I have a outer View, which has a Ribbonbar at the top. There is another View, which holds 1...n Viewmodels and displays a datagrid from a List of Datatables. Which one is shown, is up to the user.
The User can select a Button on the Ribbonbar, which should issue an operation on the selected rows in the Datagrid. But how to do this?
I could easily launch a method on the active ViewModel, but the method then needs to get hold of all selected rows - which would violate the Idea behind MVVM.
Any Ideas?
Here is a look of the Screen:
http://s7.directupload.net/file/d/3228/a3m3ttu9_jpg.htm
The Button "Zeile raus", should cause an Effect in the ViewModel / Viwe contained in the lower right Tabcontrol. The Effect needs to know which rows are selected.
Have the button publish an event from its command execute method :
public class RibbonViewModel {
IEventAggregator events;
public RibbonViewModel (IEventAggregator events){
this.events = events;
}
public void ButtonClickCommandExecute(){
events.Publish(new SomeMessage{
SomeNumber = 5,
SomeString = "Blah..."
});
}
}
Each of your ViewModel should subscribe to this event, and react on it if it is the "active" ViewModel :
public class ViewModelWithDataGrid : IHandle<SomeMessage>{
public void Handle(SomeMessage message){
if(IsActive){
//do something with the message
}
}
}
This way event source is not coupled to event sink, and you can easily unit test whenever a VM should respond to an event.
Documentation : http://caliburnmicro.codeplex.com/wikipage?title=The%20Event%20Aggregator&referringTitle=Documentation