I'm displaying a tree of custom objects, and I've got custom custom CellTreeEditor and CellTreeRenderer set.
Now what I really want is to always display all objects as in "edit mode". Right now I have the CellTreeRenderer.getTreeCellRendererComponent() and CellTreeEditor.getTreeCellEditorComponent() implemented almost identically. This kind of works, but I still have to click a node to focus it before I can do any editing.
Is there any more sensible way of doing this, perhaps like saying no renderer should never be used, defaulting to my CellTreeEditor?
******UPDATE****
To clearify: What I have is a tree looking like this (and yes, it also looks like crap, but that's beside the point):
Right now, I accomplish this by having a renderer and an editor that returns identical components from getTreeCell[Renderer|Editor]Component().
If I click on the down-arrow on the ComboBox supplied by the renderer, it will flicker slighty as it opens the dropdown, but then be interrupted and replaced by my editor component. This means I have to click it again to open the dropdown. This is the behaviour I want to avoid.
Expanding my comment: no, you don't want to have your editor shared across cells (nasty thingies start to happen) Instead, add a TreeCellListener which listens for changes in the lead (aka: focused) selection path and then explicitly start editing on that path
final JXTree tree = new JXTree();
tree.setEditable(true);
tree.expandAll();
TreeSelectionListener l = new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent e) {
final TreePath path = e.getNewLeadSelectionPath();
if (path != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
tree.startEditingAtPath(path);
}
});
}
}
};
tree.addTreeSelectionListener(l);
The trick to really make it work is the usual: wrap the custom reaction into an invokeLater to be sure the tree's internal update is complete
Related
Here's some example code, using Groovy's swingbuilder to create the code for the valueChanged event of a JList:
mainList.valueChanged = { event ->
if (event.isAdjusting) {
index = mainList.selectedIndex
otherList.clearSelection()
otherIndex = otherList.selectedIndex
} else {
mainListSelected = true
clearJList(otherList)
}
}
I have two JList's, and this function kind of controls which list is allowed to be selected via the mainListSelected variable. We also then have to change int eindex we want to use from the selection based on whether or not it's an index from mainList or otherList
I've read about event.isAdjusting, and it only fires twice like this on a mouse click event. With this knowledge, you would think I would just move everything out of there, but I need certain things to happen differently if the mouse is what causes the event as opposed to using arrows. However, with this code, using arrow key navigation prevents the index from ever changing.
I'd like to implement a ToolTip in Swing that has customised behaviour: the longer the user hovers over the component, the more detail should be shown in the tooltip (i.e., a few new lines are added after a few seconds of the user hovering over the component). I just need to check whether this is really doable with Swing without things getting too messy. My idea at the moment would probably be:
Extend ToolTipManager
Override mouseEntered to start a timer (maybe use javax.swing.Timer?). Call setToolTipText and createToolTip to refresh the tooltip and add new information at regular intervals
Override mouseExited to reset the timer
Probably use setDismissDelay to set the dismiss delay to something a lot longer (or Integer.MAX_VALUE)
Is such a thing feasible or is this not a good way to work with Swing (sorry, I'm pretty new to it)? Is there a better way of doing this?
[edit] Hmm, just remembered that ToolTipManager is a singleton with a constructor that only has package visibility, so it can't be extended.
[edit 2] I'm trying out a few solutions at the moment. One thing that I forgot to add is that I do need to know which component is being hovered over - which I guess means I'll need to be working with some sort of listener with a mouseEntered() method (or be able to access this information). And no other interactivity with the popup/tooltip is needed - it just needs to display information.
(This may seem a bit confusing so let me know if you need me to clarify let me know and I'll try to show you how I picture the code) I think your idea might work like if you extend it, and also make a private class that extends Threadand then in the run() method you do something like
while(true)
{
sleep(1);
timeElapsed++;
}
And in your class that extends ToolTipManager, create a field for that class that extends Thread and in the mouseEntered(MouseEvent e) instantiate the thing like:
extendsThreadClass = new ExtendsThreadClass();
extendsThreadClass.start();
and then in the mouseExited(MouseEvent e) method do
extendsThreadClass = null;
Then in that mouseEntered(MouseEvent e) method after starting the Thread then you can do what you want to do after the time thing like
if(timeElapsed > 3000)
{
//what you want to do here
}
Sorry it may be confusing, let me know if i can clear it up for you
I thought I should update this with the approach I took before I saw l1zZY's answer (which I think is the better way of doing things - I still had bugs in my code before I moved onto something else, but this might still be helpful to someone). This is what I did:
Extend JToolTip
Use a Swing Timer for timing
Add a MouseMotion listener to the JTree (in my case I wanted the popup to show when a node was hovered over)
Somewhat inelegantly, detect when the mouse moved over a tree node like this:
public void mouseMoved(MouseEvent e) {
int x = (int) e.getX();
int y = (int) e.getY();
TreePath path = getPathForLocation(x, y);
if (path == null) {
tooltip.hide();
} else {
TreeNode node = (TreeNode) path.getLastPathComponent();
tooltip.setHoveredNode(node);
if (!tooltip.isVisible) {
int absX = e.getXOnScreen();
int absY = e.getYOnScreen();
final Popup tooltipContainer = PopupFactory.getSharedInstance().getPopup(PDTreeView.this,
tooltip, absX, absY);
tooltip.setToolTipContainer(tooltipContainer);
tooltip.show();
}
}
}
tooltip.show() refers to how the tooltip was contained in a Popup
in order to show or hide it programmatically. show() shows the
Popup (and therefore tooltip) and also starts the Swing timer.
Timer has a method called actionPerformed() which is called at whatever interval you set. I just had that method call the code that adds new information to the tooltip. in hide(), I reset the tooltip and the timer.
I had issues with the popup or tooltip not resizing to fit the content, but otherwise this seemed ok.
On Windows if you click on the table tooltip (JToolTip), it will remove the tooltip AND select the row in the table. However on the Mac, it seems to just remove the tooltip and does NOT select the row in the table.
The code just uses a standard JTable and overrides getToolTipText method from JTable. No custom JToolTip is created or anything like that.
Code:
public class MyJTable extends JTable
{
#Override
public String getToolTipText(MouseEvent event)
{
return "Hello world";
}
}
**Update: On further investigation the issue seems to be due to something similar to this bug report. Basically the ToolTipManager.showTipWindow() is creating the tooltip as a HeavyWeight component on the Mac and a LightWeight component on Windows, which then causes the mouselistener not to be fired. The worse part is that regardless if you set the popupType to be lightweight, it will still create it as a heavyweight component in that method when the Java code calls popupFactory.getPopup(...)
No idea how to fix it.
Normally a different tooltip is given for each row or cell and not the entire table. So as you move the mouse the tooltip changes and is displayed in a different location so you never get an opportunity to actually click on the tooltip.
So, maybe as an alternative approach, you could also override the getToolTipLocation() method:
public Point getToolTipLocation(MouseEvent e)
{
return new Point(e.getX(), e.getY() + 10);
}
Now the user will never be able to click on it.
The reason clicking on the ToolTip does not generate a MouseEvent on the JTable is because the ToolTip is generated as a HEAVY_WEIGHT_POPUP rather than a LIGHT_WEIGHT_POPUP.
This can be due to a number of reasons, even something as simple as a L&F that does this.
The way it works in Java Swing is that the PopupFactory will create the ToolTip. In Java mixing HeavyWeight and LightWeight component can have some ramifications and as a result around Java 5-6 (not sure which) the Java language added the ability to define this property, however some L&F libraries don't use it or correctly adhere to it. And if you're using an older version it may just not be possible. For more details on the need for this toggle you can read the following bug report before the toggle was added to Swing.
In any case, if the component (in this case the ToolTip) is a HEAVY_WEIGHT_POPUP then the MouseEvent will not propagate to the JTable, and hence only the ToolTip will disappear and the row in the table will not be selected. The ToolTip needs to be a LIGHT_WEIGHT_POPUP for the MouseEvent to propagate to the JTable.
I've enabled the Drag'n'Drop facilities of a JTable by setting a TransferHandler to it. Now I can add items and reorder them inside this table. For easier adding items from another location (let's say windows desktop) I also set a DropTarget to the JPanel which holds the table and some other components. This DropTarget simply forwards the Transferable to the table's TransferHandler which adds the item at the end of the list:
panel.setDropTarget(new DropTarget(table, new DropTargetAdapter(){
#Override
public void drop(DropTargetDropEvent dtde){
TransferSupport transferSupport = new TransferHandler.TransferSupport(table, dtde.getTransferable());
table.getTransferHandler().importData(transferSupport);
}
}));
I've instantiated 4 of these panels so I can both add items from outside and move them from one to another panel.
That works for me, except the fact, that it performs always a COPY, not a MOVE. If I ask the DropTargetDropEvent for the DropAction with getDropAction() an exception with "Not a Drop" is thrown. (strange: Why exists a DropTargetDropEvent which isn't a drop?)
An exportDone() is also implemented and works correct when I drop directly into the body of the table. When dropping on the underlaying panel, exportDone() is invoked too, but with SourceAction==NONE (int 0).
What am I doing wrong? What is the right way to perform the right (COPY or MOVE) action when dropping on one of my panels?
I'm trying to use dynamically JTree component.
Under root node I have four nodes and one of them ("Operations") can have 0 to many children. This is set by user via editable list in separate window opened on users request. After editing this list user hits button 'Save' and then magic should happen. Edited list is sent to server(on the same machine actually, so it doesn't take to long), the window with list is closed but right before that the main window (with jtree) is told to refresh itself, and I can see that it does what is told in log output, but the changes don't show on the screen.
I am using DefaultTreeModel, this method is called to create model at the beginning(when first opening the window) and after the change to update the new model with new structure.
with dmtn.getLeafCount() I can see that newly downloaded structure from server is the right one with the changed number of leaves under 'Operations'
public DefaultTreeModel getDataStructure() {
int dataID = task.getData().getId();
LoggerUtility.logger.info("Data ID: " + dataID);
DefaultMutableTreeNode dmtn = Manager.manager.getDataStructure(task.getId());
LoggerUtility.logger.info("DTMN created "+dmtn.getLeafCount());
return new DefaultTreeModel(dmtn);
}
the method used to refresh the jtree looks like this (it's very messy):
public void updateTree(){
taskDataTree.setModel(getDataStructure());
((DefaultTreeModel)taskDataTree.getModel()).reload();
this.revalidate();
this.repaint();
taskDataTree.revalidate();
taskDataTree.repaint();
taskDataTree.updateUI();
taskDataTree.setVisible(false);
taskDataTree.setVisible(true);
jScrollPane2.setViewportView(taskDataTree);
}
It's very messy because I have tried to put in there every possible solution to my problem that I have found on forums,
I also tried with my own treemodel implementation which would call fireTreeStructureChanged(...) but it also didn't change.
I should probably also add that I'm using Netbeans GUI Builder to build my gui although I don't know if it has anything to do with that.
I would be very grateful for any help with that
BR
Lucja
EDIT!!!
I also tried puting it in another thread like that:
public void updateTree() {
SwingWorker sw = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
taskDataTree.setModel(getDataStructure());
((DefaultTreeModel) taskDataTree.getModel()).reload();
taskDataTree.revalidate();
taskDataTree.repaint();
taskDataTree.updateUI();
taskDataTree.setVisible(false);
taskDataTree.setVisible(true);
jScrollPane2.setViewportView(taskDataTree);
return null;
}
};
sw.execute();
}
but it also didn't help.
tree.setModel( anotherModel );
Is the only line of code that you need.
If it doesn't work then it means that the tree variable does not contain a reference to the tree that was added to the GUI. Maybe you have a class variable and a local variable of the same name.
From my point of view the own TreeModel implementation was a good approach. But I know that creating an TreeModelEvent with the correct data isn't that simple.
I would suggest to update your question with your TreeModel implementation so that we can find the problem with it.
In principle it should work this way (when you set a new Model, the tree reloads itself). (This is not the most efficient way, better let the model send appropriate events when it changes.)
If this does not work, make sure you are calling the setModel method in the AWT Event Dispatch Thread (with EventQueue.invokeLater (or SwingUtilities.invokeLater, which is the same), for example. I think you should not need all your revalidate(), repaint() etc. calls (and updateUI should only be done if you changed the look-and-feel configuration).