Home > front end >  Trigger EventHandler of one ViewModel in another ViewModel
Trigger EventHandler of one ViewModel in another ViewModel

Time:03-27

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.

CodePudding user response:

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