domingo, 3 de marzo de 2013

Let the context decide



Introduction 

All developers in some moment in their lives have faced (or will be facing) the need of create a graphic editor, an ER model editor, a flowchart editor, whatever... but a common problem is that every time we start a project like these, we start without a common way to implement that, there are patterns like MVC, MVP, MVVM and others M-something. These patterns are useful but in the most of cases we find a slight line in implementation where every looks like the same, separating responsibilities is sometimes quite tricky and the more user interaction is added, the more complicated is to support these changes in developments, usually because we must to modify many components in the implementation.  Well in this article we discuss a way (maybe not the best) to solve the user interaction problems.  In general is not only useful for graphic editors but these are good examples for this article.  I hope you like it.   

Background 

Let's suppose that we must to create a shapes editor, then we start creating a class model but the user interaction behavior for every shape is different, however, the device actions set performed by users are the same for every shape.  A user can click, move, release its mouse, and is the same for other devices, the device actions are finite, but ¿why is so difficult to understand the device inputs and give a meaning to these actions? Well the answer is that the meaning for some action depends on the 'context' it is raised. Let see an example.
A user press a mouse button, if there is no shape in the mouse position a selection task must start, but if there is a shape in that position we could think that user wants to select or drag that shape, we can code this in an lazy way, but, what if this action produces different meanings depending on the shape functionality for example, if we have a box with an expand-button? or maybe a shape with an internal “draggable” element?  We must to manage all these conditions in the editor but without a well-structured way to do that all becomes a mess. 
What I propose in the title of this article is not to manage the context for those actions directly, but let the context itself decide which must be the consequences for a specific action.  Working in that way, every time a new meaning is needed or appears for a specific context is easier to maintain the code, every time a new context appears in the development is easier to create and connect with the existing development.  The class diagram shown bellow display a general idea about the code posted for this article and it is organized to read from left to right and from top to bottom. 

The first class we see is the DashboardBase class which is a user control and its responsibility is to catch the control inputs for all devices (mouse, keyboard, pencil, whatever).  This class uses a Renderizer to print the view state in the control's client area Graphics. Additionally the dashboard references an InteractionManager which processes all InteractionEvent instances built by the dashboard control depending on the event raised.  The InteractionManager class uses the View class and its responsibilities are:
1. Manage the context for the view.
2. Adapt the diagram information (for example managing coordinates spaces) to the control depending on some properties like zoom, offset.

The Selection class is only a property used by the view to store the selected shapes and it is not important in order to show the idea. The Diagram class is the model we are creating or editing, this class contains for this example Layers and Shapes.  To keep it simple we only define a BoxShape and a LineShape.  When the view detects that an action have been performed, it analyzes whether exists a shape capable to solve the context, if a shape is found that shape must solve the context depending on its features.  In that way, if the shape is a BoxShape it can define the context depending on a drag area, a rotation glyph or a re-size corner, all these with different consequences. OK now we have the context, so, how it is used?  That's the wonderful part, every context share a common interface called IInteractionContext. This interface contains basically three methods.  
Begin: When the context is activated a transaction is started. 
ProcessEvent; This method filters all the user interactions that this context can manage, including events available to commit or rollback the transaction modifying the view for the specified event.  
Commit: This method defines in every context, the way all data must be applied to the diagram model or how must be modified the view (in the Selection case for example). 
As you can see, we define in the top classes implementing the IInteractionContext.  These classes decide for every context which is the meaning for all the interaction events available for the context.  We can observe the following contexts:  

ShapeResizeContext: Context used to re-size a BoxShape. 
SelectionMoveContext: Context used to move a shape in the Diagram. 
MoveLineVertexContext: Context used to move the start point and end point for a line. 
SelectRectangleContext:  Context used to select all shapes in a rectangle. 

Results


A single image about the code execution result at Shapes Test

   

No hay comentarios:

Publicar un comentario

Los comentarios son parte activa de este blog y no serán moderados. Siéntete cómodo al dar tu opinión. Esta es muy importante para nosotros.