Extending dynamic dispatch to call functions in the view model? - c#

I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid

Related

Property of partial view is not set correctly when calling with ajax

I made a BaseView-class that all my views inherit from. This contains the property Language. The language gets changed correctly in all "normal" views.
public abstract class BaseView<TModel> : WebViewPage<TModel>
{
string _language = TextRepository.DEFAULT_LANGUAGE;
public string Language { get { return _language; } }
public BaseView()
: base()
{
}
protected override void InitializePage()
{
base.InitializePage();
_language = Url.RequestContext.RouteData.Values["lang"] != null ? (string)Url.RequestContext.RouteData.Values["lang"] : _language;
}
}
public abstract class BaseView : BaseView<dynamic>
{
}
In one of my views, I call CandidatePartialRow with ajax, to add another partial row:
$("#addAnother").click(function () {
$.get('/MyController/CandidatePartialRow', function (template) {
$("#candidateEditor").append(template);
let target = $('#candidateEditor').children().last();
$('html, body').animate({
scrollTop: target.find('span').first().offset().top - 15
}, 1000);
});
});
public ActionResult CandidatePartialRow()
{
var view = PartialView("EditorTemplates/MyPartialView");
return view;
}
calling and appending works perfectly.
So I'm changing the language from "DE" (which is also default) to "FR". In the "normal" view, the language is changed. When I click the "add"-button for adding the partial view, the partial view is added, but with the default language ("DE").
Any idea what's the problem here? Or how I can change the language? (I also got the language in the controller - but this is also the default-language, otherwise I could set it in the CandidatePartialRow-method. But I don't know how..)
Okay simple solution...
The language wasn't added to the url, and so the default-language was always set with the routing.
Instead of '/MyController/CandidatePartialRow' I should have used
$.get('#Url.Action("CandidatePartialRow", "MyController")', function (template) {...})

Re-assigning a new model in MVVM -> does not update child collection view models

I have a root Model ~ PropertyPortfolio ~ which I save/load from an XML file. It contains a child collection of Property objects.
When I load the application I load this object into a PropertyPortfolioService. The model uses INotifyPropertyChanged, so does the service - so I've got that covered.
I have a child view which displays a child collection of Property objects in a grid. Binding works fine.
The problem I have is this:
When I open a new PropertyPortfolio from a file I re-assign the PropertyPortfolio object in the service:
this.PropertyPortfolio = loadedPropertyPortfolio;
The child view/viewmodel does not update.
The solution I currently have is to load the new portfolio and recreate child objects, like this:
PropertyPortfolio loadedPropertyPortfolio = /* Code to load new portfolio from XML */;
this.PropertyPortfolio.Properties.Clear();
foreach (var property in loadedPropertyPortfolio.Properties)
{
this.PropertyPortfolio.Properties.Add(property);
}
I'm looking for a better solution.
I hope that's descriptive enough?
Further information - The problem is with the PropertyViewModel (which contains a Property model object plus IsSelected logic).
This is the VM for an individual Property (for each Property in the collection):
public class PropertyViewModel
{
public Property Property
{
get { return this.property; }
}
public bool IsSelected
{
get { return this.isSelected; }
set
{
SetProperty(ref this.isSelected, value, () => IsSelected);
}
}
public PropertyViewModel(Property property)
{
this.Property = property;
}
// Other code (fields etc)
}
This is the view model for the Properties view (containing the grid):
public class PropertiesViewModel : ViewModelBase
{
public ObservableCollection<PropertyViewModel> PropertyVMs
{
get { return this.propertyVMs; }
}
public PropertiesViewModel(PropertyPortfolio propertyPortfolio)
{
Func<Property, PropertyViewModel> viewModelCreator = model => new PropertyViewModel(model);
this.propertyVMs = new ObservableViewModelCollection<PropertyViewModel, Property>(propertyPortfolio.Properties, viewModelCreator);
}
// Other code (fields etc)
}

C# MVVM: Adding new ViewModel (strict non-exposed Model design)

