JComponentTree @ JDJ

来源:互联网 发布:java 类似jsp v 编辑:程序博客网 时间:2024/04/29 16:50
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

  This month we put together a visual component, designed to render a tree structure in a scrollable window. JComponentTree can display a set of arbitrary components in a tree hierarchy. It can orient the tree in any of the cardinal directions (north, south, east or west); display the connections between nodes using straight or angled lines; and align each of the tree nodes to be left-, center- or right-justified. The JComponentTree stores its data in a DefaultTreeModel compatible with JTree and uses TreeNode objects that extend the DefaultMutableTreeNode class.

  Figure 1 shows an example JComponent hierarchy, fairly typical of the kind of structures you might want to display using the JComponentTree widget.

  Using the JFC TreeModel

  The JComponentTree widget is designed to take advantage of concepts developed by Sun Microsystems in the JFC JTree component. We work with the DefaultTreeModel, for example, so that migrating between views is possible. An application could implement a structure based on the DefaultTreeModel and easily switch between display components, permitting the JTree or the JComponentTree - or both - components to display the model.

  The TreeModel provides methods that manage (TreeModelListener) listeners, get the root node and permit access to, or statistics about, the nodes at each level in the hierarchy. However, the TreeModel interface doesn't provide methods that actually change values in the model, so we use the DefaultTreeModel as the basis for our own JComponentTree model, accessing available nodes through the TreeModel interface.

  My first attempt to keep the compatibility between JTree and JComponentTree optimized led to a few deep forays into the JTree source code. Starting with a pure implementation of the TreeNode interface wasn't enough since it doesn't allow you to change any values. For that you need the MutableTreeNode, which provides a method for setting a user object - in our case a Component to be displayed. It doesn't provide a way to get the object back, however. This is a bit of a mystery to me, and clearly a shortcoming of the interface design. Our attempt to make the tree model as generic and compatible to JTree as possible leads us finally to the DefaultMutableTreeNode class.

  Since we have to use the DefaultMutableTreeNode as the basis for our own tree structure, we'll add another layer, the ComponentTreeNode, to provide type safety, thus ensuring that the user object is always a component. By extending DefaultMutableTreeNode, we can also make sure that a JTree control could display the same basic structure with minimal effort.

  Listing 1 shows the source code for the ComponentTreeNode class. We extend DefaultMutableTreeNode and pass the component to be stored as the user object in the constructor, saving it by calling the superclass constructor. The getComponent method casts the user object into a Component when we ask for it.

  The ComponentTreeLayout Manager

  To keep the coupling to a minimum, we use a layout manager. Unfortunately, this implementation is slightly more coupled than you might normally expect since we need to draw the lines connecting nodes by explicitly using the paintComponent method in the parent container.

  The ComponentTreeLayout class performs a lot of work. Here's a quick list of its major responsibilities.

  Set/get values for alignment, linetype, tree direction, etc.

  Calculate minimumLayoutSize and preferredLayoutSize.

  Calculate component positions and lay out the tree.

  Draw the lines that connect each of the components.

  The more notable member variables are alignment, linetype and direction. The alignment value determines whether child nodes are aligned to the LEFT, CENTER or RIGHT of the parent. The linetype value determines whether lines are drawn directly between nodes in a STRAIGHT line or as SQUARE lines, forming right angles to the nodes. The direction value determines whether the tree is drawn with its leaves to the NORTH, SOUTH, EAST or WEST.

  Each of these values has an associated set and get method that follows the JavaBean standard (setValue/getValue, where Value is the actual name of the variable), and constant values are declared in the ComponentTreeConstants interface, which you can see in Listing 2. In addition to these, we have access to the TreeModel model value, which is exposed through the getModel and setModel methods, and the root node, through the setRoot and getRoot methods. Several constructor variants are available. Each requires a TreeModelListener so that we can notify the parent container of any changes to the model. Size Calculations

  The ComponentTreeLayout subclasses the AbstractLayout class, which you might remember from an earlier article I wrote, "Practical Layout Managers" (JDJ, Vol. 3, Issue 8). It provides default behavior for most layout manager calls and a good foundation to start from when you're working with them. As with any layout manager, the ComponentTreeLayout must calculate the minimum and preferred size for the container in which it's used.

  Listing 3 shows code for the preferredLayoutSize method. We take the inset values into account and consider the horizontal gap between each node. Figure 2 shows how we can determine the width of a given node and its immediate children. We simply add the child node widths and the hgap values together, and test to make sure the parent node is not wider. If it is, we always take the wider value.

  The main preferredLayoutSize method calls getPreferredSize with the root node, retrieved directly from the model, and getPreferredSize calls itself for each of the child nodes it finds along the way. This accumulates the height and width until we've traversed the entire tree and then returns the correct value at each level. The minimumLayoutSize calculation is almost identical, though we ask each node for its minimumSize instead.

  Positioning Components

  When the container calls doLayout, the layout manager calls the layoutContainer method. This method determines which orientation we're dealing with and provides a starting x and y value along with the root node to the layout method, which recursively repositions and resizes each component using the setBounds method.

  The layout method needs to determine the size of each node and its immediate children, but it can't call the getPreferredSize method without running into proportion problems. Instead, we have a near duplicate of getPreferredSize called getLayoutSize. Listing 4 shows the layoutContainer and layout methods, but leaves out getLayoutSize because it's almost identical to getPreferredSize in Listing 3.

  The layout method takes into account the orientation and node alignment before deciding where each node should be placed. We walk through the child nodes, calculate the x and y positions and recursively call the layout method to traverse the tree structure.

  To paint the lines between components, we have to call the drawLines method explicitly. Listing 5 shows the drawLines method, which recursively draws lines by calling getBounds on each component to determine where they're positioned. Painting always happens after the layout call, so this is perfectly safe.

  The JComponentTree Component

  The JComponentTree code, shown in Listing 6, provides an interface to a number of ComponentTreeLayout methods, allowing the model, orientation, linetype and alignment to be set and retrieved. Whenever one of these attributes is reset, the doLayout method is called, along with repaint, to refresh the JComponentTree view. Listing 6 shows only one of the available constructor variations, and skips some of the accessor methods and most of the methods required by the TreeModelListener interface. Each of the constructors calls the ComponentTreeLayout equivalent. The one in Listing 6 is the most extensive.

  The JComponentTree control also implements the TreeModelListener interface to monitor changes in the tree structure. If one of these events is fired, the layout is recalculated and redrawn. When field values are changed, we also fire the layout method and repaint the tree. The exception is setDirection, which also calls setSize to make sure the scrollable panel size

  is correct.

  The addNode method creates a ComponentTreeNode object and adds the component to the container. To make it possible to create children that refer to an added node, we return the ComponentTreeNode object and handle null parents as a request to set the root node. You'll want to pay attention to this since accidental null parent nodes won't throw an exception and you'll end up with unexpected results.

  When you download the code from our Web site, you'll find a test class called JComponentTreeTest. This class generates a random tree with variations in depth, maximum width and randomly selected components. In addition, it provides a set of buttons that lets you dynamically change the direction, linetype and node alignment so you can get a feel for what can be done.

  Summary

  You now have yet another control to add to your programming toolbox. JComponentTree offers an occasional alternative to JTree and provides some overlapping functionality, but it's typically used in situations that require displaying a tree structure in different ways. This widget lets you organize arbitrary components rather than using a renderer to display data, so it has a different overall purpose. Still, there's just enough commonality with JTree to allow for easy migration.

<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>