I'm wondering how an application like Adobe Photoshop, could be architectured to conform to the C+M+V+VM design-pattern, specifically how do you handle the canvas?
There would be a Model system which represents the current document, with its layers (each containing raw pixel data, potentially gigabytes worth), effects applied to layers, and other information about the document state.
Then you would have the ViewModel for the Main Window which would have child-ViewModels for each tool-window, as well as a child-ViewModel for the main canvas area. My question relates to how the ViewModel would represent the current canvas data. It is the View's responsibility to draw the pixels of the composited document to the screen (or some virtual output device for tests), but who has responsibility to compose the document? Is it correct to even compose the document at all? – especially if the View is particularly advanced enough to have the capability to do that itself, such as hardware-accelerated Views. What if composition is done at the ViewModel layer on the GPU, the data would have to be passed back into main-memory and then copied again to the GPU for the View – the optimization of keeping the data in GPU memory cannot happen because the View and ViewModel should be ignorant of each other.
Is there a solution to this problem, or would a different design-pattern be appropriate?
Best Answer
There is something I had to learn while designing medical imaging / data visualization software in WPF/MVVM:
I'll elaborate.
Let's take an example of an accounting system. There you can get every application functionality purely in the model, but it would be weird for an human to deal with all the accounting concepts without visualizing them. Enter the View, purely as an aid (a true Graphical User Interface in a Human-Computer-Interaction sense), so the human operator can see the state of the underlying information, which has not a visual by itself.
On the other hand, let's take your extreme example: an application whose very purpose is to manipulate images. That is, the core Domain Model class is the Composite Image, with its layers, and each one with its pixel effects, and masks, and so on.
One core concept of MVVM, Data Binding totally loses its sense here: how are you suppose to have a property in the ViewModel, which is bound to a property in the View, if the very thing you are representing is already composed of pixels? At least, I suppose, your code-behind would need to be very rich (instead of non-existant in purist by-the-book MVVM).
What I have been doing lately (the current application I am developing has a custom realtime plotter) is more or less like this:
My Model layer is a Domain Model, containing only the conceptual classes related to the broad problem, and the rules by which the objects cooperate with one another. Nothing application-related (that is, which might vary between different applications working on the same domain) lives in this layer, which by the way does not contain WPF infrastructure. Good class candidates would be
CompositeCanvas
,CanvasLayer
,PixelEffect
,Mask
,Tool
, etc.The ViewModel layer would contain application logic, that is, how the current application chooses to manipulate the domain model objects. Here we start to use WPF related classes. For example, I most probably would represent a
CompositeCanvas
with aWriteableBitmap
, since it can be directly used as theSource
property of anImage
control via classic Data Binding.The View layer would be the dumbest as possible. Apart from the skinned input controls (toolbar buttons, custom mouse cursors and manipulator handlers), the actual CompositeImage would be composed of one or more WriteableBitmaps stacked into a Canvas or Grid or ViewBox (or a combination of them).
One important thing to consider is how the layers interact to each other:
Image
controls over one another inside a grid.Image
to represent every layer, and apply the composition to its BitmapSource, which might be aDrawingImage
with aDrawingGroup
containing multipleImageDrawing
, one for each layer. That transfer the compositing to the ViewModel.It is interesting to "visualize" (pun intended) how, for example, drawing with a self-smoothing, brushed pencil would work: with the selected tool, you click on the image and drag. You could implement your DataContext (ViewModel) as an Interface, say
ITool
and make mouse events like MouseMove call methods on the ViewModel. You would then store the drawing-space (not screen-space) coordinates in an array, which could be continuously smoothed by some algorithm defined in the model. Then, you can regenerate your image source (that is, render it in the model), and in the ViewModel you just set its result to theCanvasSource
property, and the property changed event would automatically update the View. This is quite different than most mouse-drawing demos found using WPF.Hope this helps!