Pass parameter via ICommand - c#

The following works if I do not pass anything from the View.
View.cs
ViewModel.ReloadCommand.Execute(null);
ViewModel.cs
public ICommand ReloadCommand
{
get
{
return new MvxCommand(async () =>
{
await RefreshStudentList();
});
}
}
However I need to pass a parameter, I wonder how could I do that?
ViewModel.ReloadCommand.Execute(xxx);
ViewModel.cs
public ICommand ReloadCommand
{
get
{
return new MvxCommand(async () =>
{
await RefreshStudentList(xxx);
});
}
}

To do async operation MvvmCross also has a MvxAsyncCommand which also can take a parameter as the regular MvxCommand.
It looks something like this:
public ICommand ReloadCommand
{
return new MvxAsyncCommand(DoAsyncStuff);
}
private Task DoAsyncStuff(MyType type)
{
}
Any command can be executed with a parameter like:
ViewModel.ReloadCommand.Execute(myParameter);

I'm not familiar with MvvmCross, but from what I can tell, it would be something like this:
public ICommand ReloadCommand
{
get
{
return new MvxCommand<XXXType>(async (xxx) =>
{
await RefreshRoutesList(xxx);
});
}
}

Instead of doing that, try initialize your ViewModel on your View first. Then, based on your code, the ICommand does only RefreshRoutesList function, so I will access the RefreshRoutesList directly from the View. To make the naming clear, I will use MyView.cs and MyViewModel.cs
MyView.cs
MyViewModel vm;
.
.
protected override void OnCreate(Bundle bundle)
{
.... //other stuff
vm = ViewModel as MyViewModel;
}
After doing that, you could call your function anywhere in your view using vm variable, i.e.
await vm.RefreshRoutesList(aParameter);
Hope this can help.

Not quite the answer you might be looking for but...
My understanding of Mvvm is that it reflects the state of the View and reacts to Commands from the View. Your parameter can be treated as State and as such should have its own Property on the ViewModel that it would be bound to. Thus your Command would not have to pass a parameter. This would also further de-couple the ViewModel from the View's implementation.

Related

Run Task after View loaded without ViewModel class in Prism

Hi I want a task to run immediately after the view is opened without need for ViewModel class
This is how I do it right now
public class ContributorsViewModel : DemoViewModelBase<Model>
{
public ContributorsViewModel()
{
Task.Run(() => DataList = new DataService().GetDataList());
}
}
DemoViewModelBase:
public class DemoViewModelBase<T> : BindableBase
{
private IList<T> _dataList;
public IList<T> DataList
{
get => _dataList;
set => SetProperty(ref _dataList, value);
}
}
Bootstrapper:
containerRegistry.RegisterForNavigation<ContributorsView>();
I do not know how to do this without a viewmodel
You can do anything you want in code-behind, for example subscribe to the Loaded event:
internal class ContributorsView
{
public ContributorsView()
{
InitializeComponents();
Loaded += async (s, e) => await Task.Run( ...whatever... );
}
}
But keep in mind:
you won't be able to test this
you do not get the benefit of injected dependencies
without [...] ViewModel class
This seems to be a really bad idea(*), because what you want to do is essentially what view models are there for.
(*) unless you have some architectural that you didn't reveal

How to create view-model of prism when needed?

I really made a search for this topic and did not find anything, and because of that, I am asking the question here.
I have a WPF application with Prism installed.
I have wired the view-model with the view automatically by name convention
<UserControl x:Class="Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
and the model in the 'Model' like this
public class ViewAViewModel {
public ViewAViewModel () {
// time-resource consuming operations
}
}
the automatic binding work perfectly without a problem and the view and its corresponding view-model is matching, but the problem here.
I have a lot of those views say (50) and for every one of them, the view-model will be created with constructor exhausting the processes. This will make the startup of the application longer and also it will create a lot of view-models objects and put them in the RAM without being sure that they will be used at all.
What I need is to create the view-model class when the view is activated (I mean when the view is navigated to). Is this possible and if yes how?
Update
here is how I register the view with the Module, this is causing all the views to be created when the startup of the module.
public class Module1 : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("region1", typeof(View1));
regionManager.RegisterViewWithRegion("region1", typeof(View2));
is there any way to delay the creating of the views, until the navigation request come?
You could use navigation, for each view.
Or you must create an interfaces for your view and view model.
An example:
public interface IMyView
{
IMyViewModel ViewModel { get; set; }
}
public interface IMyViewModel
{
}
In the module or app.cs, in the method RegisterTypes you should register these.
containerRegistry.Register<IMyView, MyView>();
containerRegistry.Register<IMyViewModel, MyViewModel>();
You must implement IMyView interface in your MyView.cs class.
public partial class MyView : UserControl, IMyView
{
public MyView(IMyViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IMyViewModel ViewModel
{
get => DataContext as IMyViewModel;
set => DataContext = value;
}
}
After you could use it:
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var firstView = containerProvider.Resolve<IMyView>();
regionManager.AddToRegion(RegionNames.MainRegion, firstView);
}
In such case you shouldn't use ViewModelLocator.AutoWireViewModel in your view.

MVVM Light throws (Did you forget to call > NavigationService.Configure? Parameter name: pageKey) exception

I am making a Universal winrt app using mvvm light. In ViewModelLocator I've registered my view in the builtin NavigationService of mvvm light
SimpleIoc.Default.Register<INavigationService>(() =>
{
var navigationService = new NavigationService();
navigationService.Configure("PreRegisterPage", typeof(PreRegisterPage));
return navigationService;
});
But when I try to navigate to that page using this code,
_navigationService.NavigateTo("PreRegisterPage");
It throws this exception
No such page: PreRegisterPage. Did you forget to call
NavigationService.Configure? Parameter name: pageKey
Am I missing something?
You probably forgot to pass an INavigationService object in the ViewModel ctor, here how your ViewModel should looks like:
public class MainViewModel : ViewModelBase
{
private INavigationService _navigationService;
private RelayCommand _navigateCommand;
public RelayCommand NavigateCommand
{
get
{
return _navigateCommand
?? (_navigateCommand = new RelayCommand(
() =>
{
_navigationService.NavigateTo("PreRegisterPage");
}));
}
}
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
}
In the constructor of the PageService object, you have to call the Configure method with the ViewModel and the Page classes. As in the following:
Configure<MainViewModel, MainPage>();
The code for displaying the error message above is also in PageService.cs. I think it's a stupid way of doing it.

How to avoid calling long operations from constructor

I use MVVM and I have to create a ViewModel class that should load lots of data when the View is opened.
Basically, when I create the viewmodel, it should use the database and get the data.
I used this approach first:
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
//collect the data
StartLongOperation();
}
private void StartLongOperation()
{
Thread t=new Thread(...);
t.start();
}
}
It works and loads the data without blocking the UI thread.
Later I found this guideline about how to use constructor, and it does not recommend starting long operation from constructor.
√ DO minimal work in the constructor.
Constructors should not do much work other than capture the
constructor parameters. The cost of any other processing should be
delayed until required.
In my case, the data is required on opening the view.
My first idea was to use an event.
How should I avoid calling the long operation from construcTor? What is the best practice?
Miguel Castro has talked about a solution to this in one of his great Pluralsight courses. He binds to a property in the viewmodel called ViewLoaded which will obviously get bound when the view loads, this in turn will call your long running method.
So this goes in the view (Or a base class for all views to help with re-use):
public ViewBase()
{
// Programmatically bind the view-model's ViewLoaded property to the view's ViewLoaded property.
BindingOperations.SetBinding(this, ViewLoadedProperty, new Binding("ViewLoaded"));
}
public static readonly DependencyProperty ViewLoadedProperty =
DependencyProperty.Register("ViewLoaded", typeof(object), typeof(UserControlViewBase),
new PropertyMetadata(null));
And this is the ViewModel base class code:
public object ViewLoaded
{
get
{
OnViewLoaded();
return null;
}
}
protected virtual void OnViewLoaded() { }
Simply override the OnViewLoaded() method in your ViewModel and call the long running method from there.
Maybe use a factory pattern to avoid having the MainViewModel around but not populated.
public class VMFactory
{
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
await vm.LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public async Task LongOperation()
{
(...)
}
}
or perhapse better. move the long running method out of the MainViewModel to a repository or service
public class VMRepository
{
public async Task LongOperation()
{
(...)
}
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
vm.DataWhichTakesAlongTime = await LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public object DataWhichTakesAlongTime { get; set; }
}
To be honest though it sounds from the conversations around this question that you are simply using the constructor as a convenient trigger for a 'LoadDataNow' command and really you should add an ICommand, bind it to something in the view (Loaded) add loading spinners and completed events etc etc
Controversially I might also suggest you add a Controller Class to instantiate the repository view and vm and call the 'LoadData' method on the view. Not very MVVM I know but essentially doing the same stuff your IoC container does without having to jump through the hoops of configuration
Avoiding calling it is simple, just split it into 2 methods; The constructor and a GetData method you call when you open the view or after you set the data context.
The why is just about managing expectation. If you hadn't written the code and were writing a new view for someone else's view model, would you expect the constructor to start accessing a database? or would you expect it just to construct a view model and you need to make a second call to initiate getting the data?
Use your view lifecycle to execute this method. You can use Tasks to simplify the execution and you can bind to other properties to show progress. Example shown using a Windows Store App view.
ViewModel:
public class MainViewModel
{
public MainViewModel()
{
this.Title = "Main view";
}
public async Task StartLongOperationAsync()
{
this.IsLoading = true;
await Task.Run(() =>
{
//do work here
});
this.IsLoading = false;
}
}
And on the View:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await ((MainViewModel)this.DataContext).StartLongOperationAsync();
}
I don't know maybe it's wrong but sometimes i make(if i need retrun parameters)
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public static string GetMainViewModelString()
{
var mainViewModel = new MainViewModel();
return mainViewModel.GetString();
}
public string GetString()
{
/*your code*/
}
}
and then call
var stringData = MainViewModel.GetMainViewModelString();
but when it need i call some operation from constructor

How to pass a view model in custom event args

I use the Caliburn Micro framework. That doesn't actually matter. The point is, that I publish an event in a view model, which contains the new view model to be shown in its event args. The event gets catched in the ShellViewModel (you could see it as the root view model), which actually activates the new view model.
So how could I pass the view model in my event args? Currently it looks like this:
// where it gets published; "AnotherViewModel" is the actual class
public void AMethod()
{
var args = new ViewModelChangedEventArgs { ViewModelType = typeof(AnotherViewModel) };
PublishEvent(args);
}
// event handler
public void Handle(ViewModelChangedEventArgs message)
{
if (message.ViewModelType == typeof(AnotherViewModel))
{
// activate AnotherViewModel
}
if (message.ViewModelType == typeof(FooViewModel))
{
// activate FooViewModel
}
}
This method seems not very elegant to me. Do you have any better ideas?
Overall solution is pretty well, you just passing a meta information in the event args which is enough to create a new ViewModel. Regarding ViewModel creating itself, this is a standard design problem which is solved by implementing Factory pattern. Basically you need a factory which is able creating concrete ViewModel by a type, so your handler code would be like below:
public void Handle(ViewModelChangedEventArgs message)
{
var viewModel = viewModelFactory.Create(typeof(AnotherViewModel));
}

Categories