I've been working on an MVVM application in C# but consistiently run into some problems when working with the collections of ViewModels my View digests. Specifically, they all tend to relate to the issue of the Model being a private member of the ViewModel.
An example of this is creating new ViewModels (as requested by the View). For some preamble (although you might not need these to help me) here are example Model and ViewModel classes:
Private Class Model()
{
public string Name { get; set; }
}
Public Class ViewModel()
{
Private Model _Model;
Public Void ViewModel(Model model)
{
_Model = model;
}
Public String Name
{
get
{
return _Model.Name;
}
set
{
_Model.Name = value;
}
}
}
The entire model is never directly exposed as a public member of the ViewModel. The MainWindowViewModel handles collections of Models (private, the view cant see these) and ViewModels (public for View digestion):
Public Class MainWindowViewModel
{
Private List<Model> _NamesModel;
Private ObservableCollection<ViewModel> _NamesViewModel;
Public Void MainWindowViewModel()
{
//Lets pretend we have a service that returns a list of models
_NamesModel = Service.Request();
foreach(Model model in _NamesModel)
{
ViewModel viewmodel = new ViewModel(model);
_NamesViewModel.Add(viewmodel);
}
}
Public ObservableCollection<ViewModel> NamesViewModel
{
get
{
return _NamesViewModel;
}
}
}
Now thats the preamble but now I have a problem. How do I add a new ViewModel? Do methods within my view create a new ViewModel and populate that? Being a purist, I'm assuming the View should not be allowed to create or populate Models at all. Should my ViewModel contain a constructor that accepts nothing (i.e. no underlying model) and instead creates a blank to populate?
These kinds of issues keep coming up with a "pure" MVVM approach. I've had to create a public method in my ViewModel (bool compare(Model model)) that will compare a model (ready for deletion etc.) to it's internal one. If the models were publicly exposed (breaking purity) then it would be much easier to do stuff like find the ViewModel thats connected to a Model.
I can sympathize with some of those problems. I recently wrote an MVVM application where similar questions came up frequently. One of the tricks is to decide - definitively - which class is going to be responsible for Model instances. Do you want it to be your MainWindowViewModel? Or your NameViewModel? You don't want to share the responsibilities of creating/deleting the model between both of those classes; you'll have quite a logistical nightmare.
Secondly, even a "pure" MVVM approach doesn't dictate that you can't expose the model publicly. You said yourself that doing so would save you a lot of headache: DO IT. MVVM dictates only that the ViewModel has no knowledge/access of the View. There are many "official" MVVM examples that go so far as to implement their Model using the INotifyPropertyChanged interface, and bind directly to properties on the Model.
Personally, I think I would dictate control of the NameModel to the NameViewModel. This means that you should remove the list of NameModels completely from the MainWindowViewModel. If you want to give the NameViewModel an optional constructor which takes a Model, that would be fine too.
I'm a fan of this approach:
public NameViewModel : ViewModelBase
{
public NameModel Model
{
get { /* get stuff */ }
set { /* set stuff */ }
}
// Default constructor creates its own new NameModel
public NameViewModel()
{
this.Model = new NameModel();
}
// Constructor has a specific model dictated to it
public NameViewModel(NameModel model)
{
this.Model = model;
}
//Model wrapper properties
public String Name
{
get { return Model.Name; }
set { Model.Name = value; }
}
}
and...
public class MainWindowViewModel
{
Private ObservableCollection<ViewModel> _NameViewModels;
Public Void MainWindowViewModel()
{
//Lets pretend we have a service that returns a list of models
var nameModels = Service.Request();
foreach(Model model in nameModels)
{
ViewModel viewmodel = new NameViewModel(model);
NameViewModel.Add(viewmodel);
}
}
Public ObservableCollection<ViewModel> NameViewModels
{
get
{
return _NameViewModels;
}
}
}
In this way your MainWindowViewModeldoesn't keep an entirely separate copy of the Models; it only tracks the NameViewModels. Each NameViewModel is responsible for its own underlying model, while still making the option available to have a specific model passed to it during construction.
All the creation-related issues can be resolved with introduction of factory design pattern. The factory will take care of creating view models basing on model that was provided.
public class MainWindowViewModel
{
private List<Model> _NamesModel;
private ObservableCollection<ViewModel> _NamesViewModel;
private IViewModelFactory factory;
public void MainWindowViewModel(IViewModelFactory factory)
{
//Lets pretend we have a service that returns a list of models
_NamesModel = Service.Request();
_NamesViewModel = factory.CreateNamesViewModels(_NamesModel);
}
public ObservableCollection<ViewModel> NamesViewModel
{
get
{
return _NamesViewModel;
}
}
}
What is more, you could even get rid of Service dependency in view model and move it to the factory itself, thus reducing the need to keep model in view model (admittedly though, removal of model might not work in more complex scenarios):
public ObservableCollection<ViewModel> CreateNamesViewModels()
{
var models = Service.Request();
return new ObservableCollection(models.Select(m => new ViewModel(m)));
}
Also, your main window view model can expose commands that utilize factory to create any new instances. This way, no model is leaking to view and also no creation details are exposed (since commands will hide actual implementation).

