JTree node dosen't update after using a custom DefaultTreeCellRenderer - java

I have a JTree and a JtextField, So I have added listeners to The textfield so that if I Press Enter, the node is updated with the name entered in the field.
This works fine, the problem comes when I use a custom DefaultTreeCellRenderer to render disabled Nodes.After this I have to click manually to the node to see the change appear.
I did try reload(),nodeChanged() but no change.
The JTree panel:
public class RuleTree extends JPanel {
private static JTree autoscoreRuleTree;
private static DefaultTreeModel treeModel;
renameRule(){
/*
Code given below
*/
}
newRule(){
/*
Creates a new rule
*/
}
copyRule(){
/*
Copies a rule
*/
}
disableRule(){
/*
disables a rule codee given below
*/
}
The text field class:
public class RuleNamePanel extends JPanel {
private static JLabel ruleName;
private static JTextField ruleNameTextField;
/* To rename on pressing enter*/
ruleNameTextField.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
RuleTree.renameRule(ruleNameTextField.getText());
}
}
});
The render class: (Rule is a custom class)
public class AutoScoreRuleTreeRenderer extends DefaultTreeCellRenderer {
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf,
int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
Rule rule = (Rule) ((DefaultMutableTreeNode) value).getUserObject();
this.setEnabled(rule.isActivated());
return this;
}
}
And this is called when I press enter on the text field
public static void renameRule(String name) {
TreePath nodeToRename = autoscoreRuleTree.getSelectionPath();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodeToRename.getLastPathComponent();
Rule rule = (Rule) node.getUserObject();
rule.setName(name);
treeModel.nodeChanged(node);
}
The buttons panel
public class RuleModifyButtonPanel extends JPanel {
private JButton newRuleButton;
private JButton newChildButton;
private JButton duplicateRuleButton;
private JButton cutRuleButton;
private JButton pasteRuleButton;
private JButton disableRuleButton;
/*
Each button calls a method when clicked
*/
//When disable button is pressed this will internally call enableNode method
disableRuleButton.addActionListener(e -> RuleTree.disableRule());
}
Disable Node Method
private static void enableNodes(DefaultMutableTreeNode node, boolean enable) {
autoscoreRuleTree.setCellRenderer(new AutoScoreRuleTreeRenderer());
enablePanels(enable);
Rule rule = (Rule) node.getUserObject();
}
Expected to change on enter but changes when I manually click on the tree.
Before using render:
Enter the name > Pressed Enter
After Using render:
Enter the name after render > Press Enter > Click manually

Related

JAVA GUI - How to make a custom JList entry that has two components, and identify which component was clicked

I am trying to create a JList of JCheckBoxes. To do this, i created a JListRenderer which will render each individual entry as a checkbox.
private class JListRenderer extends JCheckBox implements ListCellRenderer<ACustomClass> {
#Override
public Component getListCellRendererComponent(JList<? extends ACustomClass> list, ACustomClass value, int index, boolean isSelected, boolean cellHasFocus) {
//code
return this;
}
}
I also created a custom ListSelectionListener that will handle the selection of these checkboxes and will select and unselect the checkbox.
public class CheckBoxListSelectionListener implements ListSelectionListener {
#Override
public void valueChanged(ListSelectionEvent e) {
//list selection code where i handle how to check or uncheck the JCheckBox
}
}
This is all working really well, hence i have not written body of the code in the above example.
I want the selection of the checkbox part of the entry, separate from the text.
Such that i can select/highlight the text without checking the checkbox. By doing this, I am hoping to choose a single entry and display its values at a different place, while the state of the checkbox stays unchanged.
I tried to create a RowPanel, and replaced JCheckbox with it, such that JList is rendered with RowPanel entries, making the JListRenderer something like this
private class JListRenderer extends RowPanel implements ListCellRenderer<RowPanel> {
#Override
public Component getListCellRendererComponent(JList<? extends RowPanel> list, RowPanel value, int index, boolean isSelected, boolean cellHasFocus) {
//code
return this;
}
}
public class RowPanel extends javax.swing.JPanel {
private DataClass data;
private JCheckBox checkbox;
private JLabel label;
/**
* Creates new form RowPanel
*/
public RowPanel() {
initComponents();
}
public RowPanel(DataClass value) {
this.data = value;
initComponents();
}
}
The problem with this approach is, that the ListSelectionListener only identify RowPanel as the source of 'selection' event. How can I identify "whether the checkbox was selected or the label"?

JTable prevent Strings

How can I prevent strings in a JTable and allow and show only numbers?
like for example I press "a" on my keyboard I won't not even that "a" will be displayed in the JTable cell. literally nothing should happen unless a user types in a number. so how can I prevent even not showing "a" ?
I had a similar issue some time ago and solved by validating with an KeyListener. This is a dirty way of doing it, but it works. The only weakness is if you're trying to edit a lot of cells quickly if you're a fast writer. Anyhow, here's the code that worked for me. I've added some commentary, but in short; we're overriding the normal validation and check with a TextField KeyListener if the given key is the one we allow in the TextField. If we allow the key, we enable TextField editing, if not, we turn it off to prevent the character being printed in the TextField. I hope this helps you.
UPDATE 1:
adding a celleditor on the TestField to prevent premature data insertion.
public class TableValidation extends JFrame
{
public static void main(String args[])
{
TableValidation x = new TableValidation();
x.setVisible(true);
}
JPanel topPanel;
JTable table = new JTable();
JScrollPane scrollPane;
String[] columnNames;
String[][] dataValues;
public TableValidation()
{
this.setTitle("JTable Cell Validation");
this.setDefaultCloseOperation (EXIT_ON_CLOSE);
this.setSize(300,112);
// make our panel to tin the table to
topPanel = new JPanel();
topPanel.setLayout(new BorderLayout());
this.getContentPane().add(topPanel);
// set some initial data for the table
columnNames = new String[] {"Anything" ,"Numbers only"};
dataValues = new String[][] { {"h4x0r","1337"} };
table.setRowHeight(50);
table.setModel( new CustomTableModel(dataValues, columnNames) );
TableColumn tableColumn = table.getColumnModel().getColumn(1); // apply our validation to the 2nd column
JTextField textfield = new JTextField(); // the textbox to which we test our validation
// setup our validation system. were passing the textfield as out celleditor source
tableColumn.setCellEditor(new MyCellEditor(textfield));
table.setCellSelectionEnabled(true);
scrollPane = new JScrollPane(table);
topPanel.add(scrollPane,BorderLayout.CENTER);
textfield.addKeyListener(new KeyAdapter()
{
public void keyTyped(KeyEvent e)
{
// check what keys can pass our test
if (textfield.isFocusOwner())
if (e.getKeyChar() != KeyEvent.VK_BACK_SPACE) // we allow backspace, obviously
if (!Character.isDigit(e.getKeyChar())) // if key is not a digit.. cancel editing
{
// when it detects an invalid input, set editable to false. this prevents the input to register
textfield.setEditable(false);
textfield.setBackground(Color.WHITE);
return;
}
textfield.setEditable(true);
}
});
}
}
class MyCellEditor extends AbstractCellEditor implements TableCellEditor
{
private static final long serialVersionUID = 1L;
private JTextField textField;
public MyCellEditor(JTextField textField)
{
this.textField=textField;
}
#Override
public boolean isCellEditable(EventObject e)
{
if (super.isCellEditable(e)) {
if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent) e;
return me.getClickCount() >= 2;
}
if (e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) e;
return ke.getKeyCode() == KeyEvent.VK_F2;
}
}
return false;
}
#Override
public Object getCellEditorValue()
{
return this.textField.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
this.textField.setFont(table.getFont());
this.textField.setText(value.toString());
return this.textField;
}
}
class CustomTableModel extends DefaultTableModel
{
CustomTableModel(String[][] data,String[] names)
{
super(data, names);
}
// we always pass true in our tablemodel so we can validate somewhere else
public boolean isCellEditable(int row,int cols)
{
return true;
}
}

Best way to render icon and text in JList

I create a JList constructed with my own sublass of AbstractListModel, the models stores instance of Action class and I had getElementAt() defined as
public final Object getElementAt(final int index)
{
return ((Action) actionList.get(index)).getValue(Action.NAME);
}
my JList displays a list of action names, which is okay.
But these actions also have an icon defined, so if I do
public final Object getElementAt(final int index)
{
return ((Action) actionList.get(index)).getValue(Action.SMALL_ICON)
);
}
it now displays the icon instead.
But I want both so I tried
public final Object getElementAt(final int index)
{
return new JButton(
(String)((Action) actionList.get(index)).getValue(Action.NAME),
(Icon)((Action) actionList.get(index)).getValue(Action.SMALL_ICON)
);
}
and now it just outputs the properties of the button instead how come
Nevermind reading the javadoc helped !
getElementAt() should just be
public final Object getElementAt(final int index)
{
return actionList.get(index);
}
then I look the render in the javadoc and modified as follows:
class MyCellRenderer extends JLabel implements ListCellRenderer {
ImageIcon longIcon = new ImageIcon("long.gif");
ImageIcon shortIcon = new ImageIcon("short.gif");
// This is the only method defined by ListCellRenderer.
// We just reconfigure the JLabel each time we're called.
public Component getListCellRendererComponent(
JList list, // the list
Object value, // value to display
int index, // cell index
boolean isSelected, // is the cell selected
boolean cellHasFocus) // does the cell have focus
{
Action action = (Action)value;
setText((String)action.getValue(Action.NAME));
setIcon((Icon)action.getValue(Action.SMALL_ICON));
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setEnabled(list.isEnabled());
setFont(list.getFont());
setOpaque(true);
return this;
}
}
Then set as the Jlists renderer
availableList.setCellRenderer(new MyCellRenderer());
and it works.

Compound JTree Node allowing events to pass through to objects underneath

I'm trying to create a JTree in which some nodes are compound objects containing a JLabel and a JButton. The Node is representing a server and port shown by the JLabel, the JButton will use the Desktop API to open the default browser and go to the URL.
I have read the following already and have followed them as closely as I can. The Node is displayed how I want it (mostly - I can deal with making it nicer later) but when I try to click on the button the JTree is responding to the events, not the button.
java swing: add custom graphical button to JTree item
http://www.java2s.com/Code/Java/Swing-JFC/TreeCellRenderer.htm
https://stackoverflow.com/a/3769158/1344282
I need to know how to allow the events to pass through the JTree so that they are handled by the object(s) underneath - the JButton or JLabel.
Here is my TreeCellEditor:
public class UrlValidationCellEditor extends DefaultTreeCellEditor
{
public UrlValidationCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
{
super(tree, renderer);
}
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value,
boolean isSelected, boolean expanded, boolean leaf, int row)
{
return renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
}
#Override
public boolean isCellEditable(EventObject anEvent)
{
return true; // Or make this conditional depending on the node
}
}
Here is the TreeCellRenderer:
public class UrlValidationRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer
{
JLabel titleLabel;
UrlGoButton goButton;
JPanel renderer;
DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
public UrlValidationRenderer()
{
renderer = new JPanel(new GridLayout(1, 2));
titleLabel = new JLabel(" ");
titleLabel.setForeground(Color.blue);
renderer.add(titleLabel);
goButton = new UrlGoButton();
renderer.add(goButton);
renderer.setBorder(BorderFactory.createLineBorder(Color.black));
backgroundSelectionColor = defaultRenderer
.getBackgroundSelectionColor();
backgroundNonSelectionColor = defaultRenderer
.getBackgroundNonSelectionColor();
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
Component returnValue = null;
if ((value != null) && (value instanceof DefaultMutableTreeNode))
{
Object userObject = ((DefaultMutableTreeNode) value)
.getUserObject();
if (userObject instanceof UrlValidation)
{
UrlValidation validationResult = (UrlValidation) userObject;
titleLabel.setText(validationResult.getServer()+":"+validationResult.getPort());
goButton.setUrl(validationResult.getUrl());
if (selected) {
renderer.setBackground(backgroundSelectionColor);
} else {
renderer.setBackground(backgroundNonSelectionColor);
}
renderer.setEnabled(tree.isEnabled());
returnValue = renderer;
}
}
if (returnValue == null)
{
returnValue = defaultRenderer.getTreeCellRendererComponent(tree,
value, selected, expanded, leaf, row, hasFocus);
}
return returnValue;
}
}
I would appreciate any insight or suggestions. Thanks!
The renderers do not work this way. They are used as rubber stamps, which means that there is really only one instance of renderer that is painted all over the place as the JList is painted. So it cannot handle mouse inputs, as the objects are not really there - they are just painted.
In order to pass mouse events to objects underneath, you need to implement a cell editor. Sometimes, the editor looks different than the renderer (String renderers are labels, editors are textfields, for example). Following this logic, the editor must be implemented using another instance of a component.
Now you are going to render buttons and use them for manipulating (ie. editing). The editor then must be another instance of JButton, distinctive from the renderer. Implementing button as renderer is easy, but the tricky part is to implement is as an editor. You need to extend AbstractCellEditor and implement TreeCellEditor and ActionListener. The button is then a field of the editor class. In the constructor of the editor class, you initialize the button and add this as a new action listener for the button. In the getTreeCellEditorComponent method, you just return the button. In the actionPerformed, you call whatever code you need to do on button press and then call stopCellEditing().
This way it works for me.
I made a SSCCE that demonstrates the usage on a String Tree
public class Start
{
public static class ButtonCellEditor extends AbstractCellEditor implements TreeCellEditor, ActionListener, MouseListener
{
private JButton button;
private JLabel label;
private JPanel panel;
private Object value;
public ButtonCellEditor(){
panel = new JPanel(new BorderLayout());
button = new JButton("Press me!");
button.addActionListener(this);
label = new JLabel();
label.addMouseListener(this);
panel.add(button, BorderLayout.EAST);
panel.add(label);
}
#Override public Object getCellEditorValue(){
return value.toString();
}
#Override public void actionPerformed(ActionEvent e){
String val = value.toString();
System.out.println("Pressed: " + val);
stopCellEditing();
}
#Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row){
this.value = value;
label.setText(value.toString());
return panel;
}
#Override public void mouseClicked(MouseEvent e){
}
#Override public void mousePressed(MouseEvent e){
String val = value.toString();
System.out.println("Clicked: " + val);
stopCellEditing();
}
#Override public void mouseReleased(MouseEvent e){
}
#Override public void mouseEntered(MouseEvent e){
}
#Override public void mouseExited(MouseEvent e){
}
}
public static class ButtonCellRenderer extends JPanel implements TreeCellRenderer
{
JButton button;
JLabel label;
ButtonCellRenderer(){
super(new BorderLayout());
button = new JButton("Press me!");
label = new JLabel();
add(button, BorderLayout.EAST);
add(label);
}
#Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){
label.setText(value.toString());
return this;
}
}
public static void main(String[] args){
JTree tree = new JTree();
tree.setEditable(true);
tree.setCellRenderer(new ButtonCellRenderer());
tree.setCellEditor(new ButtonCellEditor());
JFrame test = new JFrame();
test.add(new JScrollPane(tree));
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setSize(500, 500);
test.setLocationRelativeTo(null);
test.setVisible(true);
}
}
the node should have 2 parts a label and a button. When the user clicks the label then some detailed information about the node should appear in a different part of the GUI. When the user clicks the button it should result in a browser window opening. ..
Don't do it that way. Instead, have just the label in the tree. Add the button to the same GUI that displays the 'detailed information about the node'.

Placing JToggleButton with JPanel within into a JTable cell

I need to have a JToggleButton (that has custom background) that contains a JPanel with several JLabels within itself. That part works.
This button is placed afterwards in a JTable cell and is meant to be pressed by users. The problem is that i can only press the button on the second click. Apperenty on the first click the focus first jumps to the panel with JLabels and only afterwards to the actual button.
I tried several things to try solving this issue, but the same issue persists.
A) placing the JPanel with labels directly onto the JToggleButton#add().
B) using JLayeredPane to place Button and JPanel onto different Layers where JToggleButton takes constraint Integer(-) so that the JPanel with JLabels stays visible on top
Do you have any tips? Thanks
Below is a sample code that illustrates the problem. Clicking on the button only works second time.
public class ClickableCustomButtonInTable extends JToggleButton {
public ClickableCustomButtonInTable() {
Dimension d = new Dimension(100, 100);
JLabel lFirst = new JLabel("1st label");
lFirst.setPreferredSize(d);
JLabel lSecond = new JLabel("2nd label");
lSecond.setPreferredSize(d);
JPanel panel = new JPanel();
panel.setOpaque(true);
panel.setLayout(new BorderLayout());
panel.add(lFirst, BorderLayout.NORTH);
panel.add(lSecond, BorderLayout.SOUTH);
add(panel);
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
}
private static class CustomButtonRenderer implements TableCellRenderer {
private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
return button;
}
}
private static class CustomButtonEditor extends AbstractCellEditor
implements TableCellEditor {
private final ClickableCustomButtonInTable button = new ClickableCustomButtonInTable();
#Override
public Object getCellEditorValue() {
return button.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
return button;
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(new Dimension(200, 200));
Container content = frame.getContentPane();
TableModel model = new AbstractTableModel() {
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return ClickableCustomButtonInTable.class;
}
};
JTable table = new JTable(model);
// table.setBounds(new Rectangle(0, 0, content.getWidth(), content
// .getHeight()));
table.setRowHeight(frame.getHeight());
table.setDefaultRenderer(ClickableCustomButtonInTable.class,
new CustomButtonRenderer());
table.setDefaultEditor(ClickableCustomButtonInTable.class,
new CustomButtonEditor());
content.add(table);
content.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
When the table captures a mouse event to select a cell it passes the mouse event on to the deepest component regardless of whether that component can handle mouse events. In your example the first click ends up on one of the JLabels, bypassing the JToggleButton completely. Once the JToggleButton has become the active cell editor, mouse clicks work upon it normally. If it was to lose the focus, it would once again require two-clicks to activate.
You can also see this if you notice in your demo you click on the button border, not on the contained panel, the button works as desired.
One way to work around this is to ensure that any mouse event that is targeted at any component within the JToggleButton. You can do this using this static method:
static void addEventBubble(final Container target, Container container) {
for(Component comp:container.getComponents()) {
if (comp instanceof Container) {
addEventBubble(target, (Container) comp);
}
comp.addMouseListener(new MouseAdapter() {
private MouseEvent retarget(MouseEvent e) {
return new MouseEvent(target, e.getID(), e.getWhen(),
e.getModifiers(), e.getX(), e.getY(),
e.getClickCount(), e.isPopupTrigger(),
e.getButton());
}
public void mousePressed(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mousePressed(r);
}
}
public void mouseReleased(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mouseReleased(r);
}
}
public void mouseClicked(MouseEvent e) {
MouseEvent r = retarget(e);
for(MouseListener listen:target.getMouseListeners()) {
listen.mouseClicked(r);
}
}
});
}
}
and then at the end of your constructor invoke:
addEventBubble(this,this);
After this any mouse event upon any component within the button will also reach the button and hence change its state. After doing this, I found the button reacted to every click as desired.
http://www.coderanch.com/t/570021/GUI/java/click-event-custom-JToggleButton-JTable

Categories

Resources