MVVM - From Model to ViewModel leaving Model untouched [closed] - c#

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
Let's say I have a third-party back-end [C# .NET Standard] library representing my Model; this library is used purely as a data processing back-end tool.
Clearly no GUI, only some public readonly registers are provided in order to allow other parent/owning code to observe the status of those registers. As a simple example:
public class MyModel
{
public int MyVariable { get; private set; }
public List<int> MyCollection { get; private set; }
...
}
Now I'd like to provide a WPF application acting as a view-panel/dashboard so that a user can see on a monitor the live situation; I opted for a MVVM approach.
First questions are: is it a correct design approach to assume Model should be left untouched, without adapting it to ViewModel (forgetting Model is a third-party library the code of I actually don't possess)? how can I efficiently and correctly write ViewModel so that it can extract/retrieve those registers from Model?
I have gone through a time-based update option (each second re-reading those registers from Model), which honestly seems a little bit unefficient; I can't figure out how to intercept Model variable changing or Model collection changing from ViewModel side.
Then, let's drop for a moment the constraint I don't possess library code and consider I can modify Model (again, is it a correct design?). I would make MyModel implement INotifyPropertyChanged and change List<int> MyCollection into ObservableCollection<int> MyCollection so that I can intercept data changing and have binding to update at screen. But then I fall into Dispatcher issue when the CollectionChanged event got fired. How can I perform collection update since Model has no access to Dispatcher (.NET Standard)? How can I suppress CollectionChanged fired by Model and fire it from ViewModel inside Dispatcher?
I have gone through a Dispatcher forwarding solution but again I think this is somehow wrong, not properly coded:
public delegate void DispatcherService(Action action);
public class MyModel
{
public int MyVariable { get; private set; }
public List<int> MyList { get; private set; }
public event DispatcherService MyDispatcherService;
public void AddElementToMyList(int element)
{
MyDispatcherService?.Invoke(() => MyList.Add(element));
}
}
public class MyViewModel
{
private MyModel _myModel;
public int MyModelMyVariable { get { return _myModel.MyVariable; } }
public List<int> MyModelMyCollection { get { return _myModel.MyCollection; } }
public MyViewModel(MyModel myModel)
{
_myModel = myModel;
_myModel.MyDispatcherService += ((action) => { Application.Current.Dispatcher.Invoke(action); });
}
}

Usually your model classes don't need to implement INotifyPropertyChaged and INotifyCollectionChanged. This are interfaces that are meant to be implemented by view model classes to provide very general notifications for the view. The model should expose specialized events to notify the view model about data changes.
Since the library is 3rd party, you can only use it the way the API allows it to be used. When the library exposes dynamic data, it will most likely expose related events to which your view model can subscribe to.
Otherwise your view model would have to poll the library for data changes (in this case you may look for a more serious alternative library).
In MVVM the model should never deal with any Dispatcher. Dispatcher is a UI related concept. It deals with the UI thread affinity of UI objects (DispatcherObject). It is also responsible to manage the job queue of the UI thread.
The model component has neither UI related objects nor does it rely on an UI or UI thread.
In your described scenario the correct approach would be to subscribe to the model events from your view model. Then populate a ObservableCollection, which the view model exposes to the view for data binding.
According to MVVM the model does never access the view model. Therefore it is only the view model that has to do the marshaling of the CollectionChanged event or any other access to an object, which is associated with the UI thread (e.g., in case the access occurred from a different thread than the UI thread).
Your corrected example could look like this:
public class MyModel
{
public int Property { get; private set; }
public List<int> DataCollection { get; private set; }
public event EventHandler DataIsReady;
public void AddElementToDataCollectionOnBackgroundThread(int element)
{
Task.Run(()
{
this.DataCollection.Add(element);
DataIsReady?.Invoke(this, EventArgs.Empty);
}
}
}
public class MyViewModel : INotifyPropertyChanegd
{
private MyModel Model { get; };
private int viewModelProperty;
public int ViewModelProperty
{
get => return this.viewModelProperty;
set
{
this.viewModelProperty = value;
OnPropertyChanged();
}
}
public ObservaleCollection<int> ViewItemsSource { get }
public MyViewModel(MyModel model)
{
this.Model = model;
this.Model.DataIsReady += OnModelDataIsReady;
}
private void OnModelDataIsReady(object sender, EventArgs e)
{
// Since the event was raised on a background thread,
// the view model is responsible to marshal the collection change to the UI thread
Application.Current.Dispatcher.InvokeAsync(
() =>
{
this.ViewItemsSource.Clear();
this.Model.DataCollection.ForEach(this.ViewItemsSource.Add));
});
// PropertyChanged is automatically marshalled
// to the UI thread by the framework --> no dispatcher needed
this.ViewModelProperty = this.Model.Property;
}
}

It's perfectly OK for your model to be a wrapper around some library objects. In a perfect world, those library objects would be so full-featured that they would support observation patterns such as INotifyPropertyChanged. Then they could be used in all sorts of contexts, such as directly being the model of your MVVM. In your case, since the library objects don't support observability, it may make sense for you to create wrapper classes that provide that observability. Otherwise, you'll end up compensating for that in your view-model (which is also perfectly acceptable, although I like my scopes to be very clean when defining architectural boundaries).
As for the dispatcher thing, my instinct is that a model should not exhibit any thread affinity -- if something updates the model on a thread, notifications go out on that same thread. Since views are inherently thread-bound, the responsibility for marshaling notifications would either be in the view-model or the directly in the view. Of course, you could always marshal the call which updates the model, so that notifications are only occurring on the UI thread.

Related

How to notify all guard methods when global changes

Often in my applications built with Caliburn Micro I have a need to store some global data; this could be app specific config, authentication properties, etc. I generally put them in a class called "Session" and inject that via constructor injection so that every view model has a reference to a single instance of Session.
I found a case where I wanted a guard method on two different view models to be linked to a Session variable; the issue is guard methods are generally notified of changes in the setter of the changed variable. Since it's a global, it doesn't know what depends on it. (It occurs to me that this pattern of variables being aware of what guard is hooked into them is bad, but when it's all in the same ViewModel it doesn't matter much.)
I could throw an event, but that's messy and a lot of work for something that should be simple.
I could try to identify every spot where it may have been updated and manually notify, but that's error prone.
public class MyViewModel: Screen{
public MyViewModel(SessionInfo session){
Session = session;
}
public CanTakeAction { get { return !string.isNullOrWhitespace(Session.SomeProperty); } }
}
public class SessionInfo {
public SessionInfo(){}
public string SomeProperty { get; set; }
// this is where I would normally notify a guard method, but this is not going to work
NotifyOfPropertyChange(() => CanTakeAction); // except it doesn't know about CanTakeAction
}
One possible solution would be to introduce a base ViewModel, which has the guard methods (virtual). For Example,
public class ViewModelBase:Screen
{
private SessionInfo _sessionInfo;
public ViewModelBase(SessionInfo sessionInfo)
{
_sessionInfo = sessionInfo;
}
public void NotifyGuardMethods()
{
NotifyOfPropertyChange(nameof(CanTakeAction));
}
public virtual bool CanTakeAction { get; set; } = false;
}
For all the ViewModels that needs to be notified by the change in Session, you could now derieve from the ViewModelBase.
public class ShellViewModel:ViewModelBase
{
public override bool CanTakeAction { get=>//its own logic; set=>//its own logic; };
}
You could now introduce Events to the ViewModelBase, which could use the NotifyGuardMethods defined in the base class to notify all other view models. This ensures the messsy Events part would be restricted to one class alone (base view model).

MVVM Refresh Datagrid from ViewModel even when collection doesn't change

I'm writing an application to read and analyze some logs my company software uses. There are multiple types of logs, but let's take only two for the purpose of describing my problem. Namely, logs of TypeA and TypeB. I have designed one class to hold a single line of log data, named LogLine which looks like below.
public class LogLine
{
public long LineNum { get; set; }
public string Msg { get; set; }
}
So here's my problem/requirement.
In my main ViewModel, I'd like to read logs of each type only once when the application loads. Read TypeA logs one time, and store in an ObservableCollection of LogLine instances, do the same for TypeB. Then depending on my choice the DataGrid displays logs from one type, and if I click a button at any time, the same DataGrid should display logs from the other type. Note that my logs data doesn't change, I simply want to display my choice of logs.
For this I created three classes, namely, ControllerMain, ControllerA, and ControllerB. The last two derive from the former like so:
public class ControllerMain
{
public ControllerMain()
{
LogLineList = new ObservableCollection<LogLine>();
}
private ObservableCollection<LogLine> logLineList;
public ObservableCollection<LogLine> LogLineList
{
get { return logLineList; }
set { logLineList = value; }
}
}
public class ControllerA : ControllerMain
{
public ControllerA() { }
// More stuff here
}
public class ControllerB : ControllerMain
{
public ControllerB() { }
// More stuff here
}
As you can guess ControllerA is intended to hold logs of TypeA, and associated properties and methods unique to those logs. Same goes for TypeB logs.
In my ViewModel, I have instances of each of the classes above like so, and at application load I read log data and store in appropriate class object.
public ControllerMain COMMON_LOG { get; set; }
public ControllerA A_LOG { get; set; }
public ControllerB B_LOG { get; set; }
public ViewModelMain()
{
isAType = true;
ClickCommand = new CustomCommand(ClickCmd, CanClickCmd);
A_LOG = new ControllerA
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetAData()
};
B_LOG = new ControllerB
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetBData()
};
// This simulates switching to already loaded logs.
// When I do this the log lines don't change, but I want to refresh the datagrid and display correct info.
LoadAppropriateLog();
}
private void LoadAppropriateLog()
{
if (isAType)
{
COMMON_LOG = A_LOG;
isAType = false;
}
else
{
COMMON_LOG = B_LOG;
isAType = true;
}
}
My View binds to the COMMON_LOG instance like below:
<DataGrid Grid.Row="0" Margin="5"
Name="dgLogs"
AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding COMMON_LOG.LogLineList}">
Then at the click of a button, I call the above LoadAppropriateLog() method, so it will simply assign the instance of appropriate type to COMMON_LOG which is the instance I've used to data bind.
The problem is that when I do so, since the actual data in each instance's LogLineList doesn't change, the DataGrid doesn't automatically update to reflect my choice of logs.
Is there a way to manually refresh the DataGrid from my ViewModel after every time I switch the type of log?
If you'd like to run the project and see, here's a download link:
Download the VS Project
If you're binding to a property of a class in XAML, either
The property should never change its value after a binding would first see it, and should usually be readonly just to avoid mishaps -- or
The class should implement INotifyPropertyChanged and the property should raise PropertyChanged in its setter.
In your case, you're changing the value of COMMON_LOG, and you're never changing the value of its LogLineList.
tl;dr: So your main viewmodel needs to implement INotifyPropertyChanged, and raise PropertyChanged in the setter for COMMON_LOG. Anything that doesn't do those things isn't a viewmodel.
LogLineList being an ObservableCollection won't accomplish anything: What that class does is raise notifications when items are added, removed or replaced. That doesn't happen at any time after the binding sees it. Those instances of ObservableCollection don't even know that the main viewmodel even exists, so they certainly can't be expected to raise notification events when its properties change. Nor should they: Everybody is responsible for exactly his own notifications.
In fact, if you've made a design decision that those collections never change after initialization, use ReadOnlyCollection instead of ObservableCollection. Creating one is easy: Call List<T>.AsReadOnly<T>(). For any IEnumerable<T>, just call e.ToList().AsReadOnly(). ObservableCollection signals "you can add stuff to this". But nobody should. So don't give them ideas.