PropertyChangedEvent and CanExecute issue

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Passing DataContext between windows in MVVM

On the main window onClick I have
AddNoticeAboutWrongCity addNoticeAboutWrongCity = new AddNoticeAboutWrongCity();
addNoticeAboutWrongCity.DataContext = ((VerificationViewModule)this.DataContext).WrongCityNotice;
addNoticeAboutWrongCity.ShowDialog();
At popup window there a lot of textboxes and two buttons
Delete object:
this.DataContext = null;
And second option "Save edited notice" which is not usable , because every change of user affection datacontext on main window,and this is demand from design department :)
I don't know why first option(it's "implementation" doesn't work.
Second explanation:
On the ParentWindow I have list of Notices and I can click EditSelectedNotice.
On the EditNoticeWindow I can edit Notice or delete Notice.
Editinig works(After closing EditNoticeWindow I see changed notice on the ParentWindow), but deleting doesn't (Notice is still in collection - on control and in this.DataContext)
My ViewModel:
class VerificationViewModule
{
public ObservableCollection<ReporterNotice> ReporterNotices { get; set; }
public ReporterNotice OtherNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Other).FirstOrDefault();
}
}
public ReporterNotice DuplicateNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Duplicate).FirstOrDefault();
}
}
public ReporterNotice WrongCityNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault();
}
set { if(value==null)
{
ReporterNotices.Remove(ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First());
}
else
{
if (ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault()==null)//there is always only max one instance of this type of notice
{
ReporterNotices.Add(value);
}
else
{
var c = ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First();
c = value;
}
}}
}
public VerificationViewModule()
{
ObservableCollection<ReporterNotice> loadedReporterNotices = new ObservableCollection<ReporterNotice>();
loadedReporterNotices.Add(new ReporterNotice() { Content = "Dublic", Type = ReporterNoticeType.WrongCity });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Hilton", Type = ReporterNoticeType.Duplicate });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Another notice", Type = ReporterNoticeType.Other });
ReporterNotices = loadedReporterNotices;
}
}
You can try the following. Implement the mediator to display windows and make sure that you use view models for the DataContext for both the main and edit windows. It is important to tell the main view model that the object is being deleted. This is done via a callback and routing that through a command on the EditNoticeViewModel
//This viewmodel is on the main windows datacontext
public class ParentViewModel
{
private readonly IWindowMediator _mediator;
public ParentViewModel(IWindowMediator mediator)
{
_mediator = mediator;
}
public ObservableCollection<Notice> Notices { get; private set; } //bound to list in xaml
public void OpenNotice(Notice notice)
{
//open the window using the Mediator pattern rather than a new window directly
_mediator.Open(new EditNoticeViewModel(notice, DeleteNotice));
}
private void DeleteNotice(Notice notice)
{
//This will remove it from the main window list
Notices.Remove(notice);
}
}
//view model for EditNoticeWindow
public class EditNoticeViewModel
{
public EditNoticeViewModel(Action<Notice> deleteCallback, Notice notice)
{
Model = notice;
DeleteCommand = new DelegateCommand((a) => deleteCallback(Model));
}
//Bind in xaml to the Command of a button
DelegateCommand DeleteCommand { get; private set; }
//bound to the controls in the xaml.
public Notice Model { get; private set; }
}
//This is a basic interface, you can elaborate as needed
//but it handles the opening of windows. Attach the view model
//to the data context of the window.
public interface IWindowMediator
{
void Open<T>(T viewModel);
}
Depending on implementation you might want to close the view when the delete button gets pushed. You can do this by implementing something like the as described here with respect to WorkspaceViewModel
Why don't you wrap the WrongCityNotice in a viewModel implementing IReporterNotice and having a reference to the parent viewmodel and a Delete method:
public void Delete() { _parentvm.Delete(_wrongCityNotice); }
You can use this wrapper as DataContext.
You're trying to destroy the DataContext. C# doesn't work that way. Setting an object reference to null doesn't delete the object, it only removes the reference to it. (When nothing references an object anymore it gets garbage collected, but you can't destroy an object directly).
DataContext = null only means that locally your DataContext doesn't point to any object any more. The main view model still has a reference however so nothing changes there. You'll have to ask the main view model to remove the notification from it's collection (probably through a callback method (Action) is best so you don't have to know about the parent view model).

Categories