Reputation: 8144
I have a datagrid, which binds to an Item object which implements INotifyPropertyChanged.
In the ViewModel I subscribe to changes from an external device service, which updates the Item object. The datagrid is editable, so Item can also be changed from the View. The value should be written to the device, but not yet updated in the view, as the device write might fail. If it succeeds, the device will issue an event, which I already have subscribed to.
Some of my concerns are.
Where do I call write on the device service, from the ViewModel or from the Item object? How to ensure the value displayed in the datagrid is "reverted" after editing, until receiving the event from the device?
Some thoughts
If it's the Item object, then the Item object is no longer a DTO, but rather a ViewModel I guess. So I'll have two view models for the same view (user control). One for the user control and one for the items in the datagrid. That doesn't fit my understanding of a view model. But perhaps it's wrong? And how does the Item then know if the value was updated from the view (by the user) or the view model (by the device service)?
The ViewModel subscribes to PropertyChanged on the Item object. To detect if value is changed from View, the ViewModel can unsubscribe from PropertyChanged or setting a flag when getting events from the service. It seems clumpsy, but will work. Perhaps I should make two properties: ViewValue and ServiceValue. The ViewModel should update ServiceValue and subscribe to ViewValue, where it can revert ViewValue to ServiceValue after having read it.
The View handles CellEditEnding and notifies the view model
Upvotes: 0
Views: 496
Reputation: 4632
Regarding point 1): Yes, use the ViewModel to bind against the UI rather than the DTO. This is the main idea to help separating the data from the view in MVVM.
As for points 2) and 3) I suggest you to implement the IEditableObject
interface on your ViewModels, possibly on a common base class.
This Interface provides the methods BeginEdit(), CancelEdit(), and EndEdit()
. Using these methods gives you a clean and readable control over when which object is modified and when to submit these changes e.g. to a service or a database.
Also, implementing these methods this gives you a mechanism to handle those different data Versions like the mentioned "ViewValue" and "ServiceValue". You could create a local copy of the ServiceValue as a fall-back when method BeginEdit()
is called and recall this local fall-back value if method CancelEdit()
is called. If EndEdit()
is called, this is the time to submit the values back to the service and after that succeeds store the current Value in that local temp Value as the new fall-back.
Edit to add answer for point 1)
I am not sure if this is a "best practice", but at least this is what we would in at work.
We usually organize the ViewModels in such a way that we have one ViewModel per view that contains all information for a certain view. Let's call this the "main ViewModel". If we need a hierarchy of views, we would implement an hierarchy of ViewModels that follow the same hierarchy.
In a case like yours we would put the DataGrid into another UserControl
that is contained within the main View. The main ViewModel would hold another ViewModel that again holds the data needed for the DataGrid. This ViewModel would be set as the DataContext of that DataGrid-UserControl. This way you still preserve a clean one-ViewModel-per-View separation.
Additional formatting of the data in the DataGrid might then be configured va Templates
.
Upvotes: 2
Reputation: 6112
1) Having your DTO class be updated by your external data service will not make it in to a viewmodel. Your DTO is part of your model or business object layer. Its job is to carry data between the presentation (view and viewmodel) and storage layers (database), and perform validation (if needed). Being updated by an external (non-view) service is well within the scope of its role. Your comment about one VM per window or usercontrol isn't wrong - VMs are assigned on a per view, not per control, basis (though it is possible to reuse the same VM class for multiple views). You should have the instance of the DTO that the view binds against on the VM. That's the VM's job - to organize, manage, and present to the view the required parts of the model.
To have the DTO be aware of where the update originated from, as you mentioned in the question, you could add a flag to indicate whether it comes from the view or service and set as needed. Alternatively, you could make the service update the DTO through a public method, and have the view bind directly to the properties. Depending on your design, this option might be considerably less feasible than setting a flag.
2) IEditableObject
, as mentioned in the other answer, is a good choice. Having backups of both the service and view data on your DTO is unnecessary, and effectively triples the size of your object (assuming it's a thin wrapper, which it ought to be if it's a DTO). If you really need the ability to revert to either version of the data - say, because you give the user the option to pick which version to use when reverting - keep the extra data around on your VM instead of on the model. I can't think of a reason you'd need the option that doesn't involve some kind of presentation or user interaction decisions. Data driven reverting should be designed to always be deterministic.
3) Again, IEditableObject
. The view should not be notifying the viewmodel; it should be ignorant of the VM (not have any references). This will loosen the coupling between the two layers and increase the flexibility of your application. I typically achieve this by having a controller class dedicated solely to handling views. When it opens a view, it creates a new VM instance and points the view's DataContext
at it.
Upvotes: 1