C# cannot cast from class type to interface type

I am trying to make a picture galerie in the mvvm pattern. The viewmodel should have a model of the type IGalerieModel so that the viewmodels supports different model types in case I want to create a view with other pictures in it.
The viewmodel looks like this:
GalerieViewModel<TView, TModel>
where: TView is Galerieview
where: TModel is IGalerieModel
To call methods from code behind to the viewmodel I need to cast the datacontext. For that I have a DataContext property. It is of the type:
GalerieViewModel<GalerieView, IGalerieModel>();
But if I try to cast the DataContext into this type it return null. The datacontext isn't null I checked that, it just can't cast it to the viewModel type.
Why is this and does anybody have an alternative solution?
Edit:
Some related code, where I try to cast the datacontext.
private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
{
var selectedItem = Images.SelectedItem as Image;
if(selectedItem != null)
{
var datacontext = DataContext as GalerieViewModel<GalerieView, IGalerieModel>;
datacontext?.RemoveImage(selectedItem);
}
}
I suggest using inheritance in this case.
I'm using the following terminology:
ViewModel an object which is an encapsulation of the data (both bidirectional and unidirectional) displayed and/or consumed by a single specific View.
For example, if your system has 3 screens/windows (LoginView, OrdersListView, OrderDetailView), each would have its own ViewModel (LoginVM, OrdersListVM, OrderDetailVM).
If your application is built around a single large view (and your application does not permit multiple instances of the view) then it is permissable to have a monolithic ViewModel with nested/child ViewModels for any child views (have fun with event binding, though!)
Model a business domain entity, of whose provenance is not the concern of the ViewModel. I believe it is valid to expose a domain entity object directly to the View for rendering as it helps DRY. In many simplistic and trivial cases you can use the domain entity object as the ViewModel directly, assuming you don't need bi-directional binding and data-transfer. But in many cases it is highly inappropriate.
View the presentation layer of your system, in this context we specifically refer to some specific subclass of System.Windows.Controls.Window or System.Windows.Controls.Control which acts as a discrete component.
As an aside, when MVVM is implemented correctly it is possible to substitute the entire WPF layer for some other presentation/view system or even the entire runtime environment, such as WinForms or UWP (but not ASP.NET MVC as the entire stateful paradigm breaks down).
In your case, I'm assuming IGalerieModel is an interface for your business domain entity types and it generally wraps/encapsulates bitmap data. I can't think of how the data could be different, but I'll just take your word for it.
So you have a single view, GalerieView, which I assume is a simple *.xaml view, with a lightweight, view-logic-only .cs CodeBehind file.
Your ViewModel should therefore look something like this:
public class GalerieViewModel {
public String WindowTitle { get; set; }
public String WelcomeMessage { get; set; }
public virtual ObservableCollection<IGalerieModel> Images { get; set; }
}
So there is no need to use generics here at all.
If you do want to use generics because elsewhere in your system there is knowledge about the exact type of object in the Images collection, you should implement that functionality in a generic subclass:
public class GalerieViewModel<TImage>
where TImage : IGalerieModel {
public new ObservableCollection<TImage> Images { get; set; }
}
(Note that you will need to write code to keep both ObservableCollection collections in-sync, note that ObservableCollection<IGalerieModel> is more permissive in what it accepts as input compared to ObservableCollection<TImage> - this is a downside of using mutable collections in generics, as well as how ObservableCollection works in practice).
Then you can consume it like so (using your example):
private void MenuItem_Click(Object sender, RoutedEventArgs e)
{
IGalerieModel selectedItem = this.Images.SelectedItem as IGalerieModel ;
if( selectedItem != null )
{
GalerieViewModel viewModel = (GalerieViewModel)this.DataContext;
viewModel.Images.Remove( selectedItem );
}
}
For convenience you can add a strongly-typed property to get the ViewModel in the View (this is okay because the View knows about the ViewModel, but the ViewModel doesn't know about the View):
protected GalerieViewModel ViewModel {
get { return (GalerieViewModel)this.DataContext; }
}
private void MenuItem_Click(Object sender, RoutedEventArgs e)
{
IGalerieModel selectedItem = this.Images.SelectedItem as IGalerieModel;
if( selectedItem != null )
{
this.ViewModel.Images.Remove( selectedItem );
}
}
You might want to subclass Window to add strongly-typed ViewModel support:
public abstract class Window2<TViewModel> : Window {
protected TViewModelViewModel {
get { return (TViewModel)this.DataContext; }
}
}
GalerieViewModel<GalerieView, SomeModel> and GalerieViewModel<GalerieView, IGalerieModel> are two completely independent types that cannot be casted.
SomeModel implementing IGalerieModel does not propagate out of the generic.

MVVM: Modified model, how to correctly update ViewModel and View?

Case
Say I have a Person class, a PersonViewModel and a PersonView.
Updating properties from PersonView to the Person model is simple enough. PersonViewModel contains a Person object and has public properties the PersonView binds to in order to update the Person model.
However.
Imagine the Person model can get updated by Service. Now the property change needs to be communicated to the PersonViewModel and then to the PersonView.
This is how I would fix it:
For each property on the Person model I would raise the PropertyChanged event. PersonViewModel subscribes to the PropertyChanged event of Person. PersonViewModel would then raise another PropertyChanged in order to update the PersonView.
This to me seems the most obvious way but I kind of want to throw this question out there in the hope of someone showing me a nicer way. Is it really this simple or are there better ways to mark the model as modified and update the respective properties on the ViewModel?
Additions
The PersonView's DataContext is PersonViewModel. Person gets populated from JSON and gets updated many times during its lifetime.
Feel free to suggest architectual changes for my particular case.
Answer
I marked aqwert as the answer of my question since it provided me with an alternative to the solution I already proposed.
When the view binds directly to the model (which is also the case when the ViewModel exposes the Model) you are mixing UI code and data code. The goal of MVVM is to separate these two code domains. That's what the ViewModel is for.
The view model has to have it's own properties the view can bind to. An example:
class PersonViewModel
{
private Person OriginalModel { get; set; }
public ValueViewModel<string> Name { get; set; }
public ValueViewModel<int> Postcode { get; set; }
protected void ReadFromModel(Person person)
{
OriginalModel = person;
Name.Value = OriginalModel.Name;
Postcode.Value = OriginalModel.Postcode;
}
protected Person WriteToModel()
{
OriginalModel.Name = Name.Value; //...
return OriginalModel;
}
}
Using such a ViewModel-design really separates your data objects from your user interface code. When the structure of the class Person is changed, the UI doesn't need to be fit accordingly, because the ViewModel separates them from each other.
Now to your question. As you can see in the example above, I used a generic ValueViewModel<T>. This class implements INotifyPropertyChanged (and some other stuff). When you receive a new Person instance, you only have to call ReadFromModel(newPerson) on your ViewModel to have the UI updated, because the ValueViewModels the View binds to will inform the UI when their value changes.
Here an extremely simplified example of the internal structure of the ValueViewModel:
class ValueViewModel<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{
get { return _value;}
set
{
_value = value;
RaisePropertyChanged("Value");
}
}
}
This is an approach we used in our MVVM library. It has the advantage that it forces the developer to clearly separate code from the designers concerns. And, as a side effect, it generates a standardized code layout in all your Views and ViewModels and thus improves code quality.
If the view is binding to the Model directly then as long as the service is using the same instance any changes to the model properties will be propogated to the view.
However if you are recreating a new model in the service then you will give the viewmodel the new model. I expect to see the model as a property on the view model so when you set that property all binding should be alerted to the change.
//in the ViewModel
public Person Model
{
get { return _person; }
set { _person = value;
RaisePropertyChanged("Model"); //<- this should tell the view to update
}
}
EDIT:
Since you state there are specific ViewModel logic then you can tailor those properties in the ViewModel
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "Prop1") RaisePropertyChanged("SpecicalProperty");
...
}
public string SpecicalProperty
{
get
{
reutrn Model.Prop1 + " some additional logic for the view";
}
}
IN XAML
<TextBlock Text="{Binding Model.PropertyDirect}" />
<TextBlock Text="{Binding SpecicalProperty}" />
This way only both the Model and ViewModel propertys are bound to the view without duplicating the data.
You can get fancier creating a helper to link the property changes from the model to the view model or use a mapping dictionary
_mapping.Add("Prop1", new string[] { "SpecicalProperty", "SpecicalProperty2" });
and then find the properties to update by getting the list of properties
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
string[] props;
if(_mapping.TryGetValue(e.PropertyName, out props))
{
foreach(var prop in props)
RaisePropertyChanged(prop);
}
}

