Although many GEF applicationsProgramConnections are used, but some applications do not need to use connections to express the relationship. The project we are currently working on is an example. In such applications, the relationship between model objects is mainly expressed by the inclusion of graphs, so most of them are one-to-many relationships.
Figure 1 No connected GEF application
First, let's briefly describe this project. This project requires a graphical model editor. The main function is to add or delete nodes freely in a table with three rows and n columns, nodes can be dragged between different cells, and adjacent nodes can be merged, table columns can be increased or decreased, dragged, and so on. Because the tables provided by SWT/jface are difficult to implement these functions, we chose to use GEF for development. Currently, the results are quite good (SEE ), here we will briefly introduce some issues related to graphics and layout in the implementation process.
Before you start, you must first consider the construction of the model. Because draw2d only provides limited layout, such as toolbarlayout, flowlayout, and xylayout, and there is no gridlayout, the entire table cannot be used as an editpart, instead, we should regard each column as an editpart (because there are more operations on the column than the row, so we do not use the row as an editpart) to drag the column. In addition, we can see from the requirement that each node is included in a column, but after careful research, we will find that the node is not directly included in the column, instead, there is a cell object as a bridge in the middle, that is, each column contains three fixed cells, each cell can contain any node. After the above analysis, our model, editpart, and figure should have been initially formed, as shown in the following table:
| |
Model |
editpart |
figure |
| canvas |
digoal |
diagrampart |
freeformlayer |
| column |
column |
columnpart |
columnfigure |
| cell |
cell |
cellpart |
cellfigure |
| node |
node |
nodepart |
nodefigure |
The table contains relations from top to bottom, that is, one-to-many relationships. These relations are briefly displayed:
Figure 2 graphic inclusion Diagram
Let's start with the canvas. On the canvas, a column is displayed as a vertical (larger than width) rectangle. Each column has a header to display the column name. All columns are arranged horizontally on the canvas. Therefore, the canvas should use one of toolbarlayout or flowlayout. These two layout have many similarities, especially when they are arranged in the specified direction, the main difference is: when there are too many images to accommodate, toolbarlayout sacrifices some graphics to keep one row (column), while flowlayout allows line feed (column) display.
For our canvas, toolbarlayout should be used as the layout manager, because its subgraph columnfigure should not contain line breaks. The following figure defines the canvas image.Code:
Figure F =NewFreeformlayer (); toolbarlayout Layout=NewToolbarlayout (); layout. setvertical (False); Layout. setspacing (5); Layout. setstretchminoraxis (True); F. setlayoutmanager (layout); F. setborder (NewMarginborder (5 ));
Setvertical (false) specifies horizontal arrangement of subgraphs. setspacing (5) indicates that the distance between the subgraphs is 5 pixels long. setstretchminoraxis (true) indicates that the height of each subgraph is consistent.
Columnfigure is a little more complex, because it has a header area, and its three subgraphs (cellfigure) must be combined to fill the lower area, and adapt to its height changes. At first, I used the label provided by draw2d to implement the column header. However, you cannot set its height because the label class overwrites the getpreferedsize () method of figure, so that its height is only related to the text in it. The solution is to construct a headerfigure to maintain a label. When setting the height of the column header, the actual height of the headerfigure is set. Alternatively, the headerfiger can inherit the label and overwrite the getpreferedsize () or. I use the former in projects.
It took me some time to solve the second problem. At first I manually set the cellfigure height to 1/3 of the lower area of columnfigure IN THE cellpart refreshsponals () method, in addition, the impact of spacing needs to be considered. Later, the custom layout method successfully solved this problem. I asked columnfigure to use the custom columnlayout. This layout inherits from toolbarlayout, but covers the layout () method, the content is as follows:
Class Columnlayout Extends Toolbarlayout { Public Void Layout (ifigure parent) {ifigure namefigure = (Ifigure) parent. getchildren (). Get (0 ); Ifigure childrenfigure = (Ifigure) parent. getchildren (). Get (1 ); Rectangle clientarea = Parent. getclientarea (); namefigure. setbounds ( New Rectangle (clientarea. X, clientarea. Y, clientarea. Width, 30 ); Childrenfigure. setbounds ( New Rectangle (clientarea. X, namefigure. getbounds (). height + clientarea. Y, clientarea. Width, clientarea. Height- Namefigure. getbounds (). Height ));}}
That is to say, in layout, the height of the control column header and the lower part is 30 and the remaining height respectively. But this is not complete yet. In order to make the cells correctly positioned in the table column, we also need to specify the layout manager of the lower column graph (childrenfigure), because the cells are actually placed in this graph. As mentioned above, draw2d does not provide a layout manager like filllayout in SWT, so we need to customize another layout, for now, I name it filllayout (with the same name as SWT's filllayout). I still need to overwrite the layout method, as shown below (because transposer is used, horizontal and vertical can be processed in a unified manner, this transposer only works when horizontal ):
Public Void Layout (ifigure parent) {list children = Parent. getchildren (); Int Numchildren = Children. Size (); rectangle clientarea = Transposer. t (parent. getclientarea ()); Int X =Clientarea. X; Int Y = Clientarea. Y; For ( Int I = 0; I <numchildren; I ++ ) {Ifigure child = (Ifigure) children. Get (I); rectangle newbounds = New Rectangle (X, Y, clientarea. Width,-1 ); Int Divided = (clientarea. Height-(numchildren-1) * spacing ))/ Numchildren; If (I = numchildren-1 ) Divided = Clientarea. Height-(divided + spacing) * (numchildren-1 ); Newbounds. Height = Divided; child. setbounds (transposer. t (newbounds); y + = Newbounds. height + Spacing ;}}
The preceding statements distribute the height (width) of the parent image evenly to each subgraph. If the parent graph is in the last position, let it occupy all the remaining space (to prevent gaps from being left in case of Division ). After filllayout is completed, you only need to use childrenfigure as the layout manager. Below are most of the code of columnfigure, And the headerfigure and childrenfigure as internal classes:
Private Headerfigure name = New Headerfigure (); Private Childrenfigure = New Childrenfigure (); Public Columnfigure () {toolbarlayout Layout = New Columnlayout (); layout. setvertical ( True ); Layout. setstretchminoraxis ( True ); Setlayoutmanager (layout); setborder ( New Lineborder (); setbackgroundcolor (color); setopaque ( True ); Add (name); add (childrenfigure); setpreferredsize ( 100,-1 );} Class Childrenfigure Extends Figure { Public Childrenfigure () {toolbarlayout Layout = New Filllayout (); layout. setminoralignment (toolbarlayout. align_center); layout. setstretchminoraxis ( True ); Layout. setvertical ( True ); Layout. setspacing ( 5 ); Setlayoutmanager (layout );}} Class Headerfigure Extends Figure { Private String text; Private Label label; Public Headerfigure (){ This . Label = New Label (); This . Add (Label); setopaque ( True );} Public String gettext (){ Return This . Label. gettext ();} Public Rectangle gettextbounds (){ Return This . Label. gettextbounds ();} Public Void Settext (string text ){ This . Text = Text; This . Label. settext (text ); This . Repaint ();} Public Void Setbounds (rectangle rect ){ Super . Setbounds (rect ); This . Label. setbounds (rect );}}
The Cell Layout Manager also uses filllayout, because when you add the first node to the cell, the node must be filled with cells. When there are two nodes in the cell, each node occupies 1/2 of the height, and so on. The following table summarizes the layout management of each graph. As can be seen from tables, only those images that contain child images need the layout manager. The reason is obvious: the layout manager cares about and manages the "child" graphics. Keep this in mind.
| |
layout manager |
direct subgraphs |
| canvas |
toolbarlayout |
column |
| column |
columnlayout |
column header and column bottom |
| -column header |
none |
none |
| -lower column |
filllayout |
cell |
| cell |
filllayout |
node |
| node |
none |
none |
NOTE: If flowlayouteditpolicy or subclass is installed on the editpart of a graph when toolbarlayout or subclass is used as the layout manager, you may get a classcastexception. For example, in cellfigure, The editpart is cellpart, and celllayouteditpolicy is installed on it as a subclass of flowlayouteditpolicy. This exception occurs because the layout of the graph is forcibly converted to flowlayout in the ishorizontal () method of flowlayouteditpolicy, and toolbarlayout is used. I think this is an oversight of GEF, because the author once said that flowlayout can be applied to toolbarlayout. Fortunately, the solution is not complex: overwrite the ishorizontal () method in your editpolicy. In this method, determine whether layout is toolbarlayout or flowlayout, return the appropriate Boolean value based on the result.
Finally, there is another problem with our canvas. We hope that after the table columns are increased to a certain extent, the canvas can be expanded to the right. As mentioned above, the canvas uses freeformlayer as the image. To achieve this goal, you must also set the rooteditpart in the editor to scalablerooteditpart. Note that it is not a scalablefreeformrooteditpart, which is often used in applications that require canvas extensions in all directions. The usage of various rooteditpart will be introduced in subsequent posts.
The preceding examples show how to use toolbarlayout in GEF and customize a simple layout manager. We should follow the principle of constructing a graph, that is, let the layout manager decide the position and size of each sub-graph as much as possible, so as to avoid a lot of trouble. Of course, there are also exceptions. For example, in the layout manager that only cares about the position of the child image, you must specify the size for each child image. Otherwise, the image will not be visible because the size is too small, this is also a very easy place for developers to neglect.