Trigger EventHandler of one ViewModel in another ViewModel - c#

I have achieved desired result with MessagingCenter, but I have got an information from reading Xamarin articles that MessagingCenter is not the preferred way to trigger 30+ events. Additional to that I have to unsubscribe from MessagingCenter after action has been done. I want to have Settings page where I would have 30+ settings that have to be changed across whole application in different views. How I can inject SettingsViewModel into other ViewModels in Xamarin.Forms application?
SettingsViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public ICommand ChangeCommand { get; set; }
public SettingsViewModel()
{
Title = "Settings";
this.BoxColor = Color.Red;
this.ChangeCommand = new Command(this.ChangeColor);
}
private void ChangeColor()
{
this.BoxColor = Color.FromHex(this.BoxColorS);
MessagingCenter.Send<Object, Color>(this, "boxColor", this.BoxColor);
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
this.OnPropertyChanged();
}
}
private string _boxColorS;
public string BoxColorS
{
get => Preferences.Get("BoxColor", "#17805d");
set
{
Preferences.Set("BoxColor", value);
this.ChangeColor();
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
HomeViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class HomeViewModel : BaseViewModel
{
public HomeViewModel()
{
this.Title = "Home";
MessagingCenter.Subscribe<Object, Color>(this, "boxColor", (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("received color = " + arg);
this.BoxColor = arg;
});
this.BoxColor = Color.Red;
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
throw new NotImplementedException();
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
OnPropertyChanged();
}
}
private ISettingsViewModel SettingsViewModel { get; }
}
}
Should I somehow do all in MainViewModel? I mean:
namespace MessagingCenterApp.ViewModels
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.HomeViewModel = new HomeViewModel(this.SettingsViewModel);
}
public SettingsViewModel SettingsViewModel { get; set; }
public HomeViewModel HomeViewModel { get; }
}
}
Then initialized it in AppShell? I could not get this approach working.
Important! I don't want to use any MVVM framework! Only native behaviour.

mvvmcross' Messenger is alleged to be "lighter weight" than X-Form's built-in Messaging Center.
I use mvvmcross Messenger by defining some helper methods in a "BasePage". Then each page inherits from "BasePage" rather than "ContentPage".
This automatically handles "unsubscribe" of each method. And makes it easier to manage mvvmcross' "subscription tokens".
BasePage.xaml.cs:
// If not using mvvmcross, this could inherit from ContentPage instead.
public class BasePage : MvxContentPage
{
protected readonly IMvxMessenger Messenger;
public BasePage()
{
this.Messenger = Mvx.IoCProvider.Resolve<IMvxMessenger>();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Examples of subscribing to messages. Your subclasses of BasePage can also do this.
this.Subscribe<MyMessage1>(OnMyMessage1);
this.SubscribeOnMainThread<MyMessage2>(OnMyMessage2);
}
protected override void OnDisappearing()
{
UnsubscribeAll();
base.OnDisappearing();
}
#region Messenger Subscriptions
protected List<MvxSubscriptionToken> _subscriptions = new List<MvxSubscriptionToken>();
/// <summary>
/// Create subscription and add to "_subscriptions".
/// Call this from subclass' OnAppearing, once per subscription.
/// Automatically unsubscribed in OnDisappearing.
/// </summary>
/// <param name="token"></param>
/// <param name="msgType"></param>
protected void Subscribe<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.Subscribe<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
protected void SubscribeOnMainThread<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.SubscribeOnMainThread<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
/// <summary>
/// OnDisappearing calls this.
/// </summary>
private void UnsubscribeAll()
{
if (_subscriptions.Count > 0)
{
foreach (MvxSubscriptionToken token in _subscriptions)
{
// Per "https://www.mvvmcross.com/documentation/plugins/messenger", this is sufficient to Unsubscribe:
// "Subscriptions can be cancelled at any time using the Unsubscribe method on the IMvxMessenger or by calling Dispose() on the subscription token."
token.Dispose();
}
_subscriptions.Clear();
}
}
#endregion
}
For view models, class would be "BaseViewModel", that your view models inherit from. Contents similar to above, but different method names for Appearing/Disappearing.
BaseViewModel.cs:
public class BaseViewModel : MvxViewModel
{
...
// mvvmcross' MvxViewModel provides these.
protected override void ViewAppearing()
{
...
}
protected override void ViewDisappearing()
{
...
}
... Messenger Subscriptions methods ...
}

Related

How to inject MonoBehaviour class to non Monobehaviour in Unity3d with zenject?

