| Patrice Sechere...'s profilePatrice SecheressePhotosBlogLists | Help |
Patrice SecheressePatrice Secheresse - IT Experience March 11 Drag and Drop - TransferableHere I will go into the Transferable implementation to be able to transfer a DefaultMutableTreeNode and its content, including the children. Custom DataFlavorA Transferable bundle the data to transfer and can restitute it depending on the requested DataFlavor. The Dnd support includes few basic classes to transfer a string, a file list or an image. If you want to transfer your own object type, there are some MIME types you can use to create your own DataFlavor with the corresponding DataFlavor constructor.
Transferable implementationThe implementation including a string flavor is: 1: package dnd; 2: 3: import java.awt.datatransfer.DataFlavor; 4: import java.awt.datatransfer.Transferable; 5: import java.awt.datatransfer.UnsupportedFlavorException; 6: import java.io.IOException; 7: import javax.swing.tree.DefaultMutableTreeNode; 8: 9: /** 10: * A Transferable Mutable Tree node. 11: * <p>This Transferable bundle a DefaultMutableTreeNode to use into a JTree Drag and Drop 12: * operation.</p> 13: * 14: */ 15: public class TransferableNode implements Transferable 16: { 17: 18: public final static DataFlavor FLAVOR = 19: new DataFlavor(DefaultMutableTreeNode.class, "Tree node"); 20: 21: private static final DataFlavor flavors[] = 22: { 23: FLAVOR, 24: DataFlavor.stringFlavor 25: }; 26: 27: private final DefaultMutableTreeNode treeNode; 28: 29: public TransferableNode(DefaultMutableTreeNode treeNode) 30: {31: this.treeNode = treeNode; 32: } 33: 34: public DataFlavor[] getTransferDataFlavors() 35: {36: return flavors; 37: } 38: 39: public boolean isDataFlavorSupported(DataFlavor flavor) 40: {41: return flavor.equals(FLAVOR) || flavor.equals(DataFlavor.stringFlavor); 42: } 43: 44: public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException 45: {46: if (flavor.equals(FLAVOR)) 47: {48: return treeNode; 49: } else if (flavor.equals(DataFlavor.stringFlavor)) 50: {51: return treeNode.getUserObject().toString(); 52: } else 53: {54: throw new UnsupportedFlavorException(flavor); 55: } 56: } 57: }public static DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + In this case, the DnD will be effective only inside the same JVM. The rest of the code implements the Transferable interface. The creator line 21 takes a node and encapsulate it into the instance. The line 34 implements the Transferable method getTransferDataFlavors that returns the flavors that our class can transfer. For example, I added the stringFlavor and will return a string value of the node's user object when a DnD accepts only the text value. The list must start by the more complete representation. The line 39 is a shortcut to check that a requested data flavor is available. An generic implementation would look up into the array. Finally, starting line 44, the method returns the node value. To support the text, we have to check the flavor and return the toString value of the user object. The TransferHandlerTo close this example, we need a TransferHandler that use can use the TransferableNode. The following code can transfer our DefaultMutableTreeNode from any JTree, even in a different JVM that has this TreeNodeTransferHandler attached. Note that the method getSourceActions returns only MOVE so the copy is not allowed and the method exportDone is here to clean up after the transfer. package dnd; import java.awt.datatransfer.Transferable; /** @Override @Override @Override JTree.DropLocation dropLocation = return dropLocation.getPath() != null; @Override JTree tree = (JTree) support.getComponent(); int childIndex = dropLocation.getChildIndex(); DefaultMutableTreeNode newNode = TreePath newPath = path.pathByAddingChild(newNode); return true; @Override } ConclusionDespite being a the simple concept, the drag and drop, especially with rich components like trees, can be challenging when implemented in real world applications. Even with the Java 6 improvements, the complexity of the underlying plumbing requires a lot of knowledge. March 09 Drag and Drop - copy/move to the same JTreeBack again to the TransferHandler discovery, one method that we can override is getSourceActions(JComponent c). This method is described with this JavaDoc comment:
The default implementation of the getSourceActions checks by introspection if the TransferHandler can access a property containing the transferable data. If yes, it returns COPY otherwise it returns NONE to disallow the drop from the drag source. Now, I need to create a component based on aJTree where I can move the nodes to reorganize the hierarchy. For example, imagine a leaf that you can select and move to the root. It is easy to implements the TransferHandler to manage to drag and drop from others components, including other JTree. Unfortunately, the only problem is when you want to move from the same JTree, it does not work. When the source is attached to your own TransferHandler, you need to advert the actions that you support for the drag. So, an easy fix to overcome the problem is to override the getSourceAction by returning MOVE. This means you allow the source node to be deleted from its origin and added to the drop destination. If you want to allow a node to be cloned and attached to another part of the tree, you can return COPY. For example, to allow the source to be copied or moved:
The values set in this method will be used by the export with the support.getSourceDropActions(). Drag and Drop with Java 6 - the basicsSwing Drag and DropThe Drag and Drop aka Dnd has been improved since Java 5 and Java 6. It is old story since the Shannon Hickey blog but as I need to play with it for my current project, it is time to investigate the matter. Today, it is quite simple to enable the Dnd for basic text component, simply use the However, when it comes to more complex components like To start, I will create the simplest code that allows me to drag a text from one tree to another. The core: the TransferHandler classThe drag exports the data from the source and the drop imports the data into the target through the TransferHandler class. This class contains all the code to do the most of the job and should be extended to set up your own Dnd. First, create your class that extends TransferHandler and attach an instance to your component, for example, to transfer from a tree to another tree:
The exportThis part is all about the drag source. The TransferHandler instance has to get your data from your source component. You must bundle it into a class that implements So, the method to override for our export implementation in the transfer handler is: This creates a Transferable that encapsulates a string version of the drag source component, as defined be the JavaDoc : "Creates a Transferable to use as the source for a data transfer. Returns the representation of the data to be transferred". The importThis part is all about the drop component. The TransferHandler instance must know if the source can be imported into the drop component. To do so, we have to override the canImport method "with a return value of true indicating that the transfer represented by the given TransferSupport (which contains all of the details of the transfer) is acceptable". For now, we accept only the string data so the code is: The TransferSupport is a key class that provides all you need to drop the data. Here, we simply ask if the stringFlavor is accepted. The last bit of code takes the data and insert it into the tree. To implement it, we override the method importData(TransferSupport support). This method causes a transfer to occur from a drag and drop operation. The Transferable to be imported and the component to transfer to are contained within the TransferSupport. First, we obtain the data from the support: Second, we need to know were the drop occurs. A special class exists to help us: JTree.DropLocation. Its getPath() and getChildIndex() methods returns were we should insert the new element. The rest of the code is the usual code for inserting a node:
try TreePath path = dropLocation.getPath(); // create the new node and insert it ConclusionDespite covering the minimum, this little article takes certainly more than 2 minutes to read and the code is too lengthy to be published here. This shows that the topic is not so simple and more reading is required. While I was finishing this post, I found that a similar article exists in the Java Tech Tips that could have saved me to do all the research and this post. October 23 Improving the NetBeans unit test supportMy current project includes over 1000 unit tests and hundreds of GUI tests. To run the full test suites, it takes more than 25 minutes. NetBeans shows some weaknesses in that area:
Thankfully, the NetBeans team has accepted to improve the support in the next version 7. If you found that this list is incomplete or you have a great idea, you can add a comment to this issue. August 25 BeansBinding: validation errorTo continue the previous blog, I will add some explanations about the validator. For example, I want to validate the age to ensure that it is between 0 and 150. All you need to do is create a class that extends Validator and implements the method validate(). Here the class AgeValidator: 1: public class AgeValidator extends Validator<Integer> { 2: 3: @Override4: public Result validate(Integer value) { 5: if (value < 0 || value > 150) { 6: return new Result(1, "The age must be between 0 and 150. The value " + value + " is incorrect."); 7: }8: return null; 9: } 10: }The method returns a null value if the value is valid. If an error occurs, you create a result object with a code and/or a description. The code can be useful when you internationalise the application. It could be any object or null. To add the validator, you just have to set it in the binding: binding.setValidator(new AgeValidator()); So, if you add these lines at the end of our previous run method:
The result will be: Failure VALIDATION_FAILED: org.jdesktop.beansbinding.Validator$Result [errorCode=1, description=The age must be between 0 and 150. The value 151 is incorrect.] You can notice that the type of error is now VALIDATION_FAILED instead of CONVERSION_FAILED. The validation occurs after the conversion. The idea is to work with the source object type to keep the validation independent of the UI. However, this lead to 2 different systems:
You have to check the failure and display the error depending of the type. Using an unified way to send a failure would have been simpler. The full code of the main class is: 1: public class Main { 2: 3: Person person; 4: Text text; 5: 6: public Main() { 7: person = new Person("John", 21); 8: text = new Text(); 9: } 10: 11: /** 12: * @param args the command line arguments 13: */ 14: public static void main(String[] args) { 15: Main test = new Main(); 16: test.run(); 17: 18: } 19: 20: private void run() { 21: Property ageProperty = BeanProperty.create("age"); 22: Property textProperty = BeanProperty.create("text"); 23: // bind the persone age to a text component 24: Binding binding = Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, 25: person, ageProperty, text, textProperty);26: binding.setValidator(new AgeValidator()); 27: binding.addBindingListener(new AbstractBindingListener() { 28: 29: @Override30: public void syncFailed(Binding binding, SyncFailure failure) { 31: System.err.println("Failure " + failure); 32: } 33: }); 34: binding.bind(); 35: 36: text.setText("22"); // print value 37: text.setText("22 and half"); // incorrect value, print failure 38: text.setText("151"); //incorrect value, print failure 39: text.setText("-1"); //incorrect value, print failure 40: } |
|
||||||||||
|
|