How to refresh JTree UI - java

I have a JTree that shows files and folders of a directory. Also there is a button that disables some of the nodes in the JTree (using DefaultTreeCellRenderer).
The item gets disabled when I press the button, but JTree does not show it as a disabled item. Until I click somewhere, or in on if the items of the tree, then it also shows the disabled look of the item.
I know there is a reload() method for DefaultTreeModel. But I use a customised model. So this method doesnt work. Here is the model that I use to list files and folders: FileSystemModel
And this is my code:
public class FileViewer {
JFrame frame;
JPanel panel;
JTree tree;
File root;
public ArrayList<String> disabledNodes = new ArrayList<String>();
public FileViewer(){
frame = new JFrame("File Viewer");
panel = new JPanel(new BorderLayout());
root = new File("D:\\Documents\\A X");
FileSystemModel model = new FileSystemModel(root);
tree = new JTree();
tree.setModel(model);
panel.add(tree, BorderLayout.CENTER);
JButton press = new JButton("Press");
panel.add(press, BorderLayout.SOUTH);
press.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
disabledNodes.add("folder1");
}
});
tree.setCellRenderer(new CustomDefaultTreeCellRenderer());
frame.add(panel);
frame.setSize(600, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
new FileViewer();
}
class CustomDefaultTreeCellRenderer extends DefaultTreeCellRenderer{
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus){
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
File node = (File)value;
String name = node.getName();
for(String element : disabledNodes){
if(name.equals(element)){
this.setEnabled(false);
}
}
return this;
}
}
}
However, in the ActionListener of the button, I added tree.updateUI(); and it perfectly worked. But somehow I have heard updating UI is a bad practice since it will make other problems later. So is using updateUI correct here? or there is a better way to make the UI updated with clicks and user interactions?
Note, I will not add or remove any file from the tree, I just have to enable/disable nodes.
UPDATE: I just notice there is a repaint() option that does similar function for me. But still, Is it the right way of refreshing the JTree?

repaint() is the correct API to use in this situation.

Related

How to set tooltip in TreeCellRenderer?

I am stuck with setting a tooltip to one of my JPanel added to the node in a JTree. This question could be similar to JTree node's changable tooltip but not entirely.
I am also using JTree populated with some (custom) nodes. Each node contains a checkbox, a color box (JPanel) and node path. I am implementing TreeCellRenderer. I have not posted below code for what is being added to node as I think it is not necessary.
Below is part of my code:
public class CheckTreeCellRenderer extends JPanel implements TreeCellRenderer {
private static final long serialVersionUID = 4025435851260573240L;
CheckTreeSelectionModel selectionModel;
private TreeCellRenderer delegate;
TristateCheckBox checkBox = new TristateCheckBox();
JPanel panel = new JPanel();
public CheckTreeCellRenderer(TreeCellRenderer delegate, CheckTreeSelectionModel selectionModel){
this.delegate = delegate;
this.selectionModel = selectionModel;
setLayout(new BorderLayout());
setOpaque(false);
checkBox.setOpaque(false);
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){
Component renderer = delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
panel.setToolTipText("Hello");
removeAll();
add(checkBox, BorderLayout.WEST);
add(panel, BorderLayout.CENTER);
add(renderer, BorderLayout.EAST);
return this;
}
}
How to set a tooltip for JPanel added to a node?
Have a look at the docs of JTree.getToolTipText:
NOTE: For JTree to properly display tooltips of its renderers, JTree must be a registered component with the ToolTipManager. This can be done by invoking ToolTipManager.sharedInstance().registerComponent(tree). This is not done automatically!
This will fix it.

ListCellRendererComponent() method updates all the objects(elements) of JList similar to last added element