List / Detail Different Windows can they be synchronized and databound to the same collection?

I have a listbox that is bound to a List<T> -- this is working great.
I'd like to let my users double click a listbox item and open a new window that will display the "detail" view for that record. I'd like this new window to be databound to the same collection as the listbox on the original window. Because, that window has a timer, which polls a webserivce for updated data, I'd like the child (detail window) to also update when the main list updates.
Is this easily done? An example would be great, but any help is appreciated!
You could share the data directly (ie pass the SelectedItem reference to the child window), but that doesn't help you manage behavior and state across multiple windows. If it is a read-only view that's less of a problem, but if data is being changed it gets very problematic very quickly.
This is a good example of the benefits of using a Model-View-? pattern. MVVM is usually the preferred pattern for WPF because WPF is designed for complete separation-of-presentation. However, in a case like this, you may want something closer to MVC (Model-View-Controller), because you really want to coordinate behavior and state between distinct UI elements.
I would recommend a hybrid approach, let's call it "MVVMC" just to make the acronym even longer and more awkward. Implement a ViewModel that is completely UI-agnostic, and just exposes data and data-related state/behavior--probably mostly CRUD type stuff. Then implement a Controller specific to your UI design that consumes and exposes (either by delegation or composition) the ViewModel, but encapsulates. all of the the multi-window display behavior -- things enforcing one window per item, propagating close requests, etc.
public class MyViewModel : INotifyPropertyChanged, INotifyCollectionChanged
{
public MyViewModel(DataModel dataModel) { ... }
}
public class MyController
{
public MyController(MainWindow mainWindow, ViewModel viewModel) { ... }
public ViewModel { get { return _viewModel; } }
public ICommand DisplayChild { ... }
}
So what you're really doing is taking an MVVM, then inverting control so the controller can manage the multi-window UI. So the controller here would inject the ViewModel into windows (including main) as the DataContext for easy binding. It would also bind to events on the main window, launch child windows, and probably bind to child window events in order to manage them properly (eg one window per child record, close children when main closes, etc).
I would go one step further here, implementing the controller against an interface instead of Window. This gives you some flexibility in refactoring, but more importantly allows you to unit test your controller against mocks.
public interface IControllerChild
{
public void Show();
public bool Activate();
public void Close();
// add other behaviors here
}
public class DetailWindow : Window, IControllerChild
{
// implement other behaviors here
}
public class MockControllerChild : IControllerChild
{
public void Show() { IsShowing = true; ActionLog.Add(MockControllerAction.Show); }
public void Activate() { IsShowing = false; ActionLog.Add(MockControllerAction.Activate); }
public void Close() { IsShowing = false; ActionLog.Add(MockControllerAction.Close); }
public bool IsShowing { get; private set; }
public IList<MockControllerAction> ActionLog { get; private set; }
// mock and record other behaviors here
}
public enum MockControllerAction
{
Show,
Activate,
Close,
// Add other behaviors here
};

Categories