Underexplored VSX Nuggets Vol #37: How a DSL Diagram is rendered
This is an area I always have to remind myself of every time someone asks a question, so I thought I'd better write it up. Essentially there are four layers involved:
Starting in the middle and working outwards, the classes' responsibilities are as follows:
Diagram - This is the logical representation of you diagram's data. It is a generated class in your DSL project, created from the Diagram element in your DSL model. Consequently, it is a Store-based class and its data is therefore subject to the Store's transactional model. It acts as the root of the ShapeElement-derived object tree (the nodes and links) that provides the persistent store of your DSL's presentation model. This chiefly consists of the positions of shapes, their attachments to each other via links and nesting, expanded/collapsed state etc. You can look at the contents of your DSLs .diagram file to see the kind of data that is stored in this object tree.
The key responsibilities of the Diagram class are to manage the View Fixup process that creates ShapeElements to match domain model elements and to hold the mapping data for that process. Keeping the mapping data on the Diagram and out of individual shapes potentially allows shapes to be reused across different diagram types. Diagrams know very little about the rendering process.
DiagramClientView - This is the WinForms Control-derived class that actually renders the content of the Diagram object. It responds to mouse events, keyboard events and manages the math for the current zoom level and viewport bounds. It handles the tooltip and selected items list and supports drag/drop. It's a purely vanilla control and doesn't know that it is hosted inside Visual Studio. Its name comes from the fact that in Win32 terms it is managing the client area of the diagram surface.
DiagramView - This is the outermost layer of the control rendering stack. It's a WinForms Control that has the responsibility of providing the realization of the viewport data stored in its child, the DiagramClientView. Our implementation chooses to do this by using a couple of scroll bars. In theory, a different viewport manager could be added that used something else like a panning tool or some cool dynamic fisheye view. In practice we've never tried replacing this so it may or may not be practical.
The DiagramView also provides the Watermarking facility and expects to be sited (IObjectWithSite) in order to have access to a toolbox service.
This class is specialized as a VSDiagramView in order to hook the VS undo stack to the individual Diagram being rendered and to connect the Diagram's notion of selected items to VS's selection system.
DiagramDocView - This is part of the DSL Tools' implementation of the Doc/View pattern that Visual Studio imposes. It is generated in your DslPackage project and provides the bridge from the largely VS-agnostic diagram rendering into VS's world. In VS, a DocView's key responsibility is to provide the content that renders its underlying DocData object (which represents the content of the file or other store being edited). In this case, the immediate base class, DiagramDocView satisfies that by creating the VS-specific VSDiagramView control and connecting it to a Diagram instance in its override of the LoadView method. It also hooks the VS help system to the Diagram and its content.
If you need to use a custom VSDiagramView-derived class, you should override CreateDiagramView and don't forget to include the following (slightly unfortunate) code to set up the DiagramView:
public override VSDiagramView CreateDiagramView()
// Add a single designer to our collection
SomeDerivedVSDiagramView designer = new SomeDerivedVSDiagramView ();
designer.BackColor = System.Drawing.SystemColors.Window;
designer.Dock = System.Windows.Forms.DockStyle.Fill;
designer.DocView = this;
designer.MouseUpEvent += DesignerMouseUpEvent;
designer.DiagramClientView.ZoomChanged += DesignerZoomChanged;
designer.DiagramClientView.TakeFocusOnDragOver = true;
designer.Site = new VSDiagramViewSite(this);