I have recently been working on a Java Swing project and today I get stuck when modifying childrens of a DefaultMutableTreeNode.
The following SSCCE illustrates the problem:
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
public class Test
{
private static boolean executed = false;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//
final DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
final JTree tree = new JTree(root);
tree.addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent ev)
{
if (ev.getClickCount() == 2)
{
root.removeAllChildren();
for (String s: get())
{
root.add(new DefaultMutableTreeNode(s));
System.out.println(s + " added");
}
System.out.println();
tree.expandPath(tree.getSelectionPath());
}
}
});
frame.add(tree);
frame.setSize(200,200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
static String[] get()
{
if (!executed)
{
executed = true;
return new String[]{"a","b","c"};
}
else return new String[]{"a","b","c","d"};
}
}
As you see, the first time the get() method is invoked it returns a 3-element array, and a 4-element one afterwards. When I double-click (actually press) the root node for the first time there should be three children nodes (a, b and c), and the program behaves as expected. When I double-click the root node again I suppose it would have four children nodes. However it doesn't. When you double-click for the second time there are still only three, not four children nodes.
Did I make a mistake? Thanks for help in advance.
you should notify the model about changes use reload() method
add this lines before expand path like bellow
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
model.reload(root); // notify changes to model
tree.expandPath(tree.getSelectionPath());
Related
It has been a while since the last time I did Swing programming, and today I'm getting back to it. I have a simple JList which is backed by DefaultListModel. I also have a JButton which will show a JFileChooser. When a directory is selected, the JList is supposed to be populated with the file names under the selected directory.
What I found is that occasionally (actually it happens randomly quite often), the list wont be updated until I click on the (seemingly blank) list. I thought by using DefaultListModel, I can just call addElement() which will trigger the fireIntervalAdded (which should repaint the list, the container, etc) ? ALso, I believe the actionPerformed() method is invoked inside the EDT, so I should just be able to update the DefaultListModel. Anyway.... I have also tried calling revalidate() and repaint() on the list, the container, etc without any success either.
Secondly, when the list already has some items in it, clicking the button (which triggers the filechooser to be shown) will clear up the JList entries (without calling clear() on the model).
The source code is available at:
https://github.com/alexwibowo/spider
Here is an abstract of the code (hopefully it is sufficient)
package org.github.alexwibowo.spider.gui;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.FormLayout;
import javax.swing.*;
public class MainPanel extends JPanel {
public MainPanel() {
initComponents();
}
private void initComponents() {
toolBar1 = new JToolBar();
openFolderButton = new JButton();
splitPane1 = new JSplitPane();
scrollPane1 = new JScrollPane();
fileList = new JList();
//======== this ========
setLayout(new FormLayout(
"default:grow",
"default, $lgap, fill:default:grow"));
//======== toolBar1 ========
{
toolBar1.setFloatable(false);
//---- openFolderButton ----
openFolderButton.setIcon(UIManager.getIcon("Tree.openIcon"));
openFolderButton.setBorder(new EmptyBorder(5, 5, 5, 5));
toolBar1.add(openFolderButton);
}
add(toolBar1, CC.xy(1, 1));
//======== splitPane1 ========
{
//======== scrollPane1 ========
{
//---- fileList ----
fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
scrollPane1.setViewportView(fileList);
}
splitPane1.setLeftComponent(scrollPane1);
}
add(splitPane1, CC.xy(1, 3));
}
protected JToolBar toolBar1;
protected JButton openFolderButton;
protected JSplitPane splitPane1;
protected JScrollPane scrollPane1;
protected JList fileList;
}
and the panel which extends the above. This is the class which handles the addition of filenames to the list :
package org.github.alexwibowo.spider.gui
import javax.swing.*
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
class BarcodeMainPanel extends MainPanel {
private DefaultListModel<String> listModel = new DefaultListModel<String>()
BarcodeMainPanel() {
initModels()
initEventHandling()
}
protected void initModels() {
fileList.model = listModel
}
protected void initEventHandling() {
openFolderButton.addActionListener(new ActionListener() {
#Override
void actionPerformed(ActionEvent e) {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setLocation(50, 50);
if (chooser.showOpenDialog(BarcodeSpiderMainFrame.instance()) == JFileChooser.APPROVE_OPTION) {
listModel.clear()
File selectedDirectory = chooser.getSelectedFile()
selectedDirectory.eachFile {
listModel.addElement(it.name)
}
} else {
System.out.println("No Selection ");
}
}
})
}
}
The frame which contains the panel (just for completeness) :
package org.github.alexwibowo.spider.gui
import groovy.transform.Synchronized
import javax.swing.*
import java.awt.*
class BarcodeSpiderMainFrame extends JFrame{
private static BarcodeSpiderMainFrame INSTANCE;
BarcodeSpiderMainFrame(String title) throws HeadlessException {
super(title)
}
#Synchronized
public static BarcodeSpiderMainFrame instance() {
if (INSTANCE == null) {
INSTANCE = new BarcodeSpiderMainFrame("Spider")
INSTANCE.minimumSize = new Dimension(800,600)
INSTANCE.maximumSize = new Dimension(1024,768)
INSTANCE.defaultCloseOperation = EXIT_ON_CLOSE
}
INSTANCE.initializeContent()
INSTANCE.visible = true
INSTANCE
}
private void initializeContent() {
BarcodeMainPanel mainPanel = new BarcodeMainPanel()
this.contentPane.add(mainPanel);
}
}
and finally the launcher (just for completeness) :
package org.github.alexwibowo.spider
import org.github.alexwibowo.spider.gui.BarcodeSpiderMainFrame
import javax.swing.*
#Singleton
class SpiderLauncher {
BarcodeSpiderMainFrame barcodeSpiderMainFrame
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SpiderLauncher.instance.run(args);
}
});
}
void run(String[] args) {
barcodeSpiderMainFrame = BarcodeSpiderMainFrame.instance()
barcodeSpiderMainFrame.show()
}
}
This is what fixes it.
In BarcodeSpiderMainFrame, remove the call to setVisible. So it will look something like:
public static BarcodeSpiderMainFrame instance() {
if (INSTANCE == null) {
INSTANCE = new BarcodeSpiderMainFrame("Spider")
INSTANCE.minimumSize = new Dimension(800,600)
INSTANCE.preferredSize = new Dimension(1024,768)
INSTANCE.maximumSize = new Dimension(1024,768)
INSTANCE.defaultCloseOperation = EXIT_ON_CLOSE
}
INSTANCE.initializeContent()
// INSTANCE.visible = true // remove this line
INSTANCE
}
and in the launcher, call setVisible()
#Singleton
class SpiderLauncher {
BarcodeSpiderMainFrame barcodeSpiderMainFrame
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SpiderLauncher.instance.run(args);
}
});
}
void run(String[] args) {
barcodeSpiderMainFrame = BarcodeSpiderMainFrame.instance()
barcodeSpiderMainFrame.pack()
barcodeSpiderMainFrame.setVisible(true) // add this line
}
}
I have added the call to pack(). But i dont think it really matters. How did the above fix my problem? I do not know. It would be great if someone can explain what actually happened.
Im Trying to develop a small application that uses a gui to move files from anywhere on the system. I have the code to move the files and they do indeed move when selected and buttons pressed but I dont know how to refresh the filesystem viewer to reflect the change. The code I have to set up the system viewer is below:
public class FileMover {
//Start of Global Variables
private JTree tree;
private DefaultTreeModel treeModel;
private FileSystemView fileSystemView;
protected File currentFile;
protected LinkedList fileLocations;
protected JTree movedTree;
protected JPanel areaLeft;
protected JPanel areaRight;
protected JPanel areaMiddle;
protected final JFrame openFrame;
//end of global variables.
//Constructor for FileMover
public FileMover()
{
openFrame = new JFrame("File Mover");
createFileMover();
}
public void createFileMover(){
Container contentPane = this.openFrame.getContentPane();
fileLocations = new LinkedList();
contentPane.setLayout(new BorderLayout());
areaLeft = new JPanel();
areaRight = new JPanel();
areaMiddle = new JPanel();
contentPane.add(areaLeft, BorderLayout.WEST);
contentPane.add(areaRight, BorderLayout.EAST);
contentPane.add(areaMiddle, BorderLayout.CENTER);
areaLeft.add(createSystemView());
movedTree = new JTree(fileLocations.toArray());
JScrollPane movedPane = new JScrollPane(movedTree);
JButton moveRightButton = new JButton("->");
JButton moveLeftButton = new JButton("<-");
JButton refresh = new JButton("Refresh");
areaMiddle.setLayout(new GridLayout(1,2));
areaMiddle.add(moveRightButton);
areaMiddle.add(refresh);
areaMiddle.add(moveLeftButton);
//actualy move the file
moveRightButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("Moving file: "+ currentFile.getName());
fileLocations.add(currentFile);
try {
//move the file to the correct location.
moveFile(currentFile);
} catch (IOException ex) {
Logger.getLogger(FileMover.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println(fileLocations.getFirst().toString());
}
});
//refresh the gui
refresh.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
refresh();
}
});
//finish setting up the frame
openFrame.setSize(1280, 768);
openFrame.setLocationRelativeTo(null);
openFrame.setDefaultCloseOperation(3);
openFrame.setResizable(false);
openFrame.pack();
openFrame.setVisible(true);
}
/** Add the files that are contained within the directory of this node.
*/
private void showChildren(final DefaultMutableTreeNode node) {
tree.setEnabled(false);
SwingWorker<Void, File> worker = new SwingWorker<Void, File>() {
#Override
public Void doInBackground() {
File file = (File) node.getUserObject();
if (file.isDirectory()) {
File[] files = fileSystemView.getFiles(file, true); //!!
if (node.isLeaf()) {
for (File child : files) {
publish(child);
}
}
}
return null;
}
#Override
protected void process(List<File> chunks) {
for (File child : chunks) {
node.add(new DefaultMutableTreeNode(child));
}
}
#Override
protected void done() {
tree.setEnabled(true);
}
};
worker.execute();
}
/** Update the File details view with the details of this File. */
private void setFileDetails(File file) {
System.out.println("Path: "+ file.getPath());
System.out.println("Name: "+ fileSystemView.getSystemDisplayName(file));
}
private void refresh(){
//refresh the tree here
}
private JScrollPane createSystemView(){
//file syatem hierarchy
fileSystemView = FileSystemView.getFileSystemView();
// the File tree
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
treeModel = new DefaultTreeModel(root);
TreeSelectionListener treeSelectionListener = new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent tse){
DefaultMutableTreeNode node =
(DefaultMutableTreeNode)tse.getPath().getLastPathComponent();
showChildren(node);
setFileDetails((File)node.getUserObject());
currentFile = (File)node.getUserObject();
}
};
// show the file system roots.
File[] roots = fileSystemView.getRoots();
for (File fileSystemRoot : roots) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileSystemRoot);
root.add( node );
File[] files = fileSystemView.getFiles(fileSystemRoot, true);
for (File file : files) {
if (file.isDirectory()) {
node.add(new DefaultMutableTreeNode(file));
}
}
}
tree = new JTree(treeModel);
tree.setRootVisible(false);
tree.addTreeSelectionListener(treeSelectionListener);
tree.setCellRenderer(new FileTreeCellRenderer());
tree.expandRow(0);
JScrollPane treeScroll = new JScrollPane(tree);
tree.setVisibleRowCount(15);
Dimension preferredSize = treeScroll.getPreferredSize();
Dimension widePreferred = new Dimension(
200,
(int)preferredSize.getHeight());
treeScroll.setPreferredSize( widePreferred );
return treeScroll;
}
The move left button and the area right are not finished but what I need is when I select a node in the tree and click the right arrow button the file/folder the node reflects is moved internally by my moveFile code and that works. but that change is not reflected in the tree so how can show this change in the tree i.e refresh the tree to show the current state of the filesystem?
I've tried treeModel.reload(); but that doesnt seem to work and throws a null pointer exception.
I've tried :
areaLeft.removeAll();
areaLeft.add(createSystemView());
thinking that it may refresh it by recreating the system view but that doesnt seem to do anything.
Help here would be most appreciated!
Edit: Below is the requested code for the file tree renderer:
/** A TreeCellRenderer for a File. */
class FileTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = -7799441088157759804L;
private FileSystemView fileSystemView;
private JLabel label;
FileTreeCellRenderer() {
label = new JLabel();
label.setOpaque(true);
fileSystemView = FileSystemView.getFileSystemView();
}
#Override
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
File file = (File)node.getUserObject();
label.setIcon(fileSystemView.getSystemIcon(file));
label.setText(fileSystemView.getSystemDisplayName(file));
label.setToolTipText(file.getPath());
if (selected) {
label.setBackground(backgroundSelectionColor);
label.setForeground(textSelectionColor);
} else {
label.setBackground(backgroundNonSelectionColor);
label.setForeground(textNonSelectionColor);
}
return label;
}
}
Since from your code it looks that you know what you're doing, I'll just show the elementary example which will only work for the first time you press the refresh button:
private DefaultMutableTreeNode someNode;
private void refresh(){
System.out.println(someNode);
treeModel.removeNodeFromParent(someNode);
}
and rewrite the part of createSystemView() like this:
int cnt = 0;
for (File file : files) {
if (file.isDirectory()) {
if ((cnt++) == 5) { //1
System.out.println(file.getPath());
node.add(someNode = new DefaultMutableTreeNode(file));
}
else {
node.add(new DefaultMutableTreeNode(file));
}
}
}
This will only work if you have at least six (comment 1) directories on your root. Run the file, count the directories from root - when you press the refresh button it'll remove the sixth directory. If you press the button again it will try to remove the already removed node so you'll get an IllegalArgumentException.
You need to call removeNodeFromParent on the treeModel:
Message this to remove node from its parent. This will message nodesWereRemoved to create the appropriate event. This is the preferred way to remove a node as it handles the event creation for you.
See this example also.
If you want to refresh the entire view you should recreate the model in the refersh function as you do in initialization, or just iterate the model and update as neccessary - but I'd suggest a breadth first traversal of the model in that case.
EDIT:
"so after every move refresh will show the FS again with the files where they should now be". Let's start with reinitializing the model, by that I solely mean the treeModel instance of DefaultTreeModel class.
So, in the refresh() method you create a new treeModel instance, and fill it with DefaultMutableTreeNode instances like you did in createSystemView() method:
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
treeModel = new DefaultTreeModel(root);
File[] roots = fileSystemView.getRoots();
for (File fileSystemRoot : roots) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(fileSystemRoot);
root.add( node );
File[] files = fileSystemView.getFiles(fileSystemRoot, true);
for (File file : files) {
if (file.isDirectory()) {
node.add(new DefaultMutableTreeNode(file));
}
}
}
We have prepared the model, now we need to set it to the tree, I believe that a mere:
tree.setModel(treeModel);
should suffice, notice that you do not need to add the listener, because the setModel method reattaches the listeners from old model to the new model and also notifies the JTree view to redraw itself accordingly - I checked the source. If it won't redraw (I doubt it, but I haven't tested this) force it in the next line like this:
treeModel.reload();
APIdoc for reload() method here.
If however you want to update the model in the refresh function, which I believe would be more natural operation for an action called "refresh", you'll need to obtain the new first level directories and then traverse the tree like this (since we're traversing all the root's children, it would be the mentioned breadth-first traversal):
int firstLevelCount = treeModel.getChildCount(root);
DefaultMutableTreeNode child;
for (int i=0; i < firstLevelCount; i++) {
child = treeModel.getChild(root, index);
// update logic part 1
}
// update logic part 2
treeModel.reload();
You'll need the DefaultMutableTreeNode.getUserObject() method which will return a file that that tree node represents.
Obviously, you ll want to remove all the nodes from the model that are not in the the new files array, and for all the files that don't have their correspondent node in the model, you'll want to add them to the model.
I would advise not to use the files array but list (so that you could benefit from the List.contains() method) e.g.
List<File> files = new ArrayList<File>(Arrays.asList(FileSystemView.getFileSystemView().getRoots()));
Also, rewrite a part of your renderer like this:
if (file != null) {
label.setIcon(fileSystemView.getSystemIcon(file));
label.setText(fileSystemView.getSystemDisplayName(file));
label.setToolTipText(file.getPath());
}
because the root node in your view doesn't have associated file so you'll most probably get an NPE at some time updating the model.
In the second variant I described, you might get tempted to remove the root child node from inside the loop (at the update logic 1 part) - if you do that you'll most probably get an ConcurrentModificationException. The solution is to make another
List<DefaultMutableTreeNode> toBeRemoved = new ArrayList<DefaultMutableTreeNode>();
and at //update logic part 1 place (insde the loop) instead of removing the nodes from the model, you put it in that list. When you're done iterating the loop, you simply iterate over that list and remove them from the model at //update logic part 2 place e.g.
for (DefaultMutableTreeNode node : toBeRemoved) {
treeModel.removeNodeFromParent(node);
}
As mentioned, this will automatically trigger the view (JTree) redraw, so see what best fits your need.
EDIT^2:
Regarding the second variant of your refresh method, you already have the
private DefaultTreeModel treeModel;
as a global variable, you obtain the root of the tree like this (getRoot())
DefaultMutableTreeNode root = (DefaultMutableTreeNode)treeModel.getRoot();
and as you already did, you obtain the current state of the filesystem like this:
FileSystemView.getFileSystemView().getRoots();
these are all the variables you need to write the refresh() method as described.
Regarding the move (the buttons with arrows (<- , ->)) operation, from inside your treeSelectionListener make this variable global:
DefaultMutableTreeNode node = (DefaultMutableTreeNode)tse.getPath().getLastPathComponent();
With this variable (let's call it selectedNode), you can make use of the DefaultTreeModel methods:
removeNodeFromParent(MutableTreeNode node)
insertNodeInto(MutableTreeNode newChild,MutableTreeNode parent,int index)
Note that both methods will trigger JTree redraw. This should be sufficient to implement the operations you described. Also, you could rewrite the moveButton actionListener method like this:
moveRightButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
if (selectedNode != null) {
//all the move-code-refresh-tre-view-here
selectedNode = null;
}
}
});
the selectedNode will only be set by treeSelectionListener, so in this manner you are sure that the the file move operation will only take place if a file is actually selected in the tree view.
I'm creating this app that creates tabs and JTrees on button click events. The problem is when I try to add ne node to a JTree it doesn't refresh the JTree ( or it doesn't add the node to it ... I don't really know).
This is the functions that creates the tabs and trees:
jTabbedPane1.add(st,jSplitpane10);
int count = jTabbedPane1.getTabCount();
jTabbedPane1.setSelectedIndex(count-1);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("All Notebooks");
DefaultMutableTreeNode notebook1 = new DefaultMutableTreeNode("Notebook 1");
root.add(notebook1);
// Create tree
JTree tree = new JTree(root);
//Create Scroll Pane for the tree
JScrollPane sp = new JScrollPane(tree);
Global.trees.add(tree);
And this is the code that is supposed to add new node "Green" to a tree in the selected tab:
int i = jTabbedPane1.getSelectedIndex();
DefaultTreeModel model = (DefaultTreeModel)Global.trees.get(i).getModel();
// Find node to which new node is to be added
int startRow = 0;
String prefix = "J";
TreePath path = Global.trees.get(i).getNextMatch(prefix, startRow, Position.Bias.Forward);
MutableTreeNode node = (MutableTreeNode)path.getLastPathComponent();
// Create new node
MutableTreeNode newNode = new DefaultMutableTreeNode("green");
// Insert new node as last child of node
model.insertNodeInto(newNode, node, node.getChildCount());
model.reload(newNode);
Here's also the declaration of the global list of JTrees:
public class Global {
public java.util.List<JTree> trees = new ArrayList<JTree>();
}Global Global;
Any ideas why new nodes aren't showing in the trees???
JTree and JTable are the most complex Swing components.
I think is reloaded, but not expanded.
The JTree root node has many settings: how handler or not show root node or not and so on.
I am using a debug console listing where I dump the model data as text ( override the toString() in nodes too) and I can see easily,, what is there, but if you have just a few nodes not needed, it is enough with the Netbeans's debugger.
Also try to expand all rows in tree to have it visible.
Your code is good:
// Create new node
MutableTreeNode newNode = new DefaultMutableTreeNode("green");
// Insert new node as last child of node
model.insertNodeInto(newNode, node, node.getChildCount());
model.reload(newNode);
but probably it is not visible, you need to expand, how to do it, that is another question
Here is some code to add JTree programmatically:
Source:http://sickprogrammersarea.blogspot.in/2014/03/add-jtree-programmatically-in-java-swing.html
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
public class MyTree extends JApplet
{
JTree t;
JLabel l1;
String lang[]={"C","C++","JAVA","PYTHON","AJAX","PHP"};
public void init()
{
try
{
SwingUtilities.invokeAndWait(new Runnable() {
public void run()
{
makeGUI();
}
});
} catch(Exception e){
System.out.println("Sorry some error occured "+e);
}
}
private void makeGUI()
{
DefaultMutableTreeNode top=new DefaultMutableTreeNode("Language");
DefaultMutableTreeNode a=new DefaultMutableTreeNode("Programming");
top.add(a);
DefaultMutableTreeNode a1=new DefaultMutableTreeNode("C");
a.add(a1);
DefaultMutableTreeNode a2=new DefaultMutableTreeNode("Java");
a.add(a2);
DefaultMutableTreeNode b=new DefaultMutableTreeNode("Web Based");
top.add(b);
DefaultMutableTreeNode b1=new DefaultMutableTreeNode("PHP");
b.add(b1);
DefaultMutableTreeNode b2=new DefaultMutableTreeNode("JSP");
b.add(b2);
t=new JTree(top);
JScrollPane scr=new JScrollPane(t);
add(scr);
setLayout(new FlowLayout());
l1=new JLabel("Choose a language");
add(l1,BorderLayout.SOUTH);
t.addTreeSelectionListener(new TreeSelectionListener () {
public void valueChanged(TreeSelectionEvent ae) {
l1.setText(ae.getPath()+" is selected");
}
});
}
}
I produced a small example application to test your issue. I found that the node was being added correctly, but not expanding. Also, the call to model.reload seems unnecessary.
I added a call to expandPath on the tree to cause the display to show the new node. You may need to double check this doesn't expand too much of the tree:
public class Example extends JFrame {
private JTree tree;
public Example() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("All Notebooks");
DefaultMutableTreeNode notebook1 = new DefaultMutableTreeNode("Notebook 1");
root.add(notebook1);
tree = new JTree(root);
JScrollPane sp = new JScrollPane(tree);
setLayout(new BorderLayout());
add(sp, BorderLayout.CENTER);
add(new JButton("Go") {
{
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Example.this.addNode();
}
});
}}, BorderLayout.SOUTH);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
}
private void addNode() {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Find node to which new node is to be added
int startRow = 0;
String prefix = "N";
TreePath path = tree.getNextMatch(prefix, startRow,
Position.Bias.Forward);
MutableTreeNode node = (MutableTreeNode) path.getLastPathComponent();
// Create new node
MutableTreeNode newNode = new DefaultMutableTreeNode("green");
// Insert new node as last child of node
model.insertNodeInto(newNode, node, node.getChildCount());
tree.expandPath(path);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example().setVisible(true);
}
});
}
}
how do you trap the event before the new tab is switched to?
In every Tab I have JTable and i do something with it's data(delete, add , update). I would like to do data validation(save or cancel changes) before switching to the new tab. I use Java 1.5.
class ViewPanel extends JPanel
{
private void Components() {
setPreferredSize(new Dimension(700, 400));
tabbedPane.addTab("DC", ANSFER.getIcon(),new DcTabPanel(this), "DC");
tabbedPane.addTab("PC", THUMB4.getIcon(),new PcTabPanel(this), "PC");
tabbedPane.addChangeListener(this);
add(tabbedPane);
}
public void stateChanged(ChangeEvent e) {
}
}
JTabbedPane is backed by a SingleSelectionModel. If you extend DefaultSingleSelectionModel, you can override the setSelectedIndex method and implement your logic.
// in new selection model:
public void setSelectedIndex(int index) {
// do pre-switch things here
super.setSelectedIndex(index);
}
// in ViewPanel, on tabbedPane create:
tabbedPane.setModel(newSelectionModel);
The reason you can't simply use a ChangeListener is because that fires on change. By extending the selection model, you fire before the tab change.
You can prevent tab switching by extending JTabbedPane and override setSelectedIndex(int). Here is a small example illustrating that. It simply prevents from switching between non-contiguous tabs:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
public class Test2 {
private static class BlockingTabbedPane extends JTabbedPane {
public static interface TabSwitchAllower {
public boolean allowTabSwitch(int from, int to);
}
private TabSwitchAllower allower;
public BlockingTabbedPane(TabSwitchAllower allower) {
super();
this.allower = allower;
}
#Override
public void setSelectedIndex(int index) {
if (allower == null || allower.allowTabSwitch(getSelectedIndex(), index)) {
super.setSelectedIndex(index);
}
}
}
protected static void initUI() {
final JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BlockingTabbedPane.TabSwitchAllower allower = new BlockingTabbedPane.TabSwitchAllower() {
#Override
public boolean allowTabSwitch(int from, int to) {
if (Math.abs(from - to) == 1) {
return true;
} else {
JOptionPane.showMessageDialog(frame, "You can only switch between contiguous tabs");
}
return false;
}
};
JTabbedPane tabbedPane = new BlockingTabbedPane(allower);
for (int i = 0; i < 10; i++) {
tabbedPane.addTab("Tab-" + i, new JLabel("Hello tab " + i));
}
frame.add(tabbedPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initUI();
}
});
}
}
java actionlistener on a tab
How to Write a Change Listener (Oracle Docs)
JTabbedPane API (Oracle Docs)
Those two links should help you out. I haven't really worked with tabbedPanes, but I am assuming that the getSelectedComponent() will return the current selected tab. So you can have a handle to the currentTab which will be set during instantiation. Then you can have something like this.
class TabListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
// Replace JSlider with whatever your tab's data type is
JSlider source = (JSlider)e.getSource();
// Use the 'currentTab' handle to do what you want.
currentTab = getSelectedComponent();
// I'm assuming that the 'selected component' by the time this stuff
// runs is going to be the new selected tab.
}
}
I am not too confident about my answer, but I certainly hope that this will point you towards the right direction! Please say if you need any clarification or anything! If I happen to discover anything that I think might be useful, I'll be certain to edit my answer!
i'm having problems with this listener, the gui in general constructs and works fine, also the jlist is there but when i select some items in the list i dont see the results and also not the printl() i wrote for test purpose, pls note this code is contained within the getJContentPane in order to add the event handler at init-time
private JList myList=new JList(dlm);//a defaultlistmodel
myList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
if (evt.getValueIsAdjusting()){
System.out.println("Eventhandler called");
return;
}
System.out.println("Eventhandler called");
doMyMethod(evt);
}
});
The doMyMethod():
private void doMyMethod(ListSelectionEvent e){
if(e.getValueIsAdjusting()){
return;
}else{
String item=(String)e.getSource();
accounter.add(item);
}
}
It is a very simple method which takes as parameter an instance of ListSelectionEvent
The main problem in my opinion is not the doMyMethod() which performs very basic actions but the fact that the eventHandler is not fired at all, it seems lik the gui does not "listen" to this list at all
Any idea?
here the initialisation code:
private JScrollPane getScrollBox() {
if (scrollboxBox == null) {
scrollboxBox = new JScrollPane();
scrollBox.setBounds(new Rectangle(280, 56, 245, 204));
scrollBox.getViewport().add(myList,null);
myList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
System.out.println("addListSelectionListener");
if (evt.getValueIsAdjusting()){
System.out.println("Eventhandler called");
return;
}
System.out.println("Eventhandler called");
doMyMethod(evt);
}
});
}
return scrollboxBox;
}
After trying to understand what is going on in your question I understood that you are failing to get selected item in the list, when the selection is changed.
To present you how this could be achieved I have built the below example. You can observe there when each part of the if else is called. The if(e.getValueIsAdjusting()) is execute always when you drag the moue over items. The else part is called when you release the mouse while previously clicked on an item.
There are some serious issues in the code that you have provided, therefore I tried to remove all that is unnecessary, e.g.
1.The doMyMethod() method and the if(e.getValueIsAdjusting()) piece of code that is in it would never be called since you already are checking that condition before it is called. Plus there is no need to code a method working on listener's events, this code should stay inside the listener which is private. Then if you want to call programmatically its code you would call the listener.valueChanged(evt).
2.The evt.getSource() returns the object which is author of the event in this case the myList not the selected item which I think you were after, for this use the getSelectedValue() on the source or myList directly since this listener serves only this list.
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class ListTest extends JPanel
{
private JScrollPane myScrollPane;
private JList myList;
private ListSelectionListener lSL = new ListSelectionListener()
{
#Override
public void valueChanged(ListSelectionEvent evt)
{
if(evt.getValueIsAdjusting())
{
System.out.println("Eventhandler called evt.getValueIsAdjusting() true");
return;
}
else
{
// String item = (String) evt.getSource();//!!!Exception casting JList to String
//maybe what you need is getSelectedValue()
System.out.println("else called myList.getSelectedValue() ="+myList.getSelectedValue());
}
}
};
private ListTest()
{
int noRows = 25;
Object[] listData = new String[noRows];
for(int i =0; i < noRows; i++)
listData[i]= "Oi "+i;
this.myList = new JList(listData);
this.myScrollPane = new JScrollPane(myList);
myList.addListSelectionListener(lSL);
add(myScrollPane);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
ListTest p = new ListTest();
JPanel contentPane = new JPanel();
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
Hope that even if it doesn't solve your problem it at least will be helpful in building an SSCCE for us.