JHotDraw Architecture Explained: Tips for Extending the FrameworkJHotDraw is a Java GUI framework for structured drawing editors. It provides a robust set of reusable components for building diagramming applications (UML editors, graph designers, flowchart tools, etc.). This article explains JHotDraw’s core architecture, key design patterns it uses, and practical tips for extending and customizing the framework to build maintainable, high-performance drawing applications.
Overview and design goals
JHotDraw was designed with the following goals in mind:
- Reusability: provide modular components for common editor tasks (tools, figures, handles, etc.).
- Extensibility: allow developers to add new figure types, tools, and behaviors without modifying core code.
- Separation of concerns: maintain clear boundaries between model, view, and interaction logic.
- Consistency: offer consistent user interactions across different editors.
At its core, JHotDraw separates the drawing model (Figures) from the view (DrawingView) and user interaction (Tool). This separation makes the framework flexible and testable.
Core components
Below are the main building blocks of JHotDraw and how they interact.
-
Figure
- Represents a drawable element (rectangle, line, text, etc.).
- Encapsulates geometry, style (stroke, fill), and behavior for drawing and interaction.
- Exposes methods for drawing, moving, resizing, connecting, and cloning.
- Often implements an observer pattern to notify listeners when changed.
-
Drawing
- A collection or container of Figures.
- Manages figure ordering, adding/removing figures, and drawing invalidation.
- Responsible for serialization/deserialization when saving or loading drawings.
-
DrawingView
- The visual presentation of a Drawing.
- Handles coordinate transforms (zoom, pan), clipping, and double-buffered painting.
- Maintains selection state and delegates input to the active Tool.
-
Tool
- Handles user interaction and event interpretation (mouse, keyboard).
- Implements behaviors like selection, creation, connection, and drawing.
- Tools often create and manipulate Figures via commands and the undo/redo system.
-
Handles
- Small UI affordances attached to Figures for resize/rotate/connection.
- Encapsulate mouse interaction for figure manipulation.
- Are created per-Figure based on its capabilities.
-
UndoableEdit / Command
- JHotDraw integrates with Swing’s UndoableEdit to provide undo/redo.
- Changes to figures or drawings should be wrapped in undoable edits.
-
FigureAttributes / Styles
- Visual properties (stroke, color, font) are separated so styles can be applied consistently.
- Supports attribute maps or typed properties per figure.
Important design patterns used
JHotDraw is a practical example of several object-oriented patterns:
-
Model–View–Controller (MVC)
- Figures and Drawing are the Model; DrawingView is the View; Tools/Handles act as Controllers.
-
Strategy
- Tools and Handles encapsulate algorithms for interaction; you can swap strategies to change behavior.
-
Visitor
- For operations that traverse figure structures (e.g., export, hit-testing optimization).
-
Composite
- CompoundFigure composes multiple Figures and delegates drawing and interaction.
-
Prototype / Cloning
- Figures support cloning for copy/paste and template-based creation.
-
Observer
- Figures notify listeners about changes so views can repaint or update handles.
Event flow and interaction lifecycle
- User input (mouse/keyboard) arrives at the DrawingView.
- DrawingView forwards events to the active Tool.
- Tool interprets events, possibly creating/altering Figures or selecting them.
- Modifications are performed on the Drawing (model) and wrapped in UndoableEdits.
- Figures fire change events; DrawingView revalidates and repaints affected regions.
- Handles and selection feedback are updated accordingly.
Understanding this lifecycle is critical when extending behavior—always modify the model through the appropriate APIs and emit correct change events to keep views and undo stacks consistent.
Tips for extending JHotDraw
-
Adding a new Figure
- Subclass AbstractFigure (or an existing simple Figure).
- Implement paintFigure(Graphics2D), basic geometry methods (contains, getBounds), and handle creation.
- Provide attribute keys for style properties and use AttributeKeys for typed attributes.
- Ensure changes trigger figureChange/figureChanged notifications.
- Implement cloning correctly for copy/paste (deep clone child figures if composite).
-
Creating custom Tools
- Extend AbstractTool or an existing concrete tool (CreationTool, SelectionTool).
- Keep tools stateless where possible; store transient interaction state in the tool instance only.
- Use Commands/UndoableEdits to make modifications undoable.
- Delegate repetitive tasks (hit testing, handle creation) to helpers.
-
Custom handles and feedback
- Create handles that implement the Handle interface to provide custom resizing, rotation, or connection behaviors.
- Use lightweight feedback (rubberbanding, ghost figures) during drag operations to avoid expensive repaints.
- Update cursor and appearance to make affordances clear.
-
Performance considerations
- Use region-based repainting: call DrawingView.invalidate(Rectangle) for the minimal area changed.
- Reduce object allocation in tight interaction loops (reuse Point2D/Rectangle objects where safe).
- Implement coarse-grained invalidation for batch operations (suspend events while performing bulk changes).
- Consider spatial indexing for large drawings (quad-tree or R-tree) to accelerate hit-testing and repaint decisions.
-
Serialization and persistence
- Implement Storable/DOM/Stax helpers for robust XML serialization if you need cross-version compatibility.
- Version your serialized format; include a document version attribute and migration code.
- Avoid serializing UI state (selection, zoom) unless explicitly desired.
-
Integrating with Swing and modern Java
- Use SwingWorker for long-running tasks (export, layout calculations) to keep UI responsive.
- Keep Swing threading rules: modify model and component state on the Event Dispatch Thread (EDT) or use invokeLater/invokeAndWait appropriately.
- Consider modularizing with JPMS or separate Maven modules for custom figures and tools.
-
Undo/Redo best practices
- Wrap composite changes in a CompoundEdit to expose them as a single undoable action.
- Name edits with user-friendly messages for better UX (edit.addEdit(new AbstractUndoableEdit() { public String getPresentationName() { return “Resize 3 figures”; }})).
- Ensure redo/undo restore both model and transient view state (selection).
-
Testing strategies
- Unit-test model logic (figure geometry, connection rules) separately from UI code.
- Use headless tests for layout and persistence.
- For integration tests, use robot-based UI testing sparingly; prefer mocking Tools and Views where possible.
Example: adding a rotatable image figure (brief)
- Subclass ImageFigure (or AbstractFigure).
- Store an AffineTransform for rotation and scaling.
- Override paintFigure to apply the transform and draw the image.
- Provide a RotationHandle that adjusts the transform and fires figureChanged() during drags.
- Wrap rotation updates in an UndoableEdit to support undo.
Common pitfalls and how to avoid them
-
Forgetting to fire change events
- Result: view not updated or inconsistent UI. Always call willChange/changed methods or the figure’s change notification helpers.
-
Doing heavy work on the EDT
- Use background threads for computations but marshal model updates back to the EDT.
-
Not using UndoableEdits
- Direct changes make undo/redo unreliable. Always wrap user-initiated changes.
-
Breaking encapsulation by manipulating view-only state in model classes
- Keep persistent model separate from view/interaction artifacts.
When to extend vs. when to replace
Extend when you need a few new figures or tools and want to keep compatibility with existing behaviors. Replace when you require a drastically different interaction model or rendering pipeline (for example, WebGL-based rendering or collaborative live-editing with OT/CRDTs). JHotDraw is optimized for single-user, Swing-based editors; introducing real-time multi-user collaboration implies rethinking core assumptions.
Final notes
JHotDraw’s architecture emphasizes small, composable classes and clear separation of responsibilities. When extending it:
- Prefer composition over inheritance where helpful (wrap behaviors).
- Keep changes localized and adhere to existing patterns for notifications and undo.
- Profile and optimize only after measuring; premature optimization can complicate design.
With careful use of Figures, Tools, Handles, and the undo framework, JHotDraw remains a powerful foundation for building custom drawing and diagram editors in Java.
Leave a Reply