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
Related
I am using Swing to create a game that has a 10 by 10 grid of cells. The color of each cell can be changed by a mouse click. Here are two classes that work together to accomplish this:
public class Grid extends Battle {
public Grid(String name) {
super();
}
#Override
protected JPanel getCell()
{
JPanel panel = new JPanel();
panel.setBackground(Color.black);
panel.setBorder(BorderFactory.createLineBorder(Color.blue, 1));
panel.setPreferredSize(new Dimension(20, 20));
panel.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e)
{
panel.setBackground(Color.green);
}
});
return panel;
}
}
public abstract class Battle extends JPanel {
public BattleGrid() {
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel self = new JPanel();
self.setLayout(new GridLayout(0,10));
for (int i = 0; i < 10; i++)
{
for(int j =0; j < 10; j++) {
JPanel panel = getCell();
self.add(panel);
}
}
this.add(self);
}
protected abstract JPanel getCell();
}
The code works for any one particular cell. My question is, how can I change the color of multiple cells in the grid with ONE mouse click? For example, when you click on the grid you change the color of two cells: the one you click on AND the one that is, say, immediately to the right of it? Thank you in advance!
Edit: for those who run into a similar problem - I found super similar solution. Simply increase the dimension of the JPanel being clicked on and return in. For example, the dimension of my JPanel is 20 by 20. So if you want to color 2 cells with one click - the one being clicked and the one to the right - all you have to do is:
panel.setSize(new Dimension(40,20));
You need to start by decoupling the state management from the rest of the code. This should be maintained in some kind of model, which has no concept of the UI, nor should it care, it simply managers the state and makes appropriate decisions about what should be done as that state is changed.
The model would provide an observer pattern implementation, which allow it to generate events when the state is changed, allowing interested parties know when the state has changed, so they can respond to it.
public enum CellState {
EMPTY, SELECTED
}
public class GridModel {
private Set<ChangeListener> listeners;
private CellState[][] grid;
public GridModel() {
listeners = new HashSet<>(25);
grid = new CellState[10][10];
for (int col = 0; col < grid.length; col++) {
for (int row = 0; row < grid[col].length; row++) {
grid[col][row] = CellState.EMPTY;
}
}
}
public void setCellState(CellState state, int x, int y) {
grid[x][y] = state;
}
public CellState getCellStateAt(int x, int y) {
return grid[x][y];
}
public void addChangeListener(ChangeListener listener) {
listeners.add(listener);
}
public void removeChangeListener(ChangeListener listener) {
listeners.remove(listener);
}
protected void fireStateChanged() {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : listeners) {
listener.stateChanged(evt);
}
}
}
Next, I'd create a dedicated Cell component which would be responsible for the management of a single cell. This would provide a visual representation of a cell within the model and coordinate interaction between the user and the model.
public class Cell extends JPanel {
private GridModel model;
public Cell(GridModel model, int x, int y) {
this.model = model;
setBackground(Color.black);
setBorder(BorderFactory.createLineBorder(Color.blue, 1));
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
getModel().setCellState(CellState.SELECTED, x, y);
}
});
model.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
switch (getModel().getCellStateAt(x, y)) {
case SELECTED:
setBackground(Color.BLUE);
break;
case EMPTY:
setBackground(Color.BLACK);
break;
}
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(20, 20);
}
public GridModel getModel() {
return model;
}
}
And then, your Grid class would create an instance of this Cell as required
public class Grid extends Battle {
private GridModel model;
public Grid(String name, GridModel model) {
super();
this.model = model;
}
public GridModel getModel() {
return model;
}
#Override
protected JPanel getCell(int x, int y) {
return new Cell(getModel(), x, y);
}
}
This is only a proof of concept and your requirements may be more complex then I've generally presented, by the basic concept of decoupled state manager (model) and notification system (observer pattern) and the key elements to your solution.
This moves you closer to a Model-View-Controller paradigm, allow appropriate separation of various responsibilities with the system
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;
}
}
I want to add a hovering-effect to my customized Swing.JButton similar to the icon on my Chrome Browser:
Before hover >>
After hover >>
I am able to set the button in the "before" status when it is created, but I am not able to create the "border + raised-background" when it is hovered. When I try to re-add the border to the button, I got a moving effect as after repainting a new border is inserted.
This is my current code:
public class MyButton extends JButton implements MouseListener {
public MyButton(String iconPath, String toolTip) {
super(new ImageIcon(TipButton.class.getResource(iconPath)));
addMouseListener(this);
setBorder(null);
setBorderPainted(false);
setFocusPainted(false);
setOpaque(false);
setContentAreaFilled(false);
setToolTipText(toolTip);
}
public MyButton(String iconPath, String name, String toolTip) {
this(observers, iconPath, toolTip);
setText(name);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
if (e.getSource() != this) return;
setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
}
#Override
public void mouseExited(MouseEvent e) {
if (e.getSource() != this) return;
setBorder(null);
}
}
I suppose the main logic should be in the methods mouseEntered/mouseExited but I don't know how to get the wanted effect. Any idea?
I think I have found a solution. Using EmptyBorder with the same sizes (insets) of a raised border does the trick. Code:
public class SwingUtils {
public static JButton createMyButton (String iconPath, String toolTip) {
final JButton b = new JButton (new ImageIcon(SwingUtils.class.getResource(iconPath)));
final Border raisedBevelBorder = BorderFactory.createRaisedBevelBorder();
final Insets insets = raisedBevelBorder.getBorderInsets(b);
final EmptyBorder emptyBorder = new EmptyBorder(insets);
b.setBorder(emptyBorder);
b.setFocusPainted(false);
b.setOpaque(false);
b.setContentAreaFilled(false);
b.setToolTipText(toolTip);
b.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
if (model.isRollover()) {
b.setBorder(raisedBevelBorder);
} else {
b.setBorder(emptyBorder);
}
}
});
return b;
}
}
Note: as mKorbel says it would be using ChangeListener and the button created in a factory method instead of subclass JButton.
Use different images for every state. You can set a different Icon for selected, disabled selected, disabled, pressed, rollover, rolloverEnabled, rolloverSelected. More info here.
In java 7 there is a BevelBorder class that looks to be what you are looking for. The 2 methods you will probably be interested in are
paintRaisedBevel(Component c, Graphics g, int x, int y, int width, int height)
and
paintLoweredBevel(Component c, Graphics g, int x, int y, int width, int height)
Here is the documentation on the class: http://docs.oracle.com/javase/7/docs/api/javax/swing/border/BevelBorder.html
there (maybe) no reason to create extends JButton implements MouseListener {, there aren't overroaded any of JButtons methods, and use composition with returns instead,
better could be to create a local variable and to use methods implemented in API
don't to use MouseListener for Buttons Components, all these events are implemented in JButtons API and correctly
use set(Xxx)Icon for JButton
easiest of ways is to use JButton.getModel from ChangeListener, for example
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'.
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);
}
}