MVP - Getting Data from the View to the Presenter - c#

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.

Related

Call a method from a child view

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();
...
}

INotifyCollectionChanged UI not updated

I have problem with updating UI. I have class which, is used to binding my UI elements:
public class engine : INotifyCollectionChanged
{
RWProject project = new RWProject();
public ObservableCollection<string> ProjectListBinding
{
get { return project.list(); }
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs eventArgs)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, eventArgs);
}
}
private ICommand _usunProjekt;
public ICommand UsunProjekt
{
get
{
_usunProjekt = new UsunProjektCommand();
return _usunProjekt;
}
}
private ICommand _dodajProjekt;
public ICommand DodajProjekt
{
get
{
_dodajProjekt = new DodajNowyProjektCommand();
return _dodajProjekt;
}
}
}
ProjectListBinding is a list of files names inside folder, and this names are displayed on listview control.
Commands DodajProjekt creating in same folder, new file (UsunProjekt - removing)
Commands are binded to buttons.
I need to rise event
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
to update UI after command is executed, but I don't knew where to attach this part of code. Or maybe I should do it in different way ?
With code I already have, bindings and commands are working fine, only updating not working at all.
Can You help me solve this ?
Piotr
You should not be implementing INotifyCollectionChanged. From your view you need to bind to ProjectListBinding which will automatically raise INotifyCollectionChanged for you and update the UI. Of course, assuming that the class that you have (i.e. engine) is your view model.
When running your command, you should update ProjectListBinding for INotifyCollectionChanged events to be risen. That is, both your commands DodajNowyProjektCommand and UsunProjekt should be operating on ProjectListBinding.

Raise an event from Child View Model

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.

Caliburn.micro - notifying a viewmodel on property change in another viewmodel

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));

ReBind Controls using PRISM pattern in Silverlight

I have been trying to work with the Composite Application Library (Prism) and I have set up a pretty standard pattern that I have followed off Microsoft's tutorial. Basically, the View is injected into the Region. The View is dynamically built, adding controls and so forth all programmatically.
I have a command that gets fired and on postback I would like to rebind the controls on the current view, instead of completely re-rendering all the controls over again.
So I tried updating the model with the updated version hoping that would force a rebinding of controls. That doesn't work. Not sure what approach I should be taking, for I am new to Prism...
Any ideas?
Subscribe an event to handle postbacks
IEventAggregator aggregator = this.Container.Resolve<IEventAggregator>();
aggregator.GetEvent<DataInstanceLoadedEvent>().Subscribe(this.OnDataInstanceUpdated);
Implementation of the event
public void OnDataInstanceUpdated(DataInstance updatedInstance)
{
if(this.View.Model != null){
// We need to rebind here
IRegion region = this.LocateRegion(this.View); // gets the region....
this.View.Model.CurrentDataInstance = updatedInstance; // update the model instance
}
else{
// Render all controls over again since view.model is null ...
}
}
I have figured out how to rebind according to the suggested patterns from Microsoft.
Basically, all I had to do was inherit from INotifyPropertyChanged on my Model.
Then following this pattern, once my model updates it is then forced to rebind all the controls by firing an event that notifies the client that the property has in fact changed.
public class MyModel : INotifyPropertyChanged
{
private DataInstance currentDataInstance;
public event PropertyChangedEventHandler PropertyChanged;
public DataInstance CurrentDataInstance
{
get
{
return this.currentDataInstance;
}
set
{
if ( this.currentDataInstance == value )
return;
this.currentDataInstance = value;
this.OnPropertyChanged( new PropertyChangedEventArgs("CurrentDataInstance"));
}
}
protected virtual void OnPropertyChanged( PropertyChangedEventArgs e )
{
if ( this.PropertyChanged != null )
this.PropertyChanged( this, e );
}
}

Categories

Resources