I would like to know how to send the value of a view model to another viewmodel using mvvcross and uwp
Does anyone know how to do it?
Thanks,
You can use the IMvxNavigationService to pass and return objects. The full documentation is at: https://www.mvvmcross.com/documentation/fundamentals/navigation?scroll=26
In your ViewModel this could look like:
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public override void Prepare()
{
//Do anything before navigating to the view
}
public async Task SomeMethod()
{
_navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
}
}
public class NextViewModel : MvxViewModel<MyObject>
{
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
}
Using a IMvxMessenger you can send values without have a connection: https://www.mvvmcross.com/documentation/plugins/messenger?scroll=1446
public class LocationViewModel
: MvxViewModel
{
private readonly MvxSubscriptionToken _token;
public LocationViewModel(IMvxMessenger messenger)
{
_token = messenger.Subscribe<LocationMessage>(OnLocationMessage);
}
private void OnLocationMessage(LocationMessage locationMessage)
{
Lat = locationMessage.Lat;
Lng = locationMessage.Lng;
}
// remainder of ViewModel
}
Related
My TabbedPage uses a Binding Property, which is defined in the tabbed page's ViewModel, for showing a Badge text.
I am setting the badge property when initializing the view (actually when it (re)appears). However, sometimes the badge text is changing from outside of my ViewModel(s), this is because I have a SignalR method which is called when a new message is being added by another application.
Though, when this happens the OnAppearing method of my tabbed viewmodel is obviously not called. So the question is, how can I 'notify' the tabbedpage viewmodel that the badge text should be changed.
I think the (best) way to do this is using somekind of Event. Since all of my ViewModels inherit from a 'ViewModelBase' I could implement the event notification / change in the ViewModelBase and override the property in my TabbedPage ViewModel.
Though, sadly my knowledge about using Events / EventArgs is limited and the stuff I found about it is not working.
Is using EventArgs the best way to solve this problem? And if so, could anyone give any pointers how to implement it properly.
*On a side-note, I am also using Prism
My TabbedPage ViewModel:
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
// (omitted) Logic for setting the MessageCount property
}
}
ViewModelVase:
public class ViewModelBase : BindableBase, IInitialize, IInitializeAsync, INavigationAware, IDestructible, IActiveAware
{
public event EventHandler MessageAddedEventArgs; // this should be used to trigger the MessageCount change..
protected INavigationService NavigationService { get; private set; }
public ViewModelBase(INavigationService navigationService)
{
NavigationService = navigationService;
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
IsNotConnected = Connectivity.NetworkAccess != NetworkAccess.Internet;
}
private bool _isNotConnected;
public bool IsNotConnected
{
get { return _isNotConnected; }
set { SetProperty(ref _isNotConnected, value); }
}
~ViewModelBase()
{
Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
}
async void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
IsNotConnected = e.NetworkAccess != NetworkAccess.Internet;
if (IsNotConnected == false)
{
await DataHubService.Connect();
}
}
public virtual void Initialize(INavigationParameters parameters)
{
}
public virtual void OnNavigatedFrom(INavigationParameters parameters)
{
}
public virtual void OnNavigatedTo(INavigationParameters parameters)
{
}
public virtual void Destroy()
{
}
public virtual Task InitializeAsync(INavigationParameters parameters)
{
return Task.CompletedTask;
}
}
SignalR Datahub which should trigger the event:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
// event that message count has changed should be triggered here..
}
});
}
catch (Exception ex)
{
// ...
}
}
}
As pointed out by #Jason, this specific problem is a good use case for using the MessagingCenter.
In the end the implementation looks as following:
public static class DataHubService2
{
// .. omitted some other SignalR specific code
public static async Task Connect()
{
try
{
GetInstanse();
hubConnection.On<Messages>("ReceiveMessage", async (message) =>
{
if(message != null)
{
MessagingCenter.Send("UpdateMessageCount", "Update");
}
});
}
catch (Exception ex)
{
// ...
}
}
}
public class RootTabbedViewModel : ViewModelBase, IPageLifecycleAware
{
private readonly INavigationService _navigationService;
private int _messageCount;
public RootTabbedViewModel(INavigationService navigationService)
: base(navigationService)
{
_navigationService = navigationService;
MessagingCenter.Subscribe<string>("UpdateMessageCount", "Update", async (a) =>
{
await UpdateMessageCount();
});
}
public int MessageCount
{
get { return _messageCount; }
set { SetProperty(ref _messageCount, value); }
}
public void OnDisappearing()
{
}
void IPageLifecycleAware.OnAppearing()
{
UpdateMessageCount();
}
async Task UpdateMessageCount()
{
int messageCount = await App.Database.GetNewMessageCountAsync();
MessageCount = messageCount.ToString();
}
}
I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.
Assume the following, simplified scenario:
//Model:
public class SessionModel: ObservableObject {
public bool IsChecked; // IPropertyChanged is implemented
}
//ViewModel:
public class SessionViewModel: ViewModel {
public IDialogService DialogService {get; set;}
public ObservableCollection<SessionModel> Items; // IPropertyChanged is implemented
public async Task DownloadFromUsbDevice() {
await = Task.Run(() => DownloadFromUsbDevice());
}
private void DownloadFromUsbDevice() {
// Get all checked items. If result is empty, show a message. If an error occurs, show error message
}
}
// Simple IDialogService...
public interface IDialogService
{
void ShowMessage(string message);
}
My first idea was, that the View of the ViewModel should implement this... This is possible, but requires me to manually assign the View to the ViewModel property in the views constructor...
//DialogService:
public class MainWindow: Window, IDialogService
{
public MainWindow()
{
ViewModel.DialogService = this;
}
public void ShowMessage(string message)
{
Xceed.Wpf.Toolkit.MessageBox.Show(this, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Then i read that most of this services seems to be implemented standalone... But for modal dialogs and thread-safe-access i would need the Window/Dispatcher of the targeted view... So I would have to register the interface and its implementation to the SimpleIoc. Then i have to create the instance in the view constructor using a key that is specific to this View/ViewModel and assign the Window.
//DialogService:
public class StandaloneDialogService: IDialogService
{
public Window Parent { get; set; }
public void ShowMessage(string message)
{
Xceed.Wpf.Toolkit.MessageBox.Show(Parent, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
//ServiceLocator:
public class Locator
{
public Locator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IDialogService, StandaloneDialogService>();
SimpleIoc.Default.Register<SessionViewModel>();
}
public SessionViewModel Session => SimpleIoc.Default.GetInstance<Session>();
}
//View:
public class MainWindow: Window
{
public MainWindow()
{
SimpleIoc.Default.GetInstance<IDialogService>("x").Parent = this;
}
}
//ViewModel:
public class SessionViewModel: ViewModel {
public IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>("x");
public ObservableCollection<SessionModel> Items; // IPropertyChanged is implemented
public async Task DownloadFromUsbDevice()
{
await = Task.Run(() => DownloadFromUsbDevice());
}
private void DownloadFromUsbDevice()
{
// Get all checked items. If result is empty, show a message. If an error occurs, show error message
}
}
Long story short... Is one of those two the right way to show (modal/non-modal) messages from a worker-function of the ViewModel? Are there better solutions? Or am I completly wrong?
I have the following simple navigation flow:
ViewModel1=>ViewModel2=>ViewModel3
When ViewModel3 is closed, I publish using the Message plugin to ViewModel some information which needs to get added to list in ViewModel1. Unfortunately nothing happens (I raise NotifChanged). In my opinion it happens because it's not called from UI.
What is the best way to achieve a refreshing list? I don't see any method in the ViewModel which is called when ViewModel is back from another ViewModel i.e. when ViewModel3 is closed.
EDIT:
Example Code:
public class WarehouseInViewModel : MvxViewModel
{
public WarehouseInViewModel(IMvxMessenger messenger)
{
mvxMessenger = messenger;
myToken = mvxMessenger.Subscribe<mAcceptMessage>(OnMyMessageArrived);
}
public override void Start()
{
base.Start();
}
private readonly IMvxMessenger mvxMessenger;
private MvxSubscriptionToken myToken;
private List<mProduct> productItems;
public List<mProduct> ProductItems
{
get { return productItems; }
set
{
productItems = value;
RaisePropertyChanged(() => ProductItems);
}
}
private MvxCommand<AcceptMenuItem> buttonCommand;
public ICommand ButtonCommand
{
get
{
return buttonCommand = buttonCommand ?? new MvxCommand<AcceptMenuItem>(MenuClick);
}
}
private void OnMyMessageArrived(mAcceptMessage myMessage)
{
mProduct product = mProduct.GetById(myMessage.ProductId);
//Something more ...
// There I want to update my Listview which is binded to ProductItems
ProductItems.Add(product);
RaisePropertyChanged(() => ProductItems);
}
public async void MenuClick(AcceptMenuItem menu)
{
ShowViewModel<WarehouseInScanViewModel>();
}
}
And the most important method from Third ViewModel (we assume that secont ViewMOdel only opens third, and it's closed properly)):
public void ButtonNextClick()
{
vxMessenger.Publish(new mAcceptMessage(this, productId, scannedLocation.Id, productQuantity));
Close(this);
}
So, when I'm back from third VM, I want to refreash ListView. I can't do it from OnMyMessageArrived because it's not in UI thread.
You could add a GlobalVars.cs static class (probably create Services folder and put it inside so it will be cleaner) with static fields and use that as the data source of your list in ViewModel1. Then, all you need to do is add the data on the List in GlobalVars.cs from ViewModel3.
Services/GlobalVars.cs
public static class GlobalVars
{
public static List<CustomClass> aGlobalVariable;
}
ViewModel1.cs
using Services;
...
public CustomClass LocalVariable
{
get { return GlobalVars.aGlobalVariable; }
set
{
GlobalVars.aGlobalVariable = value;
...
}
}
//refresh purpose
public void refresh()
{
RaisePropertyChanged(() => LocalVariable);
}
ViewModel3.cs
//just add the GlobalVars List when you need it
GlobalVars.aGlobalVariable.Add(new CustomClass());
To refresh your ListView, when you get back to the ViewModel1, you just simply call refresh() method from the activity. Below is how you access ViewModel in activity.
View1.cs
ViewModel1 vm;
...
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
...
vm = ViewModel as ViewModel1;
...
}
//what you want to do is refreshing the list on resume
protected override void OnResume()
{
base.OnResume()
vm.refresh();
... //other things you might want to do
}
Hope this answer could help.
To make it work you should either change your List ProductItems to ObservableColletion or do this
ProductItems.Add(product);
ProductItems = ProductItems.ToList();
I am searching a pattern to keep SOLID principles in my application when I use ICommand. Basically my problem is the command execution has a dependency with the view model but at the same time the view model has a dependency with the command (I inject them by constructor). I would like to keep the viewmodel with properties only, so this is an example of my current implementation:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel()
{
}
public void SetCommand(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readOnly MyViewModel myViewModel;
public MyViewModelCommandManager(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public ExectueMyCommand()
{
MessageBox.Show(this.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModel viewModel = new MyViewModel();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
viewModel.SetCommand(myCommand);
return viewModel;
}
}
So, to avoid use the SetCommand method. I have thought two solutions but I don't know if they are elegant.
The first one is to move the viewmodel dependency from the constructor to the method updating the code in this way:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand(MyViewModel viewModel);
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
public MyViewModelCommandManager()
{
....
}
public ExectueMyCommand(MyViewModel viewModel)
{
MessageBox.Show(myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);
ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
return viewModel;
}
}
Of course, the xaml code will use CommandParameter:
<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />
Other solution I have thought is to use a trick creating a Wrapper of the viewModel and the commandManager have a dependency with the Wrapper instead of the viewModel:
internal class MyViewModelCommandContext
{
public MyViewModel ViewModel { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyViewModelCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModelCommandContext context = new MyViewModelCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
context.ViewModel = viewModel;
return viewModel;
}
}
In my opinion the first one is the best solution for this problem, what do you think is the best solution. Would you apply another solution?
IMHO both solutions are overly complicated. SOLID is great, KISS is better.
Your MyViewModelCommandManager is currently directly coupled to MyViewModel since it needs the latter's Message, so what is the advantage of having them separate? Why not simply implement the command inside MyViewModel?
If this would entail injecting too many dependencies into MyViewModel, then think about what you actually need the command to do, and abstract away everything else that isn't needed.
The command displays a message.
The message is held by MyViewModel
You want to display the message outside MyViewModel (maybe other viewmodels also need to display a message and you want to reuse the code?)
So all you really need is some kind of notification from MyViewModel that it wants to display a message, or that something has occured that would result in a message being displayed.
Possible solutions:
Inject an IMessageDisplayService to MyViewModel. MyViewModel calls it with the message.
Inject a callback to MyViewModel similar to above.
Have MyViewModel raise an event with the message as an EventArg.
The inferred responsibilities of the above solutions are subtly different.
means that MyViewModel is in charge. It wants to display a message.
is less explicit. MyViewModel knows it needs to call the callback, but doesn't really know or care what it does.
is like 2 but even more decoupled. Multiple things can subscribe or unsubscribe to the event but MyViewModel remains blissfully ignorant.
All three of these mean that the thing displaying the message has no need to know about MyViewModel. You have decoupled them. It's MyViewModelFactory that does any wiring up required.
Thanks for your opinions.
I understand you when you say I am creating a complex pattern, but in a big project with a big developers team, if there is not clear patterns with split responsabilities, the code maintenance could be impossible to perform.
Reading you and your third solution I have thought one possible solution. It seems complexity but, in my opinion, improves the code quality. I will create a commandContext, which only have the viewmodel properties needed for the code, avoiding to have all viewmodel in the command manager. Also I will create a class whose responsability is to mantain the context updated when the viewmodel changes. This is the possible code:
internal class MyCommandContext
{
public string Message { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public string OtherProperty { get; set; }
public ObservableCollection<MyChildViewModel> Childs { get; set; }
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.Message);
}
}
internal interface IMyViewModelCommandSynchronizer
{
void Initialize();
}
internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
private readOnly MyViewModel viewModel;
private readOnly MyCommandContext context;
MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
{
this.viewModel = viewModel;
this.context = context;
}
public void Initialize()
{
this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Message")
{
this.context.Message = this.viewModel.Message;
}
}
// Dispose code to deattach the events.
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyCommandContext context = new MyCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
synchronizer.Initialize();
return viewModel;
}
}