Nimbus TableHeader was not highlighted as 'pressed' - java

The JTableHaeder has no 'pressed' highlighting by default. (Nimbus)
NimbusDefaults says it has a default [Pressed] background painter.
What should I do, to see this when i click on the TableHeader?
UPDATE 1
The NimbusStyle.getExtendedState returns the PRESSED on mouseDown correctly. But the NimbusStyle.getBackgroundPainter(SynthContext) returns null cause there is an null in the NimbusStyle.Values cache for the CacheKey "backgroundPainter$$instance" with this state.
What is wrong there?
UPDATE 2
My example shows a JTableHeader and a JScrollBar with an 'Pressed Behavior'.
For the JScrollBar my putClientProperty( "Nimbus.State" ) works with a repaint problem.
public class Header extends JPanel{
public Header() {
super(new BorderLayout());
JTableHeader header = new JTable(5, 3).getTableHeader();
JScrollBar scroll = new JScrollBar(JScrollBar.HORIZONTAL);
add(header, BorderLayout.NORTH);
add(scroll, BorderLayout.SOUTH);
scroll.addMouseListener( new PressedBehavior() );
header.addMouseListener( new PressedBehavior() );
}
static public void main( String[] s ) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Nimbus Pressed Example");
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setBounds( 150, 150, 300, 200 );
f.getContentPane().add( new Header() );
f.setVisible( true );
}
});
} catch( Exception fail ) { /*ignore*/ }
}
private class PressedBehavior extends MouseAdapter {
#Override
public void mouseReleased( MouseEvent e ) {
JComponent source = (JComponent)e.getComponent();
source.putClientProperty( "Nimbus.State", null );
}
#Override
public void mousePressed( MouseEvent e ) {
JComponent source = (JComponent)e.getComponent();
source.putClientProperty( "Nimbus.State", "Pressed" );
//source.invalidate();
//source.repaint();
}
}
}

technically, you need that state on the rendering component, not on the JTableHeader itself:
#Override
public void mousePressed( MouseEvent e ) {
JComponent source = (JComponent)e.getComponent();
source.putClientProperty( "Nimbus.State", "Pressed" );
if (source instanceof JTableHeader) {
((JComponent) ((JTableHeader) source).getDefaultRenderer())
.putClientProperty("Nimbus.State", "Pressed");
}
}
Problem then is that the same instance (of rendering component) is used for all columns, so if you drag a column all appear pressed ...
Edit: couldn't resist to dig a bit ... Nimbus is soooo ... lacking, to put it mildly ;-)
Turns out that the defaults indeed have the styles for pressed, what's missing is the logic to set it. Probably not entirely trivial, because the logic (aka: MouseListener) resides in BasicTableHeaderUI which doesn't know about subclass' painter states. The only thingy the logic is supporting (hot needle fix) is rollover-awareness, but not pressed-ness.
While we can't hook into the logic (well, we could ... but that's another trick :-) we can look for secondary state changes like draggingColumn/resizingColumn (not-bound) properties in JTableHeader and let a custom renderer update itself as appropriate. Here's a line-out of how-to:
public static class WrappingRenderer implements TableCellRenderer {
private DefaultTableCellHeaderRenderer delegate;
private JTableHeader header;
public WrappingRenderer(JTableHeader header) {
this.header = header;
this.delegate = (DefaultTableCellHeaderRenderer) header.getDefaultRenderer();
header.setDefaultRenderer(this);
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
Component comp = delegate.getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);
TableColumn draggedColumn = table.getTableHeader().getDraggedColumn();
if (draggedColumn != null) {
if (table.convertColumnIndexToModel(column) == draggedColumn.getModelIndex()) {
setNimbusState("Pressed");
} else {
setNimbusState(null);
}
} else {
setNimbusState(null);
}
// do similar for resizing column
return comp;
}
public void setNimbusState(String state) {
delegate.putClientProperty("Nimbus.State", state);
}
}

Related

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;
}
}

How to identify a direct click on a JCheckBox in a JTable?

With a JCheckBox as an Editor in a JTable column, I would like to ignore mouseclicks in the space left and right of a CheckBox in a TableCell.
I have found a discussion from 2011 on the Oracle forum, but the problem was not solved there: https://community.oracle.com/thread/2183210
This is the hack I've realized so far, the interesting part begins atclass CheckBoxEditor:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
/**
* Trying to set the Checkbox only if clicked directly on the box of the CheckBox. And ignore clicks on the
* remaining space of the TableCell.
*
* #author bobndrew
*/
public class JustCheckOnCheckboxTable extends JPanel
{
private static final int CHECK_COL = 1;
private static final Object[][] DATA = { { "One", Boolean.TRUE }, { "Two", Boolean.FALSE },
{ "Three", Boolean.TRUE }, { "Four", Boolean.FALSE }, { "Five", Boolean.TRUE },
{ "Six", Boolean.FALSE }, { "Seven", Boolean.TRUE }, { "Eight", Boolean.FALSE },
{ "Nine", Boolean.TRUE }, { "Ten", Boolean.FALSE } };
private static final String[] COLUMNS = { "Number", "CheckBox" };
private final DataModel dataModel = new DataModel( DATA, COLUMNS );
private final JTable table = new JTable( dataModel );
public JustCheckOnCheckboxTable()
{
super( new BorderLayout() );
this.add( new JScrollPane( table ) );
table.setRowHeight( table.getRowHeight() * 2 );
table.setPreferredScrollableViewportSize( new Dimension( 250, 400 ) );
TableColumn checkboxColumn = table.getColumnModel().getColumn( 1 );
checkboxColumn.setCellEditor( new CheckBoxEditor() );
}
private class DataModel extends DefaultTableModel
{
public DataModel( Object[][] data, Object[] columnNames )
{
super( data, columnNames );
}
#Override
public Class<?> getColumnClass( int columnIndex )
{
if ( columnIndex == 1 )
{
return getValueAt( 0, CHECK_COL ).getClass();
}
return super.getColumnClass( columnIndex );
}
}
class CheckBoxEditor extends DefaultCellEditor
{
private final JCheckBox checkBox;
public CheckBoxEditor()
{
super( new JCheckBox() );
checkBox = (JCheckBox) getComponent();
checkBox.setHorizontalAlignment( JCheckBox.CENTER );
System.out.println( "the checkbox has no size: " + checkBox.getSize() );
}
#Override
public boolean shouldSelectCell( final EventObject anEvent )
{
System.out.println( "\nthe checkbox fills the TableCell: " + checkBox.getSize() );
//Throws NullPointerException: System.out.println( checkBox.getIcon().getIconWidth() );
System.out.println( "always JTable :-( " + anEvent.getSource() );
MouseEvent ev =
SwingUtilities.convertMouseEvent( ((ComponentEvent) anEvent).getComponent(), (MouseEvent) anEvent,
getComponent() );
System.out.println( "Position clicked in TableCell: " + ev.getPoint() );
System.out.println( "always JCheckBox :-( " + getComponent().getComponentAt( ev.getPoint() ) );
Point middleOfTableCell = new Point( checkBox.getWidth() / 2, checkBox.getHeight() / 2 );
System.out.println( "middleOfTableCell: " + middleOfTableCell );
Dimension preferredSizeOfCheckBox = checkBox.getPreferredSize();
int halfWidthOfClickArea = (int) (preferredSizeOfCheckBox.getWidth() / 2);
int halfHeightOfClickArea = (int) (preferredSizeOfCheckBox.getHeight() / 2);
if ( (middleOfTableCell.getX() - halfWidthOfClickArea > ev.getX() || middleOfTableCell.getX() + halfWidthOfClickArea < ev.getX())
|| (middleOfTableCell.getY() - halfHeightOfClickArea > ev.getY() || middleOfTableCell.getY() + halfHeightOfClickArea < ev.getY()) )
{
stopCellEditing();
}
return super.shouldSelectCell( anEvent );
}
}
private static void createAndShowUI()
{
JFrame frame = new JFrame( "Direct click on CheckBox" );
frame.add( new JustCheckOnCheckboxTable() );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main( String[] args )
{
java.awt.EventQueue.invokeLater( new Runnable()
{
#Override
public void run()
{
createAndShowUI();
}
} );
}
}
What I like about this solution:
all TableCell behaviour is correct: selecting, MouseOver, EditModes, ...
What I don't like about it:
the hardcoded size of the JCheckBox (int halfWidthOfClickArea)
where can I get the dimensions of an unpainted component?
Or are there better ways to achieve this Table and CheckBox-behaviour?
EDIT:
I changed the sourcecode following the advice of camickr and added a vertical hitzone for tables with higher RowHeights.
But so far I forgot to mention the main reason for my question... ;-)
I'm calling stopCellEditing() in the method shouldSelectCell(..).
Is it ok to decide there about more than the Cell-Selection?
where can I get the dimensions of an unpainted component?
System.out.println(checkBox.getPreferredSize() );
I think that stopping the selection of the actual in the shouldSelectCell() method is kind of a roundabout method of doing this, and converting mouse events seems weird.
Instead, a much cleaner approach would be to make the checkbox not fill the entire cell, so that it only gets selected if you press directly on the "checkbox" part of it.
This behavior can be accomplished by putting your JCheckbox inside a JPanel and center it without stretching it. To do this, we can make the JPanel's layout manager a GridBagLayout. See how when using GridBagLayout, the inner content doesn't stretch:
(from this StackOverflow answer)
So now, if you click in the empty space surrounding it, you will be clicking on a JPanel, so you won't be changing the JCheckBox's value.
The code for your CheckBoxEditor turns out like this in the end:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
componentPanel.setOpaque(false);
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return componentPanel;
}
}
(Note that you can't be extending from a DefaultCellEditor anymore - in the code above, you're now having to extend from an AbstractCellEditor and implement a TableCellEditor).
I think that this version of your CheckBoxEditor does what you want - if you click in the empty space around the check box, nothing happens. The check box only becomes check if you click directly on it.
Also, by using a JPanel, you don't have to do any MouseEvent calculations and (to me, at least), the code looks much cleaner and it's easier to see what's going on.
EDIT1:
I read your comment and I found a solution: Why not leave the editor as it is, but then just make a cell renderer that derives from the DefaultTableCellRenderer? Then, in your CheckBoxEditor use the same borders and backgrounds as the renderer.
This should achieve the effect you want (I've moved common code into outer class methods so I don't have to repeat them):
private static void setCheckboxValue(JCheckBox checkBox, Object value) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
}
private static void copyAppearanceFrom(JPanel to, Component from) {
if (from != null) {
to.setOpaque(true);
to.setBackground(from.getBackground());
if (from instanceof JComponent) {
to.setBorder(((JComponent) from).getBorder());
}
} else {
to.setOpaque(false);
}
}
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
setCheckboxValue(checkBox, value);
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component c = renderer.getTableCellRendererComponent(table, value, true, true, row, column);
copyAppearanceFrom(componentPanel, c);
return componentPanel;
}
}
class CheckBoxRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxRenderer() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setCheckboxValue(checkBox, value);
copyAppearanceFrom(componentPanel, this);
return componentPanel;
}
}
Then, you have to set the renderer along with the editor in your constructor:
checkboxColumn.setCellEditor(new CheckBoxEditor());
checkboxColumn.setCellRenderer(new CheckBoxRenderer());
Here's a couple of screenshots comparing the two methods:
Your original method: JPanel and JCheckBox method:
I can barely see a difference :)
IMHO, I still think that just using the plain Java table API's is cleaner than calculating checks based on mouse pointer positions, but the choice is up to you.
I hope this helped!
EDIT2:
Also, if you want to be able to toggle using the spacebar I think that you can just add a key binding to the componentPanel in the CheckBoxEditor constructor:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
// ...
public CheckBoxEditor() {
// ...
componentPanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "spacePressed");
componentPanel.getActionMap().put("spacePressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
checkBox.setSelected(!checkBox.isSelected());
}
});
// ...
}
// ...
}
I'm not sure if you can use Drag-and-Drop with boolean values. I tried dragging "true" onto the checkboxes in the original version, but nothing happened, so I don't think you have to worry about DnD.
Your code has a bug. Try pressing a cell editor's checkbox, drag outside of the cell and release the mouse button. Then click somewhere OUTSIDE the checkbox. The cell is being edited. I guess it means shouldSelectCell is not the right solution for you.
I think you should consider disabling the cell editors for the entire column, and implementing a custom MouseAdapter on th JTable to calculate the checkbox's location and change the model itself.

JTable with cell flashing

I am writing an application using the Swing library in Java. I have a table component that extends JTable, and in this component I have overridden the method getTableCellRendererComponent, because I color the cells of the table. I have a custom table model (that extends from the default table model), and the table component itself I have added to a JPanel. All this works.
Now I would like to add to this table, some functionality to have a cell flashing. Potentially, more than one cell can be flashing at a time, i.e. cells at (row 1, column 2) and (row 3, column 4).
Is this possible to do? Any hints that could get me started would be appreciated.
I find one article for your answer:
http://www.devx.com/DevX/10MinuteSolution/17167/0/page/1
The page provide the source code for downloading.
Basically it use following method to notify table to update the cell timely.
JTable.tableChanged(new TableModelEvent(table.getModel(), firstRow, lastRow, column));
After reading his code, I sort out a simpler version of his code, you can change my code or use his code (more elegant but also more complex).
public class FlashCellTable
{
public static Color color;
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(800, 600);
final JTable table = new JTable(4, 4);
table.setDefaultRenderer(Object.class, new MyFlashingCellRenderer());
table.setValueAt("Flashing", 0, 0);
frame.getContentPane().add(new JScrollPane(table));
final long startTime = System.currentTimeMillis();
Thread thread = new Thread()
{
#Override
public void run()
{
while (true)
{
long now = System.currentTimeMillis();
long second = (now - startTime) / 1000;
color = second / 2 * 2 == second ? Color.red : Color.blue;
System.out.println("FlashCellTable.run");
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
table.tableChanged(new TableModelEvent(table.getModel(), 0, 0, 0));
}
});
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
};
thread.start();
frame.setVisible(true);
}
public static class MyFlashingCellRenderer extends DefaultTableCellRenderer
{
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
JLabel label =
(JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if ("Flashing".equals(value))
{
label.setBackground(color);
}
else
{
label.setBackground(Color.white);
}
return label;
}
}
}

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