Raise an event from Child View Model - c#

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.

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

MVP - Getting Data from the View to the Presenter

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.

UITableView to ObservableCollection binding breaks when the containing UIViewController is initialised for the second time

I'm using mvvmcross and xamarin to bind an ObservableCollection to a UITableView. The collection is updated in place using the Add, Remove and Move methods. These calls correctly trigger INotifyCollectionChanged events and the TableView is updated as expected the first time the view containing the table is shown. If the user navigates away from the original view as part of the normal application flow but later returns the correct data is loaded into the table but calls to add, move and remove no longer update the table.
The INotifyCollectionChanged events are still being fired when the collection is updated
If I manually subscribe to these events in my subclass of MvxStandardTableViewSource and try and call ReloadData on the UITableView still does not update
My presenter is creating a new instance of the viewmodel and view each time the page is visited.
I'm also using Xamarin-Sidebar (https://components.xamarin.com/view/sidebarnavigation) for navigation in my application with a custom presenter to load the views but as far as I can tell the view is initialised via exactly the same code path whether it's the first or subsequent visit.
My presenters Show() method looks like this:
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.ClearStack))
{
MasterNavigationController.ViewControllers = new UIViewController[0];
base.Show(request);
}
else if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.LoadView))
{
var root = MasterNavigationController.TopViewController as RootViewController;
var view = this.CreateViewControllerFor(request) as UIViewController;
root.SidebarController.ChangeContentView(view);
}
}
else
{
base.Show(request);
}
}
The binding in my ViewController looks like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var source = new TracksTableSource(TableView, "TitleText Title; ImageUrl ImageUrl", ViewModel);
TableView.Source = source;
var set = this.CreateBindingSet<TracksViewController, TracksViewModel>();
set.Bind(source).To(vm => vm.PlaylistTable);
set.Apply();
}
And my viewmodel is as below where PlaylistTable is a subclass of ObservableCollection with the Update method using add, move and remove to keep the collection up to date.
public class TracksViewModel : MvxViewModel
{
private readonly IPlaylistService _playlistService;
private readonly IMessengerService _messengerService;
private readonly MvxSubscriptionToken _playlistToken;
public PlaylistTable PlaylistTable { get; set; }
public TracksViewModel(IPlaylistService playlistService, IMessengerService messengerService)
{
_playlistService = playlistService;
_messengerService = messengerService;
if (!messengerService.IsSubscribed<PlaylistUpdateMessage>(GetType().Name))
_playlistToken = _messengerService.Subscribe<PlaylistUpdateMessage>(OnDirtyPlaylist, GetType().Name);
}
public void Init(NavigationParameters parameters)
{
PlaylistTable = new PlaylistTable(parameters.PlaylistId);
UpdatePlaylist(parameters.PlaylistId);
}
public async void UpdatePlaylist(Guid playlistId)
{
var response = await _playlistService.Get(playlistId);
PlaylistTable.Update(new Playlist(response));
}
private void OnDirtyPlaylist(PlaylistUpdateMessage message)
{
UpdatePlaylist(message.PlaylistId);
}
}
This setup works perfectly the first time the view is initialised and updates the table correctly, it's only the second and subsequent times the view is initialised that the table fails to update. Can anyone explain why the binding fails when it appears the view is created using the same techniques in both instances?
I can post additional code if required but I believe the issue will be how I'm using the presenter since the code I've not posted from PlaylistTable functions correctly in unit tests and on first viewing.

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

WPF MVVM PropertyChanged notifications in the ViewModel triggered by Model Events

I am having a problem understanding how to propagate a property changed event in a Model class up through the ViewModel and into the view. I am trying to conform to the MVVM pattern so please keep that in mind.
I have a Model that I am trying to expose by the ViewModel. My Model class queries an Api call to get the server status and exposes that status in public properties. Ex:
public class ServerStatusRequest : ApiRequest
{
//Exposable properties by request
public ServerStatusHelperClass Status { get; set; }
Where ServerStatusHelperClass is just a wrapper for the combined results in the query:
public class ServerStatusHelperClass
{
public bool ServerStatus { get; set; }
public int OnlinePlayers { get; set; }
The cool thing about my ApiRequest base class is that it checks the cache time of a particular Api call and updates the Results by using a System.Timers.Timer. So, for example, the ServerStatus Api call is cached for 3 minutes on the Api, so every 3 minutes my ServerStatusApiRequest object will have fresh data for it. I expose a UpdatedResults event in all ApiRequest classes to notify when new data comes in.
Now I want my ViewModel to have an instance of ServerStatusApiRequest and bind to its ServerStatusHelperClass Status property and stay up to date with the changes every time the information is updated, but my view (for binding) can't know about my model, and thus, doesn't know about my UpdatedResults event in my ApiRequest class. How can I reflect that out to the View through my ViewModel? Am I doing something completely weird here?
Here is what I have that is semi-working but I feel is a very hacky solution:
In my ViewModel:
public const string EveServerStatusPropertyName = "EveServerStatus";
private ServerStatusRequest _eveServerStatus = new ServerStatusRequest();
public ServerStatusRequest EveServerStatus
{
get
{
return _eveServerStatus;
}
set
{
//if (_eveServerStatus == value)
//{
// return;
//}
//RaisePropertyChanging(EveServerStatusPropertyName);
_eveServerStatus = value;
RaisePropertyChanged(EveServerStatusPropertyName);
}
}
public void UpdateEveServerStatus(object sender, EventArgs e)
{
EveServerStatus = (ServerStatusRequest)sender;
}
And in the ViewModels constructor I subscribe to the Model's event:
EveServerStatus.UpdatedResults += new UpdatedResultsEventHandler(UpdateEveServerStatus);
As you can see, this seems extremely redundant. And I also ran into a problem where I had to comment out the check in the setter for EveServerStatus because at that point the _eveServerStatus was already updated to value just without it knowing and I wanted to fire the event anyway.
I fell like I'm missing a key concept here to link this all together much more easily.
Thanks for any input.
I have come across a much better way to implement the behavior I was looking for. Here is the code in my ViewModel:
private ServerStatusRequest _eveServerStatus = new ServerStatusRequest();
public ServerStatusRequest EveServerStatus
{
get
{
return _eveServerStatus;
}
}
No setter as my ViewModel nor my View should be changing this data. And Inside my ServerStatusRequest class I have a property exposing the ServerStatusHelperClass object as shown in the Question. I have changed the ServerStatusHelperClass and made it implement INotifyPropertyChanged as so:
public class ServerStatusHelperClass : ObservableObject
{
private bool _serverStatus;
public bool ServerStatus
{
get
{
return _serverStatus;
}
set
{
_serverStatus = value;
RaisePropertyChanged("ServerStatus");
}
}
...
ObservableObject is just a simple class that implements INotifyPropertyChanged for me from mvvmlight.
By doing this my View is automatically updated when my ApiRequest class modifies it's ServerStatusHelperClass object.
Input on this solution is welcome.

Categories

Resources