Dealing with WPF objects inside ViewModel
Using the MVVM pattern creating WPF applications you have the ViewModel providing data to the View. I've met a situation where I find it reasonable to create WPF objects in my ViewModel, and the View fetches these and shows them. More specifically I have functionality for drawing where I need to store an InkPresenter in the end. I receive the mouse gestures in the code-behind of the view, but pass the events to the ViewModel. The ViewModel handles the mouse event 开发者_StackOverflow中文版and creates drawing objects that is put in an ObservableCollection such that the View can fetch them out and display them.
The question is; is this okay, or is it bad practice to create WPF objects in the ViewModel classes? And why? If it is okay; are there any best practices or recommendations to dealing with these objects?
No, it's "not okay" (quotations as I am talking about best practices - saying a ViewModel is no ViewModel because it does so is as wrong) to create and reference WPF (Control-)classes inside your ViewModels.
A very big disadvantages comes in when you try to unittest your ViewModel: The controls need special treatments and represent not-wanted dependencies.
You should only pass the actual "data" of those objects to the ViewModel: For the example with the InkPresenter it would be O.k. in my opinion to send the ViewModel the StylusPoints but not the InkPresenter itself. Even better would be ViewModel classes representing the StylusPoints - they could be mapped to the actual StylusPoints by databinding (I don't know whether this is possible with the InkPresenter) or via some mediators (e.g. using attached properties).
For creation you can use some mediator, too, which you pass e.g. ViewModels and which then creates the suitable WPF-Control and sets the VM as its DataContext.
I ran into an interesting case for this just today. I'm building a UI that includes a collection of FlowDocument
s. These items are presented in a ListView
, one of whose cell templates is a RichTextBox
.
The first problem I ran into is that the Document
property of the RTB isn't a dependency property, so you can't bind to it. Fortunately, some intrepid developer out there ran into this problem before I did, and implemented a RichTextBox
subclass with Document
overriden as a dependency property.
All was well and good until I implemented drag/drop reordering of the collection. Here I discovered one of the reasons why Document
isn't a DP. Changing the order of the collection doesn't actually change the order of the objects they're bound to; the items that moved just refresh their binding targets. And glory be, if a FlowDocument
belongs to one RichTextBox
, you can't assign it to another. Reordering these FlowDocuments
breaks the UI, which is a problem because the whole reason I'm doing this UI in the first place is for reordering these items.
The hammer that I decided to hit this with was to make the RichTextBox
a property of my view model. That way, when I move the FlowDocuments
around in the collection, the RTBs that own them move with them.
Personally, I don't give a fig about whether or not this class is unit-testable. That's not what makes this the wrong answer.
What makes this the wrong answer is that now that I've put this component into my class, it's not part of the event routing system anymore. If I change the FontFamily
on my application-wide FlowDocument
style, these controls never hear about it. I also can't bind anything to them. (Not easily, at least.) There are probably other problems I haven't thought through yet.
I'm not quite sure what the right answer in my case is going to be yet. I think that I may have to fix my bindable RichTextBox
so that it maintains two FlowDocument
objects: the one exposed to the world by its Document
getter and setter, and the one inside the control itself. (That is, when something sets the Document
property on the object, it saves that value in its private field, and then copies the document's content into the content of the base Document
property.)
That's a very long-winded way of saying, "creating WPF objects in the view model seems like a pretty bad idea now that I'm doing it."
精彩评论