I want to organize my code as well as I can, but I have some trouble with its organization. I try to use SOLID principles and make separate entities. I want to use MVVM to view (unity-weld as well), and DI container (zenject as well).
It's my first project and I'm trying to organize code.
My question is how to inject LoginViewModel into LoginController as static class using zenject container from GameInstaller class.
[Binding]
public class LoginViewModel : MonoBehaviour, INotifyPropertyChanged
{
private string _username = "";
private string _passsword = "";
private string _errorMessage = "";
private bool _autologin = false;
[Inject]
private LoginController _loginController;
[Binding]
public string Username {
get
{
return _username;
}
set
{
if (_username == value)
{
return; // No change.
}
_username = value;
Debug.Log($"SET username: {value}");
OnPropertyChanged("Username");
}
}
[Binding]
public string Password {
get
{
return _passsword;
}
set
{
if (_passsword == value)
{
return; // No change.
}
_passsword = value;
Debug.Log($"SET password: {value}");
OnPropertyChanged("Password");
}
}
[Binding]
public bool Autologin {
get
{
return _autologin;
}
set
{
if (_autologin == value)
{
return; // No change.
}
_autologin = value;
Debug.Log($"SET autologin: {value}");
OnPropertyChanged("Autologin");
}
}
[Binding]
public void LoginButtonClick()
{
Debug.Log("LoginButtonClick");
_loginController.Login(this);
//ErrorMessage = "blabla";
}
[Binding]
public string ErrorMessage
{
get
{
return _errorMessage;
}
set
{
if (_errorMessage == value)
{
return; // No change.
}
_errorMessage = value;
Debug.Log($"SET errorMessage: {value}");
OnPropertyChanged("ErrorMessage");
}
}
/// <summary>
/// Event to raise when a property's value has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class LoginController
{
private readonly ApiController _apiController;
[Inject]
private readonly LoginViewModel _loginViewModel;
public LoginController(ApiController apiController)
{
_apiController = apiController;
}
public void Login(LoginViewModel loginViewModel)
{
try
{
string userJson = _apiController.PostLogin(loginViewModel.Username);
_loginViewModel.ErrorMessage = "bla bla trololo";
Debug.Log(userJson);
}
catch (Exception ex)
{
throw ex;
}
}
}
public class GameInstaller : MonoInstaller
{
[Inject]
Settings _settings = null;
public override void InstallBindings()
{
InstallViewModels();
InstallServices();
InstallSignals();
InstallControllers();
}
private void InstallViewModels()
{
Container.Bind<LoginViewModel>().AsSingle();
}
private void InstallControllers()
{
Container.Bind<LoginController>().AsSingle().NonLazy();
Container.Bind<ApiController>().AsSingle().NonLazy();
}
private void InstallServices()
{
}
private void InstallSignals()
{
}
[Serializable]
public class Settings
{
}
}
Let's start from here: https://unitylist.com/p/ja3/Unity-MVVM
A ViewModel (or VM) is what holds the data that will be presented on a view. It contains all the properties that can be bound to view elements. All ViewModels inherit from INotifyPropertyChanged which alerts the system when data changes and a UI element needs to be updated.
Since the ViewModel is in it's essence a simple object and not a service, I argue that it does not need go be injected anywhere.
What you could do, is inject a Factory and get your ViewModel from it.
In your code you are trying to inject the controller into your ViewModel and not the other way round.

Property change in Base ViewModel with prism

I'm using prism to develop an android app.
I'm trying to make a Base ViewModel. Inside this ViewModel I would like to set common properties to all my ViewModels.
public class BaseViewModel : BindableBase
{
protected INavigationService _navigationService;
protected IPageDialogService _dialogService;
public BaseViewModel(INavigationService navigationService, IPageDialogService dialogService)
{
_navigationService = navigationService;
_dialogService = dialogService;
}
private string _common;
/// <summary>
/// Common property
/// </summary>
public string CommonProperty
{
get { return _common; }
set
{
_common = value;
SetProperty(ref _common, value);
}
}
}
My problem is: when I set the common property in the constructor, works fine.
But when I´m setting the common property in OnNavigatingTo and using async, doesn´t work. The SetProperty is triggered when calling the OnNavigatingTo, but my binded label with this common property doesn´t refresh the value.
namespace TaskMobile.ViewModels.Tasks
{
/// <summary>
/// Specific view model
/// </summary>
public class AssignedViewModel : BaseViewModel, INavigatingAware
{
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService) : base(navigationService,dialogService)
{
CommonProperty= "Jorge Tinoco"; // This works
}
public async void OnNavigatingTo(NavigationParameters parameters)
{
try
{
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty= Current.NameToShow; //This doesn´t works
}
catch (Exception e)
{
App.LogToDb.Error(e);
}
}
}
when you use SetProperty, you should not set value for the backfield.
so you should remove this line:
_common = value;
Because you are doing asynchronous invocation on a separate thread the UI is not being notified of the change.
The async void of OnNavigatingTo, which is not an event handler, means it is a fire and forget function running in a separate thread.
Reference Async/Await - Best Practices in Asynchronous Programming
Create a proper event and asynchronous event handler to perform your asynchronous operations there
For example
public class AssignedViewModel : BaseViewModel, INavigatingAware {
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService)
: base(navigationService, dialogService) {
//Subscribe to event
this.navigatedTo += onNavigated;
}
public void OnNavigatingTo(NavigationParameters parameters) {
navigatedTo(this, EventArgs.Empty); //Raise event
}
private event EventHandler navigatedTo = degelate { };
private async void onNavigated(object sender, EventArgs args) {
try {
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty = Current.NameToShow; //On UI Thread
} catch (Exception e) {
App.LogToDb.Error(e);
}
}
}
That way when the awaited operation is completed the code will continue on the UI thread and it will get the property changed notification.