hi there i implemented a JList which contains JLabels as elements. My aim is implementing a contact list for server/client chat application. So, when a client connects to a server, JList will build to show his/her contact list.I selected to use JLabels cause they can have icons and text as well. However, i'm getting some trouble about overridden cellrenderer method. Whenever, a client gets online/offline JList updates its state and set all the items similar to the last added item. It's something like this,
this is the first time adding an offline state client into the friend list of a person;
afterwards, this is the second time adding a different client
and finally third time...
Furthermore, i remember that in tutorial it mention about JList overrides pain method and draw whole elements again and again when there is a change in the list. Well actually i'm a newbie about this rendering subject and this thing gets very annoying. Here you can see my renderer class;
RendererSample
and add an element into the model like this way in my main class
labelSetText = tempon.getNickName();
onlineStatus = tempon.isIsOnline();
model.addElement(createPanel());
and also createPanel() returns a JLabel which is like;
public static JLabel createPanel() {
JLabel panel = new JLabel();
return panel;
}
i hope that i have been clear about my problem. I have to achieve that when a contact changes his/her state or when a contact is added this action shouldn't affect the other elements. I will be very appreciate for every answers (and also if you can add brief explanation that what and why you did i will be grateful.) well thanks anyway
You should not put components such as JLabel into JList. Instead, use a model to hold the data and add a renderer to customize the presentation. See How to Use Lists for some examples.
Here is a very basic example of a renderer that adds icon:
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.*;
public class TestUserList {
public static class UserRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean hasFocus) {
if (value instanceof UserEntry) {
UserEntry user = (UserEntry) value;
JLabel label = (JLabel) super.getListCellRendererComponent(
list, user.getName(), index, isSelected, hasFocus);
if (user.isOnline())
label.setIcon(UIManager
.getIcon("OptionPane.informationIcon"));
else
label.setIcon(UIManager.getIcon("OptionPane.errorIcon"));
return label;
}
return super.getListCellRendererComponent(list, value, index,
isSelected, hasFocus);
}
}
public TestUserList() {
JFrame frame = new JFrame("User List");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JList list = new JList();
JScrollPane scrollPane = new JScrollPane(list);
JPanel content = new JPanel(new BorderLayout());
content.add(scrollPane, BorderLayout.CENTER);
final DefaultListModel model = new DefaultListModel();
model.addElement(new UserEntry("John", true));
model.addElement(new UserEntry("Jesse", false));
list.setModel(model);
list.setCellRenderer(new UserRenderer());
frame.add(content);
frame.setLocationByPlatform(true);
frame.pack();
frame.setVisible(true);
}
public class UserEntry {
private String name;
private boolean online;
public UserEntry(String name, boolean online) {
super();
this.name = name;
this.online = online;
}
public String getName() {
return name;
}
public Boolean isOnline() {
return online;
}
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestUserList();
}
});
}
}

JTree async node creation is not consistent with JGraph library

I am creating a Jtree with a rootNode and than create another thread that update the root node asynchronously.
It works fantastic if i run this Jtree independently in some JPanel, it was even working at some place in the project, but i was asked to have this Jtree in some new swing Component.
In new Swing Panel, it doesnot populate fully, it only populate the nodes that were inserted (at start for few milliseconds) before the Jtree was rendered on the Screen. Once the Jtree is rendered it doesnt get updated.
Now the interesting part i also made a mouse listener on the node so that i can create a new node by right click create node function, and with that new node is created and is added on the Jtree root node.
Important thing to add i was using newThread(){void run}).start() method to create a thread to add node on the Jtree, becuase i never felt the need of SwingUtilities.invokeLater method before. but now if i used SwingUtilities.invokeLater method than the main window also doesnot open it just halts during the startup, i just checked that SwingUtilities.invokeLater also works fine with the old component and ofcourse works fine independently.
And i do call model.nodeStructureChanged(changedNode); after adding the node thats why it was working fine before.
Kindly asist,
code is difficult to extract and Jtree code was working fine before, may be some component block the containing widgets to refresh itself async?
EDIT
Update to include some code, i am using Temp class as provided by Nick:-
public BasicGraphEditor(String appTitle, mxGraphComponent component)
{
// Stores and updates the frame title
this.appTitle = appTitle;
// Stores a reference to the graph and creates the command history
graphComponent = component;
final mxGraph graph = graphComponent.getGraph();
undoManager = createUndoManager();
// Do not change the scale and translation after files have been loaded
graph.setResetViewOnRootChange(false);
// Updates the modified flag if the graph model changes
graph.getModel().addListener(mxEvent.CHANGE, changeTracker);
// Adds the command history to the model and view
graph.getModel().addListener(mxEvent.UNDO, undoHandler);
graph.getView().addListener(mxEvent.UNDO, undoHandler);
// Keeps the selection in sync with the command history
mxIEventListener undoHandler = new mxIEventListener()
{
#Override
public void invoke(Object source, mxEventObject evt)
{
List<mxUndoableChange> changes = ((mxUndoableEdit) evt
.getProperty("edit")).getChanges();
graph.setSelectionCells(graph
.getSelectionCellsForChanges(changes));
}
};
undoManager.addListener(mxEvent.UNDO, undoHandler);
undoManager.addListener(mxEvent.REDO, undoHandler);
// Creates the graph outline component
graphOutline = new mxGraphOutline(graphComponent);
// Creates the library pane that contains the tabs with the palettes
libraryPane = new JTabbedPane();
/////////////////////////////////////////////////
// Only change i have done here: start
////////////////////////////////////////////////
Temp tempExplorer = new Temp();
libraryPane.add("new Explorere", tempExplorer);
/////////////////////////////////////////////////
// Only change i have done here: End
////////////////////////////////////////////////
// Creates the inner split pane that contains the library with the
// palettes and the graph outline on the left side of the window
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
libraryPane, graphOutline);
inner.setDividerLocation(320);
inner.setResizeWeight(1);
inner.setDividerSize(6);
inner.setBorder(null);
// Creates the outer split pane that contains the inner split pane and
// the graph component on the right side of the window
JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
graphComponent);
outer.setOneTouchExpandable(true);
outer.setDividerLocation(200);
outer.setDividerSize(6);
outer.setBorder(null);
// Creates the status bar
statusBar = createStatusBar();
// Display some useful information about repaint events
installRepaintListener();
// Puts everything together
setLayout(new BorderLayout());
add(outer, BorderLayout.CENTER);
add(statusBar, BorderLayout.SOUTH);
installToolBar();
// Installs rubberband selection and handling for some special
// keystrokes such as F2, Control-C, -V, X, A etc.
installHandlers();
installListeners();
updateTitle();
}
The above class is from Jgraph library as https://github.com/jgraph/jgraphx
And i am just adding the jtree component like as above, no other changes.
please help.
Swing isn't thread safe unless explicitly stated. In the JavaDocs for JTree, it explicitly says this isn't thread safe. If you update it in a thread outside the EDT, there's no guarentee anything will work. So if you want to update a JTree from a different Thread, you need to use SwingUtilities.invokeLater(Runnable run); in order to put the request on the EDT.
I'd recommend having a data structure to store the info of the JTree, and only using the JTree for User Interaction (not data storage).
EDIT
Here's an example of using SwingUtilities.invokeLater() to update a JTree while in the component model. Without you posting any code, this is the best I have to work with. Please try to use this to recreate your problem (add pieces of your code to this example until you have narrowed down what the problem is).
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;
public class Temp extends JPanel{
JTree tree = new JTree();
public Temp(){
JScrollPane jsp = new JScrollPane(tree);
// Creates the library pane that contains the tabs with the palettes
JTabbedPane libraryPane = new JTabbedPane();
libraryPane.add("new Explorere", jsp);
// Creates the inner split pane that contains the library with the
// palettes and the graph outline on the left side of the window
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
libraryPane, new JPanel());
inner.setDividerLocation(320);
inner.setResizeWeight(1);
inner.setDividerSize(6);
inner.setBorder(null);
// Creates the outer split pane that contains the inner split pane and
// the graph component on the right side of the window
JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
new JPanel());
outer.setOneTouchExpandable(true);
outer.setDividerLocation(200);
outer.setDividerSize(6);
outer.setBorder(null);
// Puts everything together
setLayout(new BorderLayout());
add(outer, BorderLayout.CENTER);
}
public static void main(String[] args) {
final Temp temp = new Temp();
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(temp);
frame.pack();
frame.setVisible(true);
}});
Thread updater = new Thread(temp.new CustomThread());
updater.start();
}
public class CustomThread implements Runnable{
#Override
public void run() {
for(int i = 0; i < 1000; i++){
updateTree("New Item "+ i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateTree(final String nodeToAdd){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
DefaultMutableTreeNode child = new DefaultMutableTreeNode(nodeToAdd);
model.insertNodeInto(child, root,root.getChildCount());
tree.scrollPathToVisible(new TreePath(child.getPath()));
}});
}
}
}

JTree events seem misordered

It appears to me that tree selection events should happen after focus events, but this doesn't seem to be the case. Assume you have a JTree and a JTextField, where the JTextField is populated by what is selected in the tree. When the user changes the text field, on focus lost, you update the tree from the text field. however, the tree selection is changed before the focus is lost on the text field. this is incorrect, right? Any ideas? Here is some sample code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class Focus extends JFrame
{
public static void main(String[] args)
{
Focus f = new Focus();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public Focus()
{
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
final JTextArea ta = new JTextArea(5, 10);
cp.add(new JScrollPane(ta), BorderLayout.SOUTH);
JSplitPane sp = new JSplitPane();
cp.add(sp, BorderLayout.CENTER);
JTree t = new JTree();
t.addTreeSelectionListener(new TreeSelectionListener()
{
public void valueChanged(TreeSelectionEvent tse)
{
ta.append("Tree Selection changed\n");
}
});
t.addFocusListener(new FocusListener()
{
public void focusGained(FocusEvent fe)
{
ta.append("Tree focus gained\n");
}
public void focusLost(FocusEvent fe)
{
ta.append("Tree focus lost\n");
}
});
sp.setLeftComponent(new JScrollPane(t));
JTextField f = new JTextField(10);
sp.setRightComponent(f);
pack();
f.addFocusListener(new FocusListener()
{
public void focusGained(FocusEvent fe)
{
ta.append("Text field focus gained\n");
}
public void focusLost(FocusEvent fe)
{
ta.append("Text field focus lost\n");
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Have your text field listener invoke setSelectionPath() to select the TreePath for the node that matches the text. The methods of DefaultMutableTreeNode can be used to traverse the tree. I'd use an ActionListener on the text field, but a FocusListener should work—just don't rely on the the order in which TreeSelectionListener events arrive.
Here's an example of obtaining the "pizza" node in the default JTree:
JTree tree = new JTree();
TreeNode node = (TreeNode) tree.getModel().getRoot();
node = node.getChildAt(2).getChildAt(1);
TreePath pizza = new TreePath(((DefaultMutableTreeNode) node).getPath());
Better news: I tried to defer the tree selection logic to the end of EDT, which will be executed after the text field's focus out!
JTree t = new JTree();
t.addTreeSelectionListener(new TreeSelectionListener()
{
public void valueChanged(TreeSelectionEvent tse)
{
ta.append("Tree Selection changed\n");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
logicInEDT...(tse);
}
});
}
});
This solution solved my data binding issue. Hope it make sense to you too.
Bad news: I got the same issue when I select another tree node. Selecting the same tree node which your text field is editing is fine.
Good news: I found this issue was really old. See
http://java.net/jira/browse/BINDING-67

Can I set the selected item for a JList without having an event thrown to the listeners?

I am working on a viewer, which uses a JList to show thumbnails of the pages of a document. The user can open a page by selecting it through in the JList, or throught other mechanisms, like entering the number in a text box.
When using the latter alternative, I want that the JList also selects the page. I do this using setSelectedIndex(), but this triggers an event, which causes the page to be loaded again, as if the user had clicked and selected the page in the JList, and this is causing me some problems.
How I see it, the index should be set some way (perhaps in the model) so that only the UI of the JList updates, without firing an event that the index has changed.
Is this possible? Or is there a better way to solve my issue?
You can remove all ListSelectionListener from the list, make a selection and then add them again.
You can create your own ListSelectionModel with a method that doesn't throw the event and set it as a selection model to your JList, and then use getSelectionModel().yourSelectIndexMethod(index).
You can also divert all your other methods of selection to the list, just find the corresponding entry if selecting the page by other means and select the item in the list. This way the item is selected and the page is loaded once.
Code for option 2:
public class ListTest extends JPanel{
private static final String[] items = new String[]{"1", "2", "3"};
private JList mylist;
private JComboBox myCombo;
private JTextArea myTA;
public ListTest() {
setLayout(new BorderLayout());
myCombo = new JComboBox(items);
myCombo.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
valueSelectedCombo(myCombo.getSelectedIndex());
}
});
JPanel pn = new JPanel();
pn.setLayout(new BoxLayout(pn, BoxLayout.X_AXIS));
pn.add(myCombo);
pn.add(Box.createHorizontalGlue());
pn.add(new JButton(new AbstractAction("Clear"){
#Override
public void actionPerformed(ActionEvent e){
myTA.setText("");
}
}));
add(pn, BorderLayout.NORTH);
add(new JScrollPane(getJList()), BorderLayout.WEST);
add(new JScrollPane(myTA = new JTextArea()), BorderLayout.CENTER);
}
private void valueSelectedList(int index){
myTA.setText(myTA.getText() + "\n" + items[index]);
}
private void valueSelectedCombo(int index){
myTA.setText(myTA.getText() + "\n" + items[index]);
((CustomSelectionModel)mylist.getSelectionModel()).setSelectionSilent(index);
}
private JList getJList(){
if (mylist == null){
mylist = new JList(items);
mylist.setSelectionModel(new CustomSelectionModel());
mylist.addListSelectionListener(new ListSelectionListener(){
#Override
public void valueChanged(ListSelectionEvent e){
if (!e.getValueIsAdjusting()){
valueSelectedList(mylist.getSelectedIndex());
}
}
});
mylist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mylist.setPreferredSize(new Dimension(100, 106));
}
return mylist;
}
private static class CustomSelectionModel extends DefaultListSelectionModel{
private boolean isSilent = false;
public void setSelectionSilent(int firstIndex){
isSilent = true;
setSelectionInterval(firstIndex, firstIndex);
isSilent = false;
}
protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting){
if (isSilent){
return;
}
super.fireValueChanged(firstIndex, lastIndex, isAdjusting);
}
}
public static void main(String[] args){
JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add content to the window.
frame.add(new ListTest());
// Display the window.
frame.pack();
frame.setSize(300, 200);
frame.setVisible(true);
}
}
It looks like setSelectedIndex() is just a convenient way to set the selection in the ListSelectionModel. Maybe your ListModel could flag or cache the result so it won't get loaded a second time.

Categories