MVVM runs all view model property getters when page is constructed - maui

I'm new to .net maui mvvm and need an advise on how to handle the following scenario:
.net maui solution based on Shell with community toolkit mvvm. Page1 calls Page2 passing ObjectToPass:
await Shell.Current.GoToAsync(nameof(Page2), new Dictionary<string, object>()
{
[nameof(ObjectToPass)] = ObjectToPass
});
Viewmodel of Page2 (Page2ViewModel) uses ObjectToPass to build bindable properties. However at the moment I run this line of code in the viewmodel constructor
public BuildGamePage(Page2ViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
Maui runtime (or mvvm runtime) starts invoking getter of each bindable property through the reflection mechanism, even if these properties are not used in Page2 at all.
Problem here is Page2ViewModel is not ready to fetch its properties at the time (Page2 constructor) since it needs to wait ObjectToPass to be passed to fetch relevant information. So what would be the best way to handle that: should I check ObjectToPass for null in every property getter and return default value or there is a better way? And overall, why does maui mvvm needs to call each property of a view model in the page constructor when BindingContext is set, even before visual elements of the page are constructed?
UPDATE
Page2ViewModel getter example:
public string Title => $"Details of {ObjectToPass.Name} object"
interestingly the getter is called even if it's not used inside Page2, right after BindingContext = viewModel; in constructor
here's call stack with Reflection mechanism running the getters

Related

Trying to understand list selection in MvvM pattern with ICommands (no specific framework)

First note that I am not referring to any specific framework or technology like XAML.
The question is how to implement the MvvM pattern using ICommand for selection of an item in a list (=clicking a row)?
I have a view model (pseudo code):
class ListViewModel
{
// Items in the list.
public ObservableCollection<T> Items {};
// Command for item selection.
public ICommand ItemSelectedCommand
{
...
}
// Select an item in the list.
public void SelectItem(int index)
{
...
}
// The current selected item.
public T SelectedItem
{
get { ... };
}
}
How would I now connect my UI to that view model "manually"? Say, for instance in an iOS application.
I would probably have a UITableViewController, get an instance of the view model and populate the UITableView contents from it. Then I would trigger the ICommand from the RowSelected() method.
And here comes the thing I don't understand: how does the view model now know which item index was selected? I don't want to call SelectItem() because then I would not need the loosely coupled ICommand at all.
And maybe here we have to look how it is solved in XAML to understand the trick?
Coming from XAML and WPF, there are two options to forward selection changes from the UI to the ViewModel (as I understand your question, you're not asking about the other way around - feedbacking changes in the ViewModel to the UI - here):
Command with payload
The ICommands Execute method has a payload parameter. Executing a command without a payload can be done passing null:
SomeCommand.Execute(null);
In your case, it would make sense to pass the selected item as the parameter in the event handler:
vm.ItemSelectedCommand.Execute(eventArgs.SelectedItem);
or
vm.ItemSelectedCommand.Execute(myList.SelectedItem);
In the command's execution method, you can handle the parameter. Note that your ViewModel property SelectedItem is not directly involved here. If you need the selected index explicitly (which is not the case, usually), I would check the selected item's index in the Items collection.
Binding selected item of list to a ViewModel property
Option B is to 'bind' the selected item of the list to a distinct property on the ViewModel, in your case the SelectedItem property in the event handler of the list:
vm.SelectedItem = myList.SelectedItem;
The command is kind of redundant then, although you could invoke it without a payload after setting SelectedItem on the ViewModel. I would rather handle the change of the selected item in the set accessor of the property on the ViewModel.
Note: XAML and WPF come with quite a lot of infrastructure code out of the box. MVVM doesn't make sense without a proper framework to actually take care of binding UI and ViewModels in a loosely coupled way. You quickly end up with a lot of extra work and little benefit, because you're still maintaining tight dependencies. Bottom line: I recommend getting or writing a proper MVVM framework, before actually implementing it.

Simplifying ICommand/RelayCommand in a MVVM approach

I'm pushing myself to make the applications I write simpler, and I've taken some steps to do that, but I'm left with an interesting problem that doesn't at all feel like it would be unique to me. I'm wondering what I'm doing wrong.
I have a ViewModel that keeps a collection of model objects. The view is a ListView that displays all of the objects in the collection. The model objects have all the logic in them to manipulate them. Inside the ListView row for each item I have a button, and that button needs to be wired to call a method on the model object.
To get this to work I need to add a command binding, but to the parent window data context, that passes a parameter of the model object in the row, all so that model object can be used inside the ViewModel (the parent window data context) to call the method on the model object that's being passed in.
This seems really much more complex than it needs to be. I'm willing to throw out anything I've done already, there are no sacred cows, I just want this to be done in a simpler method that will be easy to look back on in a year and figure out what I was doing.
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.MyCommand}
Create a presenter class in your ViewModel for the model objects and have a collection of those. You can then put the ICommand property on those instead and pass a reference to the method you want to call in the parent datacontext.
Perhaps something like the following:
public class ModelPresenter : INotifyPropertyChanged
{
private Model _model;
public ModelPresenter(Model model, Action<Model> parentAction)
{
_model = model
_action = parentAction;
}
public ICommand MyAction
{
get { return new RelayCommand(() => _parentAction(_model)); }
}
...
}
It also sounds like you might be binding to Properties of your model your view. You shouldn't do this as it can cause a memory leak if your models aren't implementing INotifyPropertyChanged (see: http://support.microsoft.com/kb/938416/en-us).

how (multiple=true) presenter and view can be initialized with parameters using eventBus.addHandler?

With the Mvp4g architecture, (Only)one instance of the view (injected using #Presenter annotation)is associated with its presenter.
In my case, I have a EntityView with its Presenter EntityPresenter.
whenever user clicks on an Leaf node of a Navigator tree,
I add a new Tab into TabSet. And this new Tab will contain an EntityView.
So, I will have as many EntityView as many Tab in the TabSeT.
I have set multiple=true for EntityPresenter.
EntityView's constructor accepts one argument.
#Inject
public EntityView(final Record view) {
//some initialization
}
Question is, where I do (from another presenter):
EntityPresenter presenter = eventBus.addHandler(EntityPresenter.class);
I have one argument Record params which I want to pass to EntityView's constructor, how to do that?
and annotating constructor(accepting argument) with #Inject will inject EntityView to EntityPresenter ?
I suggest to use an EventHandler - that's a presenter without a view in mvp4g - which get an event showEntity(long key). In the onShowEntity(...) - method you can create the presenter with the statement:
EntityPresenter presenter = eventBus.addHandler(EntityPresenter.class);
With that reference of the instance, you can esaly set the key in the presenter.
But keep in mind, you have to manage your presenter instances by yourself, when using multiple=true.

MVVM access other view's element from viewModel

I started working with the MVVM pattern in a new project.
Everything is ok, but i came to the following problem.
The implementation looks like this:
I have a MainView, the main app window. In this window i have a telerik RadGroupPanel in wich I host the rest of the app views as tabs.
The rest of the viewModels does not know about this RadGroupPanel which is hosted in MainVIew.
How should i correctly add those views to the RadGroupPanel from the commands in the viewModels?
Thanks.
Have you considered injecting your view into the ViewModel using an interface to maintain separation? I know this breaks MVVM but I've successfully used this on a number of WPF projects. I call it MiVVM or Model Interface-to-View ViewModel.
The pattern is simple. Your Usercontrol should have an interface, call it IView. Then in the ViewModel you have a property with a setter of type IMyView, say
public IMyView InjectedView { set { _injectedView = value; } }
Then in the view you create a dependency property called This
public MyUserControl : IMyView
{
public static readonly DependencyProperty ThisProperty =
DependencyProperty.Register("This", typeof(IMyView), typeof(MyUserControl));
public MyUserControl()
{
SetValue(ThisProperty, this);
}
public IMyView This { get { return GetValue(ThisProperty); } set { /* do nothing */ } }
}
finally in Xaml you can inject the view directly into the ViewModel using binding
<MyUserControl This="{Binding InjectedView, Mode=OneWayToSource}"/>
Try it out! I've used this pattern many times and you get an interface to the view injected once on startup. This means you maintain separation (Viewmodel can be tested as IView can be mocked), yet you get around the lack of binding support in many third party controls. Plus, its fast. Did you know binding uses reflection?
There's a demo project showcasing this pattern on the blog link above. I'd advocate trying out the Attached Property implementation of MiVVM if you are using a third party control.
You can have the list of viewmodels that you need to add controls for in an ObservableCollection in your main window viewmodel. You can then bind the ItemsSource of the RadGroupPanel to that collection and use the ItemTemplateSelector and ContentTemplateSelector of the RadGroupPanel to select the right template to use based on the viewmodel that is bound.

ASP.NET MVC Access model data in masterpage

I have created a UserSiteBaseController that gets commonly used data and sets the data to a UserSiteBaseViewData viewmodel in a method called SetViewData
public T CreateViewData<T>() where T : UserSiteBaseViewData, new()
{
....
}
I then create specific Controllers that inherit from the UserSiteBaseController as well as viewModels that inherit from UserSiteHomeViewData and can be created in the controller like so:
public ActionResult Index(string slug)
{
Slug = slug;
var viewData = CreateUserSiteHomeViewData<UserSiteHomeViewData>();
//If invalid slug - throw 404 not found
if (viewData == null)
return PageNotFound();
viewData.Announcements = _announcementsData.All(slug).ToList();
return View(viewData);
}
private T CreateUserSiteHomeViewData<T>() where T : UserSiteHomeViewData, new()
{
T viewData = CreateViewData<T>();
return viewData;
}
The UserBaseViewData holds data that needs to be use on every page so it would be great to be able to access this data from the Masterpage in a strongly typed manner. Is this possible or am I going about this in the incorrect manner?
If you get your masterpage to inherit from System.Web.Mvc.ViewMasterPage<BaseViewModel> you should be good to go...?
BUT, when I first started using MVC I went down the same route using a BaseViewModel class which contained all my generic ViewModel stuff and then I created specific view models, e.g. EventListingViewModel, which inherited from this BaseViewModel - much like you are doing. My masterpages then inherited from System.Web.Mvc.ViewMasterPage<BaseViewModel> and everything was going swell.
But after a while it all became a bit tightly coupled and a quite brittle. It became a pain in the ass to make changes and what not. I also came across this issue.
So I've reverted back to using the standard ViewData dictionary (with a static VewDataKeys class to avoid magic strings) for all my generic properties and then using custom POCO objects that don't inherit from anything as my presentation models. This is working much better and I wouldn't change back.
HTHs,
Charles