Why does opening and closing a window speed up my application? - C# WPF MVVM Observer Pattern

I have a C# WPF application built with Visual Studio 2015. I'm using MVVM and the Observer Pattern.
My Provider is a user control called 'ucClientFilter1ViewModel' that contains two text box controls where the user can search for a client(s):
namespace NSUCClientControls
{
public class ucClientFilter1ViewModel : ViewModelBase, IObservable<ClientFilterParameter>
{
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
private List<IObserver<ClientFilterParameter>> observers;
public ucClientFilter1ViewModel()
{
observers = new List<IObserver<ClientFilterParameter>>();
}
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set
{
whereSearch1 = value;
TestUpdateGrid(filterLocation);
}
}
public string WhereSearch2
{
get { return whereSearch2; }
set
{
whereSearch2 = value;
TestUpdateGrid(filterLocation);
}
}
private void TestUpdateGrid(string _filterLocation)
{
var filterInfo = new ClientFilterParameter(this);
foreach (var observer in observers)
{
observer.OnNext(filterInfo);
}
}
public IDisposable Subscribe(IObserver<ClientFilterParameter> observer)
{
// Check whether observer is already registered. If not, add it
if (!observers.Contains(observer))
{
observers.Add(observer);
// Provide observer with existing data
var filterInfo = new ClientFilterParameter(this);
observer.OnNext(filterInfo);
}
return new Unsubscriber<ClientFilterParameter>(observers, observer);
}
internal class Unsubscriber<ClientFilterParameter> : IDisposable
{
private IObserver<ClientFilterParameter> observer;
private List<IObserver<ClientFilterParameter>> observers;
public Unsubscriber(List<IObserver<ClientFilterParameter>> _observers, IObserver<ClientFilterParameter> _observer)
{
observers = _observers;
observer = _observer;
}
public void Dispose()
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
}
My Observer is a user control called 'ucClientGrid1ViewModel' that contains a datagrid where the search results are displayed.
namespace NSUCClientControls
{
public class ucClientGrid1ViewModel : ViewModelBase, IObserver<ClientFilterParameter>
{
private IDisposable cancellation;
private ObservableCollection<Client> clientsMultiple;
public ucClientGrid1ViewModel()
{
}
public ObservableCollection<Client> ClientsMultiple
{
get
{
var myClientDataAccess = new ClientDataAccess();
clientsMultiple = myClientDataAccess.GetClientListFromSQL_Test2();
return clientsMultiple;
}
set
{
}
}
public virtual void Subscribe(ucClientFilter1ViewModel provider)
{
cancellation = provider.Subscribe(this);
}
public void OnNext(ClientFilterParameter myFilter)
{
OnPropertyChanged("ClientsMultiple");
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
}
}
This all works and I get the search results that I am expecting. But what I don't understand is why the inclusion of the following lines actually speed things up!
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
I'm new to MVVM and the observer pattern, so as I was writing the code I had included message boxes at various points to help me to follow the flow of it. It was all working as expected. Then I removed the message boxes and it still worked but the application was pausing at the end before you could continue to keep searching.
Putting a message box back in at the end prevented this pause. Replacing the message box with a "DummyWindow" that just opens and closes has the same affect and prevents the pause at the end. This is what I currently have but I'd rather not leave this in there.
Presumably opening the window causes something else to happen which stops some redundant process, and this then prevents the pause? What else could I do to prevent the pause at the end, without using this DummyWindow?
I've tried searching on here and with Bing with no luck.
Thanks in advance!
Edit:
ViewModelBase...
namespace NSCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
ClientFilterParameter...
namespace NSCommon
{
public class ClientFilterParameter
{
public ClientFilterParameter(ucClientFilter1ViewModel myFilter)
{
FilterLocation = myFilter.FilterLocation;
WhereSearch1 = myFilter.WhereSearch1;
WhereSearch2 = myFilter.WhereSearch2;
}
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set { whereSearch1 = value; }
}
public string WhereSearch2
{
get { return whereSearch2; }
set { whereSearch2 = value; }
}
}
}

Mvvm application loading data

I have a windows phone application built using the MVVM pattern and I have pages that have to load data. I know this practice is bad but I used an async void LoadData() called in the constructor to load data. Are there any better ways to achive this?
Thanks in advance
public class SomeViewModel: BaseViewModel
{
public SomeViewModel()
{
LoadData();
}
public async void LoadData()
{
//load data, ex. add items to list
}
private RelayCommand _refreshCommand;
/// <summary>
/// Gets the RefreshCommand.
/// </summary>
public RelayCommand RefreshCommand
{
get
{
return _refreshCommand
?? (_refreshCommand = new RelayCommand(() => LoadData()));
}
}
}
Who's responsible for create view model is Ioc in Locator, so putting your LoadData method on constructor you will not have control.
My advice: Does not load data in Vm constructor. Do like this:
public class SomeViewModel: BaseViewModel
{
public IMessenger Messenger { get { return base.MessengerInstance; } }
public SomeViewModel()
{
Initialize();
}
public void Initialize()
{
RegisterMessengers();
}
public void RegisterMessengers()
{
Messenger.Register<string>(
this, "TokenRefresh", u => LoadData(u));
}
public async void LoadData(string u)
{
//load data, ex. add items to list
}
private RelayCommand _refreshCommand;
/// <summary>
/// Gets the RefreshCommand.
/// </summary>
public RelayCommand RefreshCommand
{
get
{
return _refreshCommand
?? (_refreshCommand = new RelayCommand(() => LoadData()));
}
}
}
And you call from another view model like that:
_navigationService.Navigate(typeof(YourView));
Messenger.Send("", "TokenRefresh");

Is this WPF error handling a good idea?

I have a multi threaded wpf application with various HW interfaces.
I want to react to several HW failures that can happen.
For example :
one of the interfaces is a temperature sensor and i want that from a certain temp. a meesage would appear and notify the user that it happened.
i came up with the follwing design :
/// <summary>
/// This logic reacts to errors that occur during the system run.
/// The reaction is set by the component that raised the error.
/// </summary>
public class ErrorHandlingLogic : Logic
{
}
the above class would consume ErrorEventData that holds all the information about the error that occurred.
public class ErrorEventData : IEventData
{
#region public enum
public enum ErrorReaction
{
}
#endregion public enum
#region Private Data Memebers and props
private ErrorReaction m_ErrorReaction;
public ErrorReaction ErrorReactionValue
{
get { return m_ErrorReaction; }
set { m_ErrorReaction = value; }
}
private string m_Msg;
public string Msg
{
get { return m_Msg; }
set { m_Msg = value; }
}
private string m_ComponentName;
public string ComponentName
{
get { return m_ComponentName; }
set { m_ComponentName = value; }
}
#endregion Private Data Memebers and props
public ErrorEventData(ErrorReaction reaction, string msg, string componenetName)
{
m_ErrorReaction = reaction;
m_Msg = msg;
m_ComponentName = componenetName;
}
}
the above ErrorHandlingLogic would decide what to do with the ErrorEventData sent to him from various components of the application.
if needed it would be forwarded to the GUI to display a message to the user.
so what do you think is it a good design ?
thanks,
Adiel.
It seems fair enough, however, in terms of design I would have probably just went with a standard Event with custom event args.
Here is an example:
public interface IEventData
{
ErrorReaction Reaction { get; }
string Message { get; }
ComponentName { get; }
}
public class HardwareChangeEventData : IEventData
{
public HardwareChangeEventData(ErrorReaction reaction, string msg, string componentName)
{
Reaction = reaction;
Message = msg;
ComponentName = componentName;
}
public ErrorReaction Reaction { get; private set; }
public string Message { get; private set; }
public ComponentName { get; private set; }
}
....
// introduce a base class so all hardware components can raise the event
public class HardwareComponent
{
public delegate void HardwareChangedEventHandler(IEventData ed);
public event HardwareChangedEventHandler HardwareChanged;
//event-invoking method that derived classes can override.
protected virtual void OnHardwareChanged(IEventData ed)
{
HardwareChangedEventHandler handler = HardwareChanged;
if (handler != null)
{
handler(this, ed);
}
}
}
public class TemperatureGauge : HardwareComponent
{
public void Monitor()
{
// example logic
while (...)
{
if (Temperature < LowThreshold)
{
IEventData ed = new HardwareChangeEventData(ErrorReaction.IncreaseTemp, "Temperature too low!", "TemperatureGauge");
OnHardwareChanged(ed);
}
}
}
public override OnHardwareChanged(IEventData ed)
{
// do something with ed internally (if applicable)
// forward event on to base so it can be passed out to subscribers
base.OnHardwareChanged(ed);
}
}
Above code looks fine.
But for notifying different components , i would say look for Observer pattern ( Event/Deleagte)
if you are going to handle error in WPF why don't use validators for that? see this acticle

Categories