views:

223

answers:

6

I'm in the early stages of developing a C++ multi platform (mobile) application which has a graphical user interface. I'm trying to find a good way of abstracting the actual UI/windowing implementation. Here is basically what I have tried so far:

I created an interface hierarchy

Screen
 +Canvas
 +Form
   +Dialog
Control
 +EditBox
 +CheckBox
 +...

and I also have a class, Application, which basically uses all of these interfaces to implement the UI logic. The Application class also provides abstract factory methods which it uses to create UI class instances.

These interfaces all have implementations. For example, for Windows Mobile, Win32Form implements Form, Win32Canvas implements Canvas and Win32Dialog implements Dialog. As you can see, the problem is that the implementations loses the hierarchy, i.e. Win32Dialog doesn't extend Win32Form.

This becomes a problem in, for example, the method Form::addControl(Control &ctrl). In the Windows Mobile version of the application, I know the ctrl parameter is one of the Win32... control implementations. But since the hierarchy is lost, there is no way of knowing if it's a Win32EditBox or a Win32CheckBox, which I really need to know in order to perform any platform specific operations on the control.

What I am looking for is some design pattern on how to solve this problem. Note that there is no requirement to retain this interface hierarchy solution, that is just my current approach. I'll take any approach that solves the problem of letting the UI logic be separate from the several different UI implementations.

Please don't tell me to use this or that UI library; I like coding stuff from scratch and I'm in this for the learning experience... ;-)

+1  A: 

I've spent years doing mobile phone development.

My recommendation is that you do not fall into the 'make an abstraction' trap!

Such frameworks are more code (and tiresome hell, not fun!) than just implementing the application on each platform.

Sorry; you're looking for fun and experience, and I'd say there are funner places to get it.

Will
I also have years in mobile phone development and most of the time I have made specific implementation of the UI logic on each target platform. Which is exactly why I would like to get this abstraction thing working :-).
vonolsson
Better luck than everybody else has had then :) Make your hierarchy, and implement containment as another poster.
Will
-1: This doesn't seem to be a helpful answer
quamrana
+1  A: 

You can use containment. For example the class that implements Dialog would contain a Win32Dialog as a property, and the class that implements Form would contain a Win32Form; all the accesses to the Dialog or Form members would delegate to accesses to the contained classes. This way, you can still make Dialog inherit from Form, if this is what makes sense for your project.

Having said that, I see somethinf strange in your interface design. If I understood correctly (I may have not, then please correct me), Dialog inherits from Form, and Form inherits from Screen. Ask yourself this to validate this design: Is a Dialog also a Form? Is it also a Screen? Inheritance should be used only when a is-a relationship makes sense.

Konamiman
Ok, makes sense. Not sure how to apply this when targeting, for example, Symbian though, and I want my Dialog to contain a SymbianDialog instead.
vonolsson
+1  A: 

The GOF pattern that addresses this issue is the Bridge Pattern. Konamiman's answer explains it, you use delegation/containment to abstract one of the concerns.

iain
+2  A: 

Some inspiration from my side:

Don't inherit Form from a Screen. It's not "is-a" a realtionship.

 Screen
 Canvas
 GUIObject
    Form
       Dialog
    Control
       Button
       EditBox
       CheckBox
       ...
       Panel

More detailed description:

Screen

  • "has-a" canvas
  • provides display specific functions
  • don't be tempted to inherit from Canvas

Canvas

  • your abstracted drawing environment
  • target is screen canvas, form canvas, image, memory...

GUIObject

  • Wnd in some of hierarchies
  • usually provides some introspection capabilities
  • usually provides message processing interface

Form

  • in some toolkits actually called "dialog"
  • "has-a" canvas on which you can draw
  • "has-a" list of controls or a client area Panel
  • "has-a" title, scrollbars, etc.

Dialog

  • I like to call dialog standardized YesNo, OpenFile etc, basically simplified Form (no close button, no menu etc.)
  • may or may not be inherited from Form

Controls are hopefully self-explanatory (Panel is area with controls inside with optional scrollbars).

This should work. However how to exploit specific features of each platform without ruining genericity of this interface and/or ease of use is the difficult part. I usually try to separate all drawing from these basic classes and have some platform specific "Painter" that can take each basic object and draw it in platform specific way. Another solution is parallel hierarchy for each platform(I tend to avoid this one, but sometimes needed - for example if you need to wrap windows handles) or "capability bits" approach.

MaR
Many UI toolkits think of a 'screen' as being a container that the user interacts with - think more in terms of 'screenful'. Forms and dialogs are typically subclasses of screen objects, and the screen has the menu and hotkeys and such generic information.
Will
Well, exactly what Screen means is individual, I suppose, and I'm happy with my definition. However, the actual hierarchy and naming isn't the real problem.
vonolsson
A: 

First, the primary problem seems to be that there is no abstraction in your "abstraction" layer.

Implementing the same controls as are provided by the OS, and simply prefixing their names with "Win32" does not constitute abstraction.

If you're not going to actually simplify and generalize the structure, why bother writing an abstraction layer in the first place?

For this to actually be useful, you should write the classes that represent concepts you need. And then internally map them to the necessary Win32 code. Don't make a Form class or a Window class just because Win32 has one.

Second, if you need the precise types, I'd say ditch the OOP approach, and use templates.

Instead of the function

Form::addControl(Control &ctrl)

define

template <typename Ctrl_Type>
Form::addControl(Ctrl_Type &ctrl)

now the function knows the exact type of the control, and can perform whatever type-specific actions you want. (Of course, if it has to save the control into a list in the control, you lose the type information again. But the type information is propagated that much further, at least, which may help.

The collection could store something like boost::variant objects, even, which would allow you to retain some type information even there.

Otherwise, if you're going the OOP route, at least do it properly. If you're going to hide both EditBoxes and CheckBoxes behind a common IControl interface, then it should be because you don't need the exact type. The interface should be all you need. That's the point in interfaces.

If that contract isn't respected, if you're going to downcast to the actual types all the time, then your nice OOP hierarchy is flawed.

If you don't know what you're trying to achieve, it's going to be hard to succeed. What is the purpose of this Trying to write an "abstraction of the user interface exposed by Windows", you're achieving nothing, since that's what Windows already exposes. Write abstraction layers if the abstraction exposed by Windows doesn't suit your needs. If you want a different hierarchy, or no hierarchy at all, or a hierarchy without everything being a Form, or a Control, then writing an abstraction layer makes sense. If you just want a layer that is "the same as Win32, but with my naming convention", you're wasting your time.

So my advice: start with how you'd like your GUI layer to work. And then worry about how it should be mapped to the underlying Win32 primitives. Forget you ever heard of forms or dialog boxes or edit boxes, or even controls or windows. All those are Win32 concepts. Define the concepts that make sense in your application. There's no rule that every entity in the GUI has to derive from a common Control class or interface. There's no rule that there has to be a single "Window" or "Canvas" for everything to be placed on. This is the convention used by Windows. Write an API that'd make sense for you.

And then figure out how to implement it in terms of the Win32 primitives available.

jalf
Well, the hierarchy I show in my question doesn't show enough detail but the interfaces do represent the concepts I need. The Win32 implementation (or the iPhone/Symbian implementation for that matter) would, if I could solve the problem explained above, have simple methods containing a lot of code in order to implement the concept I need.
vonolsson
A: 

Interesting problem, so a couple of thoughts:

  • first: you could always use MI so that Win32Dialog inherits from both Win32Form and Dialog, since you then get a nice diamond you need virtual inheritance
  • second: try to avoid MI ;)

If you want to use specific controls, then you are in a Win32 specific method, right ? I hope you are not talking about up_casting :/

Then how comes that this Win32 specific control method has been handed a generic object, when it could have got a more precise type ?

You could perhaps apply the Law of Demeter here. Which works great with a Facade approach.

The idea is that you instantiate a Win32Canvas, and then ask the canvas to add a dialog (with some parameters), but you never actually manipulate the dialog yourself, this way the canvas knows its actual type and can execute actions specific to the platform.

If you want to control things a bit, have the canvas return an Id that you'll use to target this specific dialog in the future.

You can alleviate most of the common implementation if canvas inherit from a template (templated on the platform).

On the other hand: Facade actually means a bloated interface...

Matthieu M.
Hey, nothing wrong with up casting so long as it doesn't have to be a dynamic_cast ;-).
vonolsson
Actually, `dynamic_cast` it should be (at least in debug builds) :)
Matthieu M.