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
Related
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.
I have the following code snippet on my ViewModel and I would like to get rid of the new keyword and give the responsibility of creation to a DI-container. However, I am having some difficulties to be able to inject IDataFileReader into my ViewModel because the given parameter progress is tied to a ViewModel ProgressBarValue property.
Basically, my file reader requires the progress as a parameter so I can display the progress on my UI.
So the question is, how to register IDataFileReader with AutoFac modules on
ViewModelLocator?
VieModel.cs
ProgressBarIsIndetermined = true;
var progress = new Progress<int>(status => { ProgressBarValue = status; });
await Task.Run(() =>
{
IDataFileReader fileImporter = new DataFileReader(progress);
DataSet = new ObservableCollection<MeasurementPoint>(fileImporter.DataSet);
});
I am using Mvvm Light viewmodelLocator and MVVM with WPF. For simple services which do not require any parameters, I can easily achieve this by constructor injection.
ViewModelLocator.cs
static ViewModelLocator()
{
var builder = new ContainerBuilder();
builder.RegisterModule<AutofacModule>();
var container = builder.Build();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));
}
public SettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance<SettingsViewModel>();
AutoFacModule.cs
The following module is just a draft and would work for a simple constructor injection without parameters.
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<DataFileReader>().As<IDataFileReader>();
builder.RegisterType<SettingsViewModel>().AsSelf().SingleInstance();
}
}
An alternative option is to inject a delegate that can create the IDataFileReader, rather than an already instantiated one. This will allow you to pass the Progress object to it.
Autofac has support for delegate factories. This could result in something like the following (untested):
public class DataFileReader : IDataFileReader
{
public delegate DataFileReader Factory(Progress progress);
public Shareholding(Progress progress)
{
Progress = progress;
}
}
public class ViewModel
{
private readonly DataFileReader.Factory factory;
public ViewModel(DataFileReader.Factory dataFileReaderFactory)
{
factory = dataFileReaderFactory;
}
...
IDataFileReader fileImporter = factory(progress);
}
Basically, you can't do this in nice way :) You should first ask yourself, why does DataFileReader cares about progress. Maybe, something else should observe progress and report it to the world?
I'd also recommend avoiding ServiceLocator pattern. Classes should contain only well defined dependencies injected explicitly via constructor. Properties injection should also be considered as anti pattern in my opinion.
What you want is that DataFileReader update the ProgressBarValue property of your ViewModel. The easiest way to do that would be to add a OnUpdate method on the DataFileReader
reader.OnUpdate(status => this.ProgressBarValue = status.PercentProgress);
By doing so you will add a new responsibility to your IDataFileReader interface which may not be suitable and break Single Responsibility Principle.
In this case it is common to introduce a new component that will focus on only one thing.
public interface IProgressObserver
{
void OnUpdate(Action<Int32> updater);
void Update(Int32 percent);
}
Your DataFileReader can rely on this component and call the Update method when needed. Your ViewModel will have a IProgressObserver dependency and a IDataFileReader
One possible implementation for IProgressObserver can be as easy as
public class ProgressObserver : IProgressObserver
{
private Action<Int32> _updater = _ => { };
public void Update(Int32 percent)
{
this._updater(percent);
}
public void Register(Action<Int32> updater)
{
this._updater = updater;
}
}
I'm trying to use the following code as a safe way to call an async service from within the constructor of my viewmodel as suggested in this post. Problem is, nothing from within the body of the this.WhenActivated ever fires, any ideas why?
here is my code:
class MainViewModel : ReactiveObject, ISupportsActivation, IMainViewModel
{
private IDataService _dataService;
private Part _part;
public Part MyPart
{
get { return _part; }
set { this.RaiseAndSetIfChanged(ref _part, value); }
}
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
this.WhenActivated(disposables =>
{
_dataService.GetPart("9176900515")
.ToObservable()
.Subscribe(
result => { MyPart = result; },
exception => { LogMe.Log<string>(exception.Message); }
)
.DisposeWith(disposables);
});
}
private readonly ViewModelActivator activator = new ViewModelActivator();
ViewModelActivator ISupportsActivation.Activator
{
get { return activator; }
}
}
To make WhenActivated work inside a view model, the view model has to be the ViewModel of a view that implements IViewFor<MainViewModel>.
MainViewModel's WhenActivated will then be called by the view's WhenActvated.
Update:
This is done in WPF, but it's supported on all platforms (WPF, UWP, Xamarin).
The view implements IViewFor<TViewModel>. By best practices the ViewModel property is a DependencyProperty (or BindableProperty in Xam.Forms).
public partial class MainWindow : Window, IViewFor<MainViewModel>
{
public MainWindow()
{
InitializeComponent();
this.WhenActivated(d =>
{
// This will be called
});
}
public MainViewModel ViewModel
{
get => (MainViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(MainViewModel), typeof(MainWindow), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = value as MainViewModel;
}
}
The WhenActivated in the VM will now be called when WhenActivated is called in the view.
class MainViewModel : ReactiveObject, ISupportsActivation
{
public ViewModelActivator Activator => _activator;
private ViewModelActivator _activator = new ViewModelActivator();
public MainViewModel()
{
this.WhenActivated(d =>
{
// This will be called
});
}
}
Implementing the ViewModel
For one, the WhenActivated pattern is described here: https://reactiveui.net/docs/handbook/when-activated/
WhenActivated is a way to track disposables. Besides that, it can be used to defer the setup of a ViewModel until it's truly required. WhenActivated also gives us an ability to start or stop reacting to hot observables, like a background task that periodically pings a network endpoint or an observable updating users current location. Moreover, one can use WhenActivated to trigger startup logic when the ViewModel comes on stage.
That part of the pattern seems already done right in the question.
Implementing the View
For your ViewModel to be activated, you have to fully follow a specific pattern which is provided in https://reactiveui.net/docs/getting-started/#create-views
Here are the key areas:
public partial class MainWindow : IViewFor<AppViewModel>
{
// Using a DependencyProperty as the backing store for ViewModel.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel",
typeof(AppViewModel), typeof(MainWindow),
new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
ViewModel = new AppViewModel();
// ....
}
// ....
// Our main view model instance.
public AppViewModel ViewModel
{
get => (AppViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
// This is required by the interface IViewFor, you always just set it to use the
// main ViewModel property. Note on XAML based platforms we have a control called
// ReactiveUserControl that abstracts this.
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (AppViewModel)value;
}
}
Notice that the framework will notice the link from that specific MainWindow instance to that specific AppViewModel instance when the ViewModel setter is called:
ViewModel = new AppViewModel();
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.
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