Question

Lets say I have a GUI application which tries to adhere to Model-View-Presenter (MVP) as best as possible. In this application I have a list box with items. One should be able to interact with these items via a context menu. But: Which entries in the context menu are shown depend on the state of the item. For example: I have a list box of data sources whose context menu shall show "connect" or "disconnect", depending on whether its already connected or not.

This is where I don't know how to correctly implement this with the MVP architecture.

How can I fill the context menu with state dependent items while the view does not know about the state and the presenter does not know about where to put a context menu?

My thoughts so far:

The view receives the framework event for opening the context menu. Now it must populate the menu but has no info on the state of the underlying model. It could just call a corresponding presenter method (the original framework event object would not be passed to the view, see assumptions). The presenter could query the model and get the state, but how does the info get to the view? If the view had a method like "show_context_menu" then how would the view know where to put the context menu. This information would only be available in the method handing the original context menu event and I don't think something like coordinates should be passed to the presenter. Or should it?

This is a general question and not framework or language dependent.

My Assumptions (Which might be wrong):

  • Presenter should be independent from GUI framework (QT in my case), so no GUI framework objects used in presenter.
  • View should not access model data directly (adhering to MVP)
Était-ce utile?

La solution

The presenter and the view are both part of the presentation layer. So, while the presenter doesn't know anything about the GUI framework, it does know about presentation logic, and presentation-related concepts. It just doesn't express them in GUI framework–specific way. E.g., you could have a method on it that returns actions available for an item, encoded as data, and have the view (or some view-aware helper function/class) convert those actions into GUI controls. Heck, you could even have a method on it that returns a different presenter for a subview (without knowing what the view actually is - it's just designed to have such a method, in order to support presentation logic as conceptualized at a high level).

Such methods and data structures are what forms the presenter abstraction as seen from the view side. When people hear "abstraction", their mind usually jumps to "abstract class", but here it's used in this more fundamental sense. Basically, think of it like this: if you're determined to avoid any mention of GUI framework–specific names, what data structures and functions you need to come up with to be able to express the essential features of the presentation logic when writing the presenter? Or perhaps: if you expect a lot of fiddling with the details of the view (color-coding, kinds of controls for certain actions, layout, ...), how can you express what these things are about in a way that's relatively resilient to these changes? E.g. a simple example of that is, instead of having an enum { Green, Yellow, Red }, you use { OK, Warning, Error }. The end product of this process is what constitutes your abstraction: the functions, data structures and interfaces at the view-facing boundary.

So, for the situation you're describing, a way to do it would be:

  1. The view receives the "open context menu" event for a list item
  2. The view (directly or through some mechanism) invokes a "get actions for [item]" method on the presenter. In this case, it's probably best if the view can obtain the return value of this method, rather than wait for the presenter to subsequently push the result into the view by some other mechanism (that's possible too, but likely more convoluted; e.g., you could pass a lambda as the second parameter and have the presenter call it).
  3. The presenter calls something that knows how to produce a list of actions (or something describing the actions) given the current item, then passes the result along (perhaps after embellishing it with some additional presentation-related data).
  4. The view converts these actions into controls (context menu items), or calls something that can do the conversion for it, or relies on some framework mechanism that can, say, instantiate a template, or whatever.
  5. The context menu is shown.

E.g., you could do this within a slot that's connected to the customContextMenuRequested signal.

But don't stop there. I said that this is "a way to do it" on purpose. Explore a couple of other options, perhaps even some that are a bit out there, all the while keeping YAGNI in mind. The goal now is not to predict some future change, but to attempt to get a fresh perspective on the problem.

For example, suppose that, since there are only a couple of possible actions (bear with me if that's not actually the case), your users ask you to replace the context menu with a set of small buttons (some enabled, some disabled), shown inline with the list item, so that they can save a click. Assuming that it's cheap to determine the available actions, in this case it makes sense to get them together with the items, when first populating the list box. Maybe that's a better abstraction. Perhaps that will let you do things like live update of available actions, in case the state of the selected item can be changed in a different view that's simultaneously visible.

OK, now come back to your current implementation that is based around a context menu. Perhaps it makes sense to do this anyway. Perhaps you can calculate all that you need up front, eliminating the need to call the presenter in this case. Nobody can tell you what's the best option here - you get to decide based on your understanding of the domain. You get to decide if this is something worth pursuing or if you should apply YAGNI. Another thing to take into account is which of the approaches you've considered offers least friction with the language and the framework, while retaining a good amount of design benefits that you're after - because, while you can, in principle, ignore the framework in code, there are practical concerns that mean that you can't really ignore all aspects of it (to various degrees).

Now, the problem is, the views tend to be very fiddly (they tend to be tweaked a lot, especially in the beginning), so it's hard to come up with these abstractions and have them be reasonably stable. Also, people tend to make presenters (and classes in general) too big, responsible for too many things, and thus they hardcode a lot of wrong assumptions into them; this leads to a lot of accidental internal coupling. But then parts of these presenters need to change in different ways, at different rates, for different reasons, and this accidental coupling makes changing things tedious. So one thing that helps is: think smaller, and distribute responsibilities. There doesn't have to be a single presenter - you can have a number of collaborating classes; adjust the pattern to suit your specific problem better.

Also, GUI frameworks and control libraries help in some ways (e.g., they might provide a databinding mechanism, eliminating some boilerplate), but they can also bring in some constraints (e.g., they might be geared more towards getting things up and running quickly for a certain set of scenarios, but can get in your way if you try to do something outside of that, or they might impose a certain style of programming). So you have to find a balance between designing for your needs and not working against the framework too much.

"Lets say I have a GUI application which tries to adhere to Model-View-Presenter (MVP) as best as possible."

I know that when you don't yet have a deep understanding of a concepts, it's helpful to follow a set of rules, but this is bit of a double-edged sword. There are several variations of the MVP pattern; I'm mentioning this to show you that people have been adjusting the pattern to better suit their specific needs or to work better with the language and the framework they use. So don't be afraid to introduce your own tweaks if you need to.

Autres conseils

I feel like you are overthinking this. Here is how I would approach this task:

  1. User calls your context menu. This is done in your view, which handles everything regarding presentation.

  2. Your view stores all the presentation-specific details (position of the menu to open), does presentation-specific actions (e.g. plays the animation to acknowledge user's input), and passes the request for data onto the presenter.

  3. Your presenter receives request for data from its view. It pokes the model for data.

  4. Model retrieves and serves the data.

  5. Presenter receives the data, formats it (e.g. adds underlines for shortcut letters, filters items, applies capitalization), and passes it onto the view.

  6. Your view receives the formatted data and displays it where it remembers the context menu was called from.

Basically, your view just has to expose an interface for things you would consider to fall under "buisness logic" of your UI to the presenter. Is position on the screen "business logic"? Is the color of your menus? Is your font?

Sometimes—absolutely, an this makes it necessary to expose these to your presenter and at some point your presenter will have to become aware of and tied to your GUI framework.

But in most cases, view should handle presentation on its own. There is no need for it to be this empty husk that doesn't do any logic at all aside from routing to the presenter. Remember—that was the entire point of having three separate classes: we do not want all this logic to be bundled together in a single object for no good reason.

One more thing I would add is to have a sub-window/menu/view opened immediately upon context menu being called. Then it can handle everything related to that menu and your main view just needs to handle its position relative to itself.

Licencié sous: CC-BY-SA avec attribution
scroll top