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));
Related
I'm trying to make some changes to the code a colleague made.
So, I have a ShellView that loads documents (and shows them as its content) with a method defined in its ViewModel, and the child view, a StatusBarView which holds the path navigated in the documents and some other infos.
public class ShellViewModel
{
public StatusBarViewModel StatusBar { get; }
public ShellViewModel(StatusBarViewModel statusBarViewModel, ...)
{
StatusBar = statusBarViewModel;
var keymap = new Keymap();
keymap.Map("F2", new SimpleCommand("open-file",
"Shows the open file dialog",
param => OpenFile());
}
private void OpenFile()
{
// Logic to open the file that uses other methods
// inside this VM to validate the file
}
}
At the moment you can load a new pack of documents pressing a key, I'd like to do the same with a button in the Status Bar and calling that method.
What is the proper way to call a method existing in the parent view from the child view?
In your child view Define an event Handler
public EventHandler OpenFileHandler
On the click of the button of your Status Bar view do this:
public Btn_Click(object sender, RoutedEventArgs e)
{
OpenFileHandler(this, e);
}
in your parent view, when you create your status bar view, define the delegate
statusbar.OpenFileHandler+= delegate
{
ShellViewModel instance = this.DataContext as ShellViewModel;
instance.OpenFile();
}
statusbar is the name i gave to your status bar view, but it represent the instance of it
There are many ways. First need to analyze your purpose.
- You can create an ActionEvent or EventHandler inside childview and on button click you can raise that event.
- Another way use can use Mediator pattern/Observer pattern
Example:
Inside child ViewModel:
public event EventHandler openFileEvent;
Inside click button action on status bar:
private void btnClick()
{
if(null != openFileEvent)
{
openFileEvent(this, new EventArgs{});
}
}
Inside Parent ViewModel:
statusBarViewModel.openFileEvent += new EventHandler(EventHandlerName);
private void EventHandlerName(objehct sender, EventArgs...)
{
...
OpenFile();
...
}
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.
I'm trying to get some practice implementing the MVP pattern in a simple C# WinForms application. On the left of the view is a tree view with a list of the files saved by the application; on the right of the view is a DataGridView for displaying whichever file is clicked in the tree view, or for typing into to save as a new file. The files are simply Dictionary objects written to disk with BinaryFormatter.
I created an interface for the view:
public interface IMappingsView
{
event EventHandler SaveMapping;
event EventHandler NewMapping;
event EventHandler<DeleteArgs> DeleteMapping;
event EventHandler PasteData;
event EventHandler NodeClicked;
}
The delete button on the view has the following click event handler:
private void buttonDeleteMapping_Click(object sender, EventArgs e)
{
var node = treeView1.SelectedNode.Text;
var args = new DeleteArgs(Path.Combine(RootDir,node));
if (DeleteMapping != null)
{
DeleteMapping(this, args);
dataGridView1.Rows.Clear();
RefreshTreeView();
}
}
What is the best way to to pass information from the view to the presenter? I feel as though needing custom event arguments for every scenario is very wrong.
Make the data you want available available via the interface as a property.
Assuming you have a firstName and lastName field that you want exposed...
public interface IMappingsView
{
event EventHandler SaveMapping;
event EventHandler NewMapping;
event EventHandler<DeleteArgs> DeleteMapping;
event EventHandler PasteData;
event EventHandler NodeClicked;
string FirstName {get;set;}
string LastName {get;set;}
}
Then in your form that implements the interface,
string FirstName {
get {
return textFirstName.Text;
}
set {
textFirstName.Text = value;
}
}
as an example.
I have a setup as follows.
ContainerViewModel
SearchViewModel
ResultsViewModel
Thats because I wan't to use the SearchView and ResultsView in different parts of my application
My ContainerViewModel has a handle to the other VM's like
SearchViewModel searchbViewModel = new SearchbViewModel();
ResultsViewModel resultsViewModel = new ResultsViewModel();
Each View Model has their own DataContext
I want to be able to raise an event from the SearchViewModel to the ContainerViewModel to let it know a search has been performed.
This is what I have tried:
ContainerViewModel
searchJobViewModel.OnSearchPerformed += SearchJobViewModel_OnSearchPerformed;
public void SearchJobViewModel_OnSearchPerformed()
{
}
SearchViewModel
public delegate void SearchPerformed();
public SearchPerformed OnSearchPerformed { get; set; }
public void Execute_SearchJobs()
{
if (OnSearchPerformed != null)
OnSearchPerformed();
}
When I hit the search button and the Execute_SearchJobs method fires OnSearchPerformed is always null
What am I missing?
Does that even compile? I think what you want is an event:
public event SearchPerformed OnSearchPerformed;
Why your eventhandler is null is probably because the code that added a receiver to the event was not called yet or was called on a different instance of the class. You will need to debug that behaviour or post more code here.
I am just getting to grips with the concept of a UserControl.
I've created a UserControl to group together a number of controls that were being duplicated on individual pages of a TabControl.
Some of these controls are text fields that require validation, and when validation is unsuccessful I need to display an error message. However the place where I want to display the error message is on the status bar on the main form.
What is the best way to handle validation/error display in this situation?
To handle validation do one of these:
Validate with a method inside the user control
Have your user control have a delegate property (e.g. ValidationHandler) that can handle the validation (this would allow you to have a class with a bunch of validators that you could assign to your controls)
public delegate void Validator(...)
public Validator ValidationHandler { get; set; }
Have your user control generate a validation request event (e.g. ValidationRequested)
public event EventHandler<ValidationEventArgs> ValidationRequested
To notify the system that an error has occurred do one of these:
Use an event that interested parties can subscribe to (e.g. ValidationFailed)
If the object that performs the validation (via the delegate or event) is also the one that you want to generate the error message from, it can raise the error message itself.
EDIT:
Since you've said you would validate inside your control, the code for a ValidationFailed event might look like:
// In your user control
public class ValidationFailedEventArgs : EventArgs
{
public ValidationFailedEventArgs(string message)
{
this.Message = message;
}
public string Message { get; set; }
}
private EventHandler<ValidationFailedEventArgs> _validationFailed;
public event EventHandler<ValidationFailedEventArgs> ValidationFailed
{
add { _validationFailed += value; }
remove { _validationFailed -= value; }
}
protected void OnValidationFailed(ValidationFailedEventArgs e)
{
if(_validationFailed != null)
_validationFailed(this, e);
}
private void YourValidator()
{
if(!valid)
{
ValidationFailedEventArgs args =
new ValidationFailedEventArgs("Your Message");
OnValidationFailed(args);
}
}
// In your main form:
userControl.ValidationFailed +=
new EventHandler<ValidationFailedEventArgs>(userControl_ValidationFailed);
// ...
private void userControl_ValidationFailed(object sender,
ValidationFailedEventArgs e)
{
statusBar.Text = e.Message;
}
If you're doing the validation in the UserControl, you can have it offer a public ValidationFailed event and include the message in the EventArgs. The parent control could then subscribe to the ValidationFailed event and update the status bar.
You can either put a validator on the user control itself, throw an exception, or add public getters to the fields you want shown in the parent form.
Make a public method on your usercontrol that validates its field, and you can pass in a string output parameter.
so something like
public bool IsValid(out string status)
{
// do validation and set the status message
}
You can use asp.net validators in the user controls, and a validation summary on the main form and it will list the errors for you.
For other type of uses, you can expose an event, and have the page that contains the control subscribe to the event and take whatever action necessary.