Hi,
the deeper I try to understand the working of the MVP model in graphical cases, the more I sink into confusion. Can you help me on this methodological issue? Ian sent a beautiful example in an earlier e-mail with the followings: "" NB1 This is very "bare bones" but should give you some ideas. NB2 This is not the only way and some of these classes are not really needed in this example. Create an Object subclass called MyDrawingTool Add one method - drawOn: aCanvas within: aRectangle aCanvas ellipse: aRectangle Create a Model subclass called MyModel Add one method - drawingTool ^MyDrawingTool new Create a Presenter subclass called MyPresenter Add one class method - defaultModel ^MyModel new Create a View subclass called MyView Add two methods - initialize super initialize. self setModel: MyModel new onPaintRequired: aPaintEvent | canvas | canvas := aPaintEvent canvas. self model drawingTool drawOn: canvas within: ((Point zero extent: self extent) insetBy: 100) Finally, open a workspace and evaluate the following. This creates a resource of MyView (it will now appear in the resources list) and makes it the default view for MyPresenter. The #initialize above method just ensures that we open the resource in the ViewComposer even when it does not have an attached Presenter/Model. MyPresenter addView: MyView asResource: 'Default view' You can now go the presenter and edit it's default view, as normal, to set the background colour etc. NB. You have now created an MVP triad that appears in the resource list and which can be dropped onto any composite presenter in just the same way as any existing resources. You can now add code to MyModel to change the drawingTool answered (probably by adding more DrawingTool classes to the image) and also code to MyPresenter to allow input (a pop up menu for example) to change triads drawing tool on the fly."" This works beautiful until.... until I try to "add code to MyModel to change the drawingTool answered" Let's say I would like to differentiate that in one case I would like to draw a line, in another an ellipse, and so on, i.e. a graphical object can be a line, a stroke, an ellipse and so on. And the job of the draw is different in each case. Where do I put the differentiating logic: in the Model? In the Presenter? In the View? I studied intensively the graphical examples, and the graphical goodies of the masters (OA, Ian, Bill, Chris), but my confusion only inreased. The pieces of the mosaic in my understanding are as follows: In Scribble the logic is in the Presenter (the DrawOn. in the Scribble presenter). I can imagine this as follows: I create an OrderedCollection, where the elements can be #line, #dot, #ellipse... and in the presenter I call the different drawing routines. Also Chris's excellent Graphic Base Goodie seems to delegate the Draw function into the presenter, and it is very elegant in the sense that the view does not even know about it's model, so there is not even a model in it.. Other places (like Bill) tell sometimes, that it is good, if the model knows, how to draw itself. This would mean the logic of drawing line, ellipse... should be in the model... And obviously all of them work. So my confusion is that at the end the view is responsible to "repaint" itself, or a part of itself (a small rectangle). In other cases you put the logic into the "graphical" presenter part. In other cases "the model knows how to draw itself"... I tend to think, that I must put a logic into the presenter, which goes through the OrderedCollection one by one, and for each element it executes the "LineDrawOn:", the "DotDrawOn:", the EllipseDrawOn:" depending on the type of the element. Only I have have a bad feeling about this approach, because it looks for me too procedural: in the presenter a big classical "if then else" chain, although the Line, the Dot, the Ellipse are (or can be) different objects in the model, which "know themselves, how to draw themselves". Can you bring me light into my darkness? Many thanks in advance, Janos |
Janos,
> the deeper I try to understand the working of the MVP model in > graphical cases, the more I sink into confusion. [] > Can you bring me light into my darkness? It's a bit of a wimp answer but I don't think there is any right or wrong way, it depends on what you are trying to achieve. For example.... It can sometimes make most sense to put drawing code in the View. Something like drawing a watermark, permanent icon or grid lines on every page. Sometimes the Model might feel right, when the Model contains the data that is being drawn as a graph for example - although putting the code in the View might be just as sensible. Sometimes it is better to create individual objects that draw themselves (as in Scribble, Playground or BouncingBalls). Personally I like lots of classes :-) so to display different shapes, as described in your post, I would probably do something like the Playground demo. -- Ian Use the Reply-To address to contact me. Mail sent to the From address is ignored. |
In reply to this post by Janos Kazsoki
Janos,
In general (as you have noted), I recommend separating the drawing from the medium. Doing so allows you to test the code without running into the problems that result from having troubles on presenter startup. Note that the above is not inconsistent with any other approach. You can still invoke the separate rendering component from anywhere you want. By keeping the graphics "non-graphical", you can use a do-it to generate a bitmap. Early on, you would expect walkbacks, so don't even bother with a presenter. Fix the problems until it runs. Then, use an image presenter to look at the bitmap. Still not right? Fix that. Then you can apply the graphics anywhere, especially if you utilize the resolution of the canvas. Re updating the display, there is no magic solution. One approach is to always draw to bitmaps and to use ImagePresenter to display them. The trick is to invalidate the correct portion of the view's client area after changes (more below). On the other extreme, you can let individual objects draw on a canvas that turns out to be the view's canvas - note that the separate drawing object(s) concept works here too. Efficiency results from limiting the number of objects that need to be drawn. This usually starts as either an object that has changed, or by some portion of the view being uncovered, either of which can be reduced to an invalid rectangle. Note that a long/thin object affect a large rectangle (life is seldom fair<g>). Given the invalid rectangle, do the best you can at discarding objects that you know do not interesect the rectangle, and do not draw them. Do draw the others. Again consider a long/thin object. It might go from far to the left to far to the right of the rectangle, with only a small portion of it interesecting the rectangle. It must be drawn, but you might do well to inform it of the rectangle so it can draw only what is needed. With large numbers of small objects, a boolean test on intersection goes a long way. Clearly if you choose to cache graphics in a bitmap, then it needs to be updated as above, and then the view needs to be updated too, though the latter is simply #invalidate: followed by a bitblt (hopefully the view is smart enough to look at its invalid rect in the paint struct and draw only that much of the bitmap. All of this was illustrated very well in the early MFC tutorials. Yes, Bill is giving Microsoft credit for doing something right - mark your calendars :) However, the most recent time I looked at the MFC tutorial (which has been a while), it went on an on about "smooth scrolling" and did not appear to address optimization that I could find. Also, have you noticed the little pieces of "left over selection" that sometimes appear in Microsoft lists and trees? The screen scarring that occurs with the animated menu effects? That stuff is a symptom of incorrect drawing. You need to tell the model what is going on so that it can correctly update the display _any_ time that it must be drawn, not only on selection, etc. Those things that scar the screen do so because somebody thought it was sufficient to invert a rectangle on selection, rather than altering the model and then invalidating the relevant rectangle, or otherwise at least arranging for it to do so should it be asked to update the display. Put another way, if you draw in two places, they need to agree, or you need to be certain they never interact (which is a risky assumption when the machine is busy and the user wants to do work rather than watch cool graphics). Does that help? Have a good one, Bill -- Wilhelm K. Schwab, Ph.D. [hidden email] |
In reply to this post by Janos Kazsoki
Janos Kazsoki wrote:
> So my confusion is that at the end the view is responsible to "repaint" > itself, or a part of itself (a small rectangle). In other cases you put > the logic into the "graphical" presenter part. In other cases "the > model knows how to draw itself"... It's an interesting issue. There is, as Ian said, no single correct answer. The responsibility for rendering may end up on the Model, View, Presenter, or somewhere else. All can be "best" in some specific situation, but what determines when ? BTW, a lot of this stuff I hadn't worked out before you raised the question -- I had been just following my instincts, and had never really tried to work out /why/ I've done things the way(s) I have. (Which means that this post will be rather long -- I'm thinking aloud...) Anyway, here's my (current) take on it: I think the answer is not very much about "good" software structure (giving responsibility to the right objects) so much as it is about more general issues of software lifecycle and re-use. Let's start with a rough characterisation of the standard components in MVP in those terms: Models are domain-specific rather than application-specific or generic. If there are several applications in the same domain then they are likely to share Model code. On the other hand, an application (even a similar application) in a different domain would use different models. (There are also some "dumb" generic model classes that go with generic views, but I don't think they affect the picture much.) The Presenters are application-specific, rather than domain-specific or generic. Of course there are some "dumb" generic Presenter classes (such as ListPresenter) that go with generic Views, but any interesting application will have at least one "interesting" Presenter -- and once you've described what those Presenters do, you have also described your application. The Views are generic, pluggable, objects. They are assembled by aggregation to make application-specific GUIs. It is rare to write a View at all, let alone write an application-specific one. In general a View is conceptually very simple (even though it may have a complicated implementation). You can usually describe what it does in one short sentence. Because they are intended to be generic, there's a strong tendency for Views to have pluggable behaviour and lots of settable "attributes". Of course, an important point about Views is that they are supposed to work well with the View Composer. Typically you write a View class once and then re-use it in many un-related projects. But, once a View is written, you don't often touch it again (apart from bug-fixes, and maybe a few more-or-less trivial enhancements). That's mostly because each View is conceptually simple, so there's not much to change, but also because it is rather awkward to modify View code once existing instances have been saved as resources (just as a practical point) . So, when we have graphics to draw, who gets the job ? In some cases it may be clear that it's the Model's job. If the Model is inherently a graphical "thing" then it seems silly for it not to know how to do graphics. (But I'll return to this point later.) For instance, a Diagram or Map (in the geographical sense) might be inherently graphical. Many applications in the domain that use a Diagram or Map will have a graphical element, and the Models know how to provide that. In rather more cases, the Model is not inherently graphical. Often the graphics will be application-dependent, and in such cases it seems that some Presenter should take responsibility for it. It can't be handed off to the Model because that shouldn't know about graphics ("not my job, mate!"), and it isn't generic enough for a View to handle naturally; but it fits nicely into the MVP framework for one of the "interesting" (application-specific) Presenters to take on the job. OA's "Scribble" is example of this kind of thinking (IIRC). And in my 'Graphics Base' package, the PaintingPresenter is designed specifically for this case -- you can either subclass it or handle its #paintCanvasRequired:in: event in a higher-level Presenter. The PaintableView objects just provide simple generic graphical services like double-buffering. (BTW, that package was written some time ago, and I now think it needs a number of improvements.) Lastly, the graphics may be generic. In that case the natural implementation is for a View to handle it. E.g. given some "standard" file format for graphics, PDF perhaps, the job of rendering PDF on-screen is a very generic job, and a specific View class for rendering that format would be useful. (The file, or something derived from it would be the Model). It's natural, as you write several applications, to discover common code and move that into some kind of more generic framework. In the case of graphics, that means there's a tendency for graphics code to start off in Presenters, but after a while get generalised and move into some View. (Incidentally, one of the things that I've realised is wrong about my 'Graphics Base' package, is that I've ended up using PaintableView as a base-class for a number of such "generic" graphical Views. It's handy as a base-class because the new Views can inherit services it provides, like double-buffering, but really that's not a good hierarchy. I should have an abstract base class that provides the services, and the PaintableView and the other Views can all inherit from that.) Returning to the case where the Model does the graphics. There are a few reasons for wanting to factor out the responsibility for the graphics into a fourth component. One is that graphics code tends to be complicated/intricate, and it can just over-complicate the Model. Another is that there will often be /some/ part of the graphical code that is purely about presentation (choice of colour-scheme for instance). So you can end up with anything from a "Settings" object that holds presentation-specific state (colours, line-widths, and so on), through to a full-blown "Renderer" object which knows how to draw a Model on a Canvas. In either case the Model holds domain-specific state, whereas the Settings/Renderer is much more generic (it may even be fully generic if it's a mere "Settings" object), and whatever state it holds is more relevant to presentation than to the domain. In a similar way, if the View has responsibility for graphics it may still be worthwhile splitting the code out into an associated helper object. One reason is, again, that Views are already pretty complicated, and adding complicated graphics code too can muddle things up. Another reason is that it makes it easier to paint the same graphics in other contexts (printing for instance, or in testing/development as Bill mentioned). Such an object would be a Settings/Renderer object in much the same way as above (but, since it's attached to the View, is almost certain to be fully generic). In either of the above two cases, it can be a good idea to make the helper object an editable aspect of the View. That allows you to edit it in the View Composer, which can make life quite a bit easier. (Yet /another/ thing that's missing in my graphics framework is a generic "RenderingView" which holds a Renderer, and uses that to paint its Model on the screen.) And that's it. No simple answers, I'm afraid. At least I understand what I've been doing now ;-) But I'll have to rewrite 'CU Graphics Base'... -- chris |
In reply to this post by Schwab,Wilhelm K
Bill,
[just a couple of thoughts] [re: separating the drawing from the medium] > Doing so allows you to test the code without running into > the problems that result from having troubles on presenter startup. Nah, can't be bothered with all that. Just splat it in the window and debug it if it breaks... (Of course, you do need an error catcher around the code before you can risk that ;-) [re: optimised repainting of only the "wrong" bit] > All of this was illustrated very well in the early MFC tutorials. Yes, > Bill is giving Microsoft credit for doing something right - mark your > calendars :) However, the most recent time I looked at the MFC > tutorial (which has been a while), it went on an on about "smooth > scrolling" and did not appear to address optimization that I could find. Possibly because they feel that for simple graphics on a modern machine, there's little or no need for optimisation. Or at least that it's an "advanced" concept, not needed by everyone. If so, then I think they are right (this /is/ a day for the calendars, folks!). > Also, have you noticed the little pieces of "left over selection" that > sometimes appear in Microsoft lists and trees? The screen scarring that > occurs with the animated menu effects? That stuff is a symptom of > incorrect drawing. Which in turn is probably caused by someone trying to optimise the painting, and getting it /almost/ right... ;-) -- chris |
In reply to this post by Janos Kazsoki
Janos Kazsoki wrote:
> I tend to think, that I must put a logic into the presenter, which goes > through the OrderedCollection one by one, and for each element it > executes the "LineDrawOn:", the "DotDrawOn:", the EllipseDrawOn:" > depending on the type of the element. > > Only I have have a bad feeling about this approach, because it looks > for me too procedural: [...] Hmm... missed that bit. So here's part II :-) If your models, or parts of your models, are really such "graphical" objects as names like Ellipse, Line, Dot, suggest, then I'd say it would be natural for them to know how to draw themselves. That's not the most common situation, though. If your objects are things like ConnectingRod, Bolt, GearChain, then it would be rather odd if they knew how to draw themselves. And it would be downright weird if objects like EndOfYearStatement, SeasonallyAdjustedEconomicIndicators, PhysicalInventory, knew. In such cases, I think it's much more likely that your painting code wouldn't need the kind of test-and-switch logic, since it would be asking the model for data and using the answers to construct the drawing ("how many gears are there and where are they placed ?" Ok, I'll draw circles here, here, and here. "What was the largest monthly gain ?", right I'll draw a grid this high... and so on). I doubt if there are many applications that don't fall into one or the other of the above pattern. One kind of application that doesn't fit is when you have to render externally-defined data. For instance if you were given a "graphics" file that consisted of lines like: E 0 0 100 100 0xFF000 C 50 50 50 0x0 ... (meaning ellipse from (0,0) to (100,00), red circle at (50,50) radius 50, black ...) then you'd have to have a cascade of tests, or a dictionary lookup, somewhere in your rendering code (whether that code is in your Model, View, Presenter or anywhere else). Even if you converted each line into a "proper" graphical object (Ellipse, Circle,...) which knew how to paint itself, you'd still have needed tests or a dictionary to convert the lines into objects in the first place. In such a case it would probably not be worth creating all the objects unless they had something else important to do besides just painting themselves. In intermediate cases it might be worth considering double-dispatch. The rendering object runs down a collection of model objects aCollection do: [:each | each renderWith: self on: aCanvas]. and the model objects each implement #renderWith:on by double-dispatching back to the renderer. A GearChain's implementation would do: aRenderer renderGearChain: self on: aCanvas. whereas a Bolt's implementation of the same method would do: aRenderer renderBolt: self on: aCanvas. and so on... -- chris |
Thank you all for the help.
Now I see light at the end of the tunnel. I study them at the weekend, and try out. Thanks again, Janos |
In reply to this post by Chris Uppal-3
Chris,
> [just a couple of thoughts] > > [re: separating the drawing from the medium] > >>Doing so allows you to test the code without running into >>the problems that result from having troubles on presenter startup. > > > Nah, can't be bothered with all that. Just splat it in the window and debug it > if it breaks... Ok, but that leaves you with a view (arguably the stickiest creatures with respect to STB versioning), and starting from scratch when it's time to render on a printer. Split it, and you fix both problems, and the view is trival to write in terms of the renderer. >>All of this was illustrated very well in the early MFC tutorials. Yes, >>Bill is giving Microsoft credit for doing something right - mark your >>calendars :) However, the most recent time I looked at the MFC >>tutorial (which has been a while), it went on an on about "smooth >>scrolling" and did not appear to address optimization that I could find. > > Possibly because they feel that for simple graphics on a modern machine, > there's little or no need for optimisation. I suspect they do, and I submit that this is part of their trouble. >>Also, have you noticed the little pieces of "left over selection" that >>sometimes appear in Microsoft lists and trees? The screen scarring that >>occurs with the animated menu effects? That stuff is a symptom of >>incorrect drawing. > > > Which in turn is probably caused by someone trying to optimise the painting, > and getting it /almost/ right... But it's nowhere close to right, at least not conceptually. I fear that they see the selection as being tied to the mouse event, and don't realize the need to handle other cases. Writing Solid Code is dated now, but it describes a process involving experienced programmers roughing in design (reasonable) and turning it over to less experienced programmers to fill in functionality (also reasonable). However, it will work only if there is adequate supervision and mentoring, and that would be the first thing to get cut. Go back even farther (win3.1 days) as described in Barbarians Lead by Bill Gates (or whatever it's called), and you will read about programmers having two machines, one of which was slow - by mandate. Why pay somebody lots of money and then hobble them with a slow machine? Because that's what the end users had. If they still thought like that, they'd do more testing under load (if only by accident), and they would see the redraw problems. Have a good one, Bill -- Wilhelm K. Schwab, Ph.D. [hidden email] |
Free forum by Nabble | Edit this page |