I am having a bug I'm having trouble tracking down and was wondering if anyone had seen anything like this. I am building my code in Java 8. I run/test this code on both Macintosh & Windows. This bug only happens on a machine running Windows OS. (Haven't tried linux yet) I am testing on Windows 8.
I have a JComboBox that is in the same window as a JTable and a JTree which I have modified to add checkboxes. The JComboBox initially works perfectly. Popups up and works correctly. But once I interact with the JTable or JTree it no longer functions. It does not popup anymore.
When this pane is further added to a JTabbedPane, I can get it functioning again when tab out of this pane & back into the one with the JComboBox. In an attempt to go around this bug (and understand it better) I tried using a JPopupMenu instead of the JComboBox. The bug happens with the JPopup as well and it's easier to tell where it fails. When I debug I find that I do enter the mousePressed event and everything looks good in the mousePressed event. But the JPopupMenu.show() function doesn't get executed correctly.
#Override
public void mousePressed(MouseEvent e) {
topDirPopup.show(e.getComponent(), e.getX(), e.getY());
}
I could show more code but I'm not sure what code is interacting with the popup.
Any ideas? Any work arounds?
Isolated it down to it's going wrong in the checkboxes in the JTable (or the JTree) but I'm using standard Swing Table so it's easier to isolate. Very basic code.. doesn't read anything from the file system. Doesn't do any modifications to swing objects. Still see the bug.
I stripped most everything that is useless out of my code.
Main Test Class.
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Test extends JPanel {
Test() {
super(new BorderLayout());
FileSelectionPane fpane = new FileSelectionPane();
JLabel label = new JLabel("File Selection Pane Test");
this.add(fpane, BorderLayout.CENTER);
this.add(label, BorderLayout.NORTH);
}
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("File Selection Test Tool");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add content to the window.
frame.add(new Test());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event dispatch thread:
//creating and showing this application's GUI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Turn off metal's use of bold fonts
UIManager.put("swing.boldMetal", Boolean.FALSE);
createAndShowGUI();
}
});
}
}
My FileTool Pane. Yes I have a BorderLayout within a BorderLayout. It's silly in this example but my actual code is more complex and I have a Tabbed Pane in between these two.
import java.awt.BorderLayout;
import java.awt.Panel;
import javax.swing.JComboBox;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
public class FileSelectionPane extends Panel {
/**
*
*/
private static final long serialVersionUID = 1784004323821479260L;
/**
*
*/
JTable fileTable;
JComboBox<String> topDirComboBox;
FileSelectionPane() {
super(new BorderLayout());
createTopDirectoryComboBox();
FileSelectionTableModel tModel = new FileSelectionTableModel();
fileTable = new JTable(tModel);
JScrollPane jScrollPane2 = new JScrollPane(fileTable);
this.add(jScrollPane2, BorderLayout.CENTER);
this.add(topDirComboBox, BorderLayout.NORTH);
}
void createTopDirectoryComboBox() {
topDirComboBox = new JComboBox<String>();
topDirComboBox.addItem("C:\\Test\\Fake");
topDirComboBox.addItem("C:\\Test\\More");
topDirComboBox.addItem("C:\\Test\\View");
}
public class FileSelectionTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
static final int numCols = 4;
private String[] columnNames = {"..", "Name", "Type", "Size", "Creation Date", "Modification Date"};
private final static int checkCol = 0;
private final static int nameCol = 1;
private final static int typeCol = 2;
private final static int sizeCol = 3;
public int getColumnCount() { return numCols; }
public int getRowCount() { return 5; }
public String getColumnName(int col) {
return columnNames[col];
}
// Only the check item is editable.
public boolean isCellEditable(int row, int col)
{
return (col == checkCol) ? true : false;
}
Boolean tBool[] = new Boolean[10];
public void setValueAt(Object value, int row, int col) {
if (col == checkCol)
tBool[row] = (Boolean) value;
}
public Object getValueAt(int row, int col) {
Object rtnObject = null;
switch (col) {
case checkCol:
rtnObject = (Object) tBool[row];
break;
case nameCol:
rtnObject = (Object) "Testing";
break;
case sizeCol:
rtnObject = (Object) "5";
break;
}
return rtnObject;
}
/* Set up the types of each column */
public Class<?> getColumnClass(int col) {
// getValueAt(0, c).getClass();
Class<?> rtnValue = null;
switch (col)
{
case checkCol:
rtnValue = Boolean.class;
break;
case nameCol:
rtnValue = String.class;
break;
case sizeCol:
rtnValue = String.class;
break;
case typeCol:
rtnValue = String.class;
break;
}
return rtnValue;
}
}
}
Related
I have JTable with checkboxes (because getColumnClass is Boolean).
User selects several rows, and clicks space but only "lead selection" checkbox gets KeyEvent and becomes checked.
How can I check all selected checkboxes?
It looks like a very simple task, but I can't find "official" way to do that, and I do not want to use hacks.
I can override processKeyEvent in my table and physically set true to all values in model, but it doesn't look good.
I followed #camickr and #Jean Willian S. J 's suggestion, and did:
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
public class TableTest {
private static final int CHECKBOX_COL = 0;
public static void main(String[] args) throws InvocationTargetException, InterruptedException {
SwingUtilities.invokeAndWait(() -> {
var frame = new JFrame("TableTest");
Object[][] data = {new Object[]{true, "Foo"}, new Object[]{false, "Buz"}};
MyModel dm = new MyModel(data, new Object[]{"Enabled", "Name"});
var table = new JTable(dm);
var invertSelected = "InvertSelected";
table.getInputMap().remove(KeyStroke.getKeyStroke("SPACE"));
table.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), invertSelected);
table.getActionMap().put(invertSelected, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
TableModel model = table.getModel();
for (int selectedRow : table.getSelectedRows()) {
var row = table.convertRowIndexToModel(selectedRow);
var value = !(Boolean) model.getValueAt(row, CHECKBOX_COL);
model.setValueAt(value, row, CHECKBOX_COL);
}
}
});
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
frame.getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
});
}
private static class MyModel extends DefaultTableModel {
public MyModel(Object[][] data, Object[] columnNames) {
super(data, columnNames);
}
#Override
public boolean isCellEditable(int row, int column) {
return column == CHECKBOX_COL;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == CHECKBOX_COL ? Boolean.class : String.class);
}
}
}
It now works. Thank you, everyone
As simple as Renderers and Editors sound and despite the dozen or so SO bookmarks I return to regarding similar issues I’m missing something elementary. I want to drag any old text file into a 2-column JTable, have the first column display the filename and the second contain a JComboBox whose options depend on the contents of the dragged file. (In the code below I just fake a few entries.)
This all works fine until I make a selection from a combo box - the selection doesn’t display - just a combo box, populated correctly but no selection made. I know it must have something to do with my misuse of renderers/editors but after at least two weeks of flailing I’m seeking professional help. And if you think I’ve totally missed the boat on how renderers and editors are written, well, I’m glad you didn’t see my earlier attempts.
Hopefully this code qualifies as an SSCCE - sincere apologies if I’ve included something I shouldn’t have. I’ve retained the DnD stuff just in case it has some significance.
For what it’s worth, I use a static list of ComboBoxModels (one per row) since each JComboBox contains different options, and likewise TableCellEditors (although I don’t know if that’s the right way to go about it).
To run this just drag any file into the table that appears and then make a selection from the JComboBox in the right column and watch it ignore you. Thanks very much, even if you have some advice without taking the trouble of running this.
Java 1.7/OS X 10.9.5/Eclipse Mars.2
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class Main extends JFrame {
static List<AComboBoxModel> priceComboModels = new ArrayList<AComboBoxModel>();
static List<DefaultCellEditor> editors = new ArrayList<DefaultCellEditor>();
public Main() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 400));
JPanel panel = new JPanel(new BorderLayout());
JTable table = new JTable(0, 2) {
public TableCellEditor getCellEditor(int rinx, int cinx) {
if (cinx == 0) {
return super.getCellEditor(rinx, cinx);
}
return editors.get(rinx);
}
};
table.setPreferredScrollableViewportSize(new Dimension(360, 80));
table.setTransferHandler(new ATransferHandler());
table.setModel(new ATableModel());
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setHeaderValue("File Name");
tcm.getColumn(1).setHeaderValue("Selection");
TableColumn column = tcm.getColumn(1);
column.setCellRenderer(new ACellRenderer());
column.setCellEditor(new ACellEditor());
table.setDragEnabled(true);
table.setFillsViewportHeight(true);
JScrollPane sp = new JScrollPane(
table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
panel.add(sp, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(200, 300));
add(panel, BorderLayout.CENTER);
pack();
}
public static int addComboModel(AComboBoxModel model) {
priceComboModels.add(model);
return priceComboModels.size() - 1;
}
public static AComboBoxModel getComboModelAt(int inx) {
return priceComboModels.get(inx);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main().setVisible(true);
}
});
}
}
class ATableModel extends DefaultTableModel {
List<ARecord> data = new ArrayList<ARecord>();
public void addRow(ARecord row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
#Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
#Override
public int getColumnCount() {
return 2;
}
public void setValueAt(Object value, int rinx, int cinx) {
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
row.setFilename((String) value);
break;
case 1:
row.setCbox((JComboBox) value);
break;
}
}
#Override
public Object getValueAt(int rinx, int cinx) {
Object returnValue = null;
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
returnValue = row.getFilename();
break;
case 1:
returnValue = row.getCbox();
break;
}
return returnValue;
}
// I assume this is unnecessary since column 1 defaults to text
// and column 2 is handled by ACellRenderer. I think.
// #Override
// public Class getColumnClass(int cinx) {
// return cinx == 0 ? String.class : JComboBox.class;
// }
}
//////////////////////////////////////////////////////////////////////////////////
// This class handles the drag and drop.
class ATransferHandler extends TransferHandler {
int getSourceActions(JList<String> lst) {
return TransferHandler.COPY;
}
Transferable createTransferable(JList<String> list) {
return null;
}
void exportDone(JList<String> lst, Transferable data, int action) {
}
public boolean canImport(TransferHandler.TransferSupport info) {
return true;
}
//////////////////////////////////////////////////////////////////////////
// This is the method of interest where the dropped text file is handled.
//////////////////////////////////////////////////////////////////////////
public boolean importData(TransferHandler.TransferSupport info) {
if (! info.isDrop()) return false;
JTable table = (JTable)info.getComponent();
Transferable tr = info.getTransferable();
List<File> files = null;
try {
files = (List<File>)tr.getTransferData(DataFlavor.javaFileListFlavor);
} catch(UnsupportedFlavorException | IOException e) {
}
ATableModel tm = (ATableModel)table.getModel();
String[] options;
// For each dropped text file...
for (File fl : files) {
String fname = fl.getName();
// Just fill the JComboBox with some unique options for now
// (in practice this comes from the dropped text file contents).
String dummyText = fname.substring(0, 5);
options = new String[] { dummyText + "_A", dummyText + "_B", dummyText + "_C" };
// Create a ComboBoxModel for this JComboBox containing the selection options.
AComboBoxModel cboModel = new AComboBoxModel(options);
// Create the combo box itself.
JComboBox<String> cbox = new JComboBox<String>();
// Assign the model to the box.
cbox.setModel(cboModel);
// Create and add to the editor list the table cell editor.
Main.editors.add(new DefaultCellEditor(cbox));
// Also add the ComboBoxModel to the model list.
Main.addComboModel(cboModel);
// Add the row to the model data.
tm.addRow(new ARecord(fname, cbox));
}
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
class ARecord {
String filename;
JComboBox cbox;
// Just a bean to describe a table row (a filename and a JComboBox).
public ARecord(String filename, JComboBox cbox) {
super();
this.filename = filename;
this.cbox = cbox;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public JComboBox getCbox() {
return cbox;
}
public void setCbox(JComboBox cbox) {
this.cbox = cbox;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// This is the model for the JComboBoxes. A different model is instantiated
// for each row since each one has different contents.
class AComboBoxModel implements MutableComboBoxModel {
List<String> items = new ArrayList<String>();
public AComboBoxModel(String[] items) {
this.items = Arrays.asList(items);
}
#Override
public int getSize() {
return items.size();
}
#Override
public Object getElementAt(int index) {
return items.get(index);
}
#Override
public void addListDataListener(ListDataListener l) {
}
#Override
public void removeListDataListener(ListDataListener l) {
}
#Override
public void setSelectedItem(Object anItem) {
}
#Override
public Object getSelectedItem() {
return null;
}
#Override
public void addElement(Object item) {
}
#Override
public void removeElement(Object obj) {
}
#Override
public void insertElementAt(Object item, int index) {
}
#Override
public void removeElementAt(int index) {
}
}
//////////////////////////////////////////////////////////////////////////////////////
// I won't pretend that I'm confident as to how this should work. My guess is that
// I should just retrieve the appropriate ComboBoxModel, assign it and return.
class ACellRenderer extends JComboBox implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rinx, int cinx) {
setModel(Main.getComboModelAt(rinx));
return this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
class ACellEditor extends AbstractCellEditor implements TableCellEditor {
static JComboBox box = null;
// This is where I think I'm actually lost. I don't understand the significance of
// returning a JComboBox when one was already created when the text file was
// dropped. Is it correct to just assign the appropriate ComboBoxModel to a JComboBox
// and return it here?
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int rinx,
int cinx) {
box = (JComboBox)(table.getModel().getValueAt(rinx, cinx));
box.setModel(Main.getComboModelAt(rinx));
return box;
}
#Override
public Object getCellEditorValue() {
return box;
}
}
make a selection from the JComboBox in the right column and watch it ignore you
Something is wrong with your custom editor and I'm not sure what. You have a big problem in that you are trying to use a JComboBox as the data of the editor. This is completely wrong.
But the good new is that there is no need for you to use a custom renderer or a custom editor.
You should NOT be storing a JComboBox in the TableModel. You simply store the String of the selected item from the combo box. (This will be done for you automatically by the default combo box editor).
There is no need for you to create a new editor for every file that is dragged to the table.
the second contain a JComboBox whose options depend on the contents of the dragged file
So the only part of the table that you need to customize is the getCellEditor(...) method.
I would guess you would have a different editor for a given file extension.
So the basic code might be something like:
int modelColumn = convertColumnIndexToModel( column );
if (modelColumn == 1)
{
String file = getModel.getValueAt(row, 0);
if (file.endsWith(".txt"))
return txtEditor;
else if (file.endsWith(".html"))
return htmlEditor;
}
return super.getCellEditor(row, column);
Check out:
How to add unique JComboBoxes to a column in a JTable (Java) for a working example. The logic in that posting does have a separate editor by row for demonstration purposes only. The example demonstrates that the code works with the default renderers and editors. All you need to do is provide the items for each combo box editor.
In your case the editor will be based on the file type so the logic needs to test the data in the first column.
Note: the nested if/else statement is not a good solution. You might want to use a Hashmap of filetype/editor. Then the getCellEditor(...) method would just be a Hashmap lookup once you extract the filetype for the File.
So your dragging code should have nothing to do with the editors of the table. You need to know before hand which file types you want to support and define the valid items for each of those file types.
Also, your TableModel should NOT extend DefaultTableModel. You are providing your own data storage and implementing all the methods so you should just be extending the AbstractTableModel.
In the following code, I create a jtable with a custom cell editor for the first column and then add undo capabilities to the table. When you run the program, the program allows you to change the values in the first column (test by appending a "d" and then an "e" to the "abc" already there). Now enter control-z (undo) and enter control-z again. It works as expected. But now enter control-z (undo) again. This time the "abc" is erased. It looks like the swing system is setting the initial value of the column and creating an undo event for that action which the user can then undo. My question - how do I write my code so that the user only can undo the actions the user makes?
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
public class UndoExample extends JFrame {
private static final long serialVersionUID = 1L;;
static Boolean objEnableUndoRedoActions = true;
UndoExample rootFrame;
public UndoExample() {
// This procedure starts the whole thing off.
//Create table
final String[] tableColumns = {"Column 1", "Column 2"};
JTable tabUndoExample = new JTable(
new DefaultTableModel(null, tableColumns) {
private static final long serialVersionUID = 1L;
});
final DefaultTableModel tabUndoExampleModel = (DefaultTableModel) tabUndoExample
.getModel();
tabUndoExampleModel.addRow(new Object[]{"abc", true});
tabUndoExampleModel.addRow(new Object[]{"zyw", false});
// Create the undo/redo manager
UndoManager objUndoManager = new UndoManager();
// Create a cell editor
JTextField tfTabField = new JTextField();
TableCellEditor objEditor = new DefaultCellEditor(tfTabField);
// Make the cell editor the default editor for this table's first column
tabUndoExample.getColumnModel().getColumn(0)
.setCellEditor(objEditor);
// Create the undo action on the field's document for the column
tfTabField.getDocument().addUndoableEditListener(
new uelUndoRedoTableCellField(objUndoManager, tabUndoExample));
// Allow undo and redo to be entered by the user
UndoRedoSetKeys(this, "Example", objUndoManager);
tabUndoExample.setInheritsPopupMenu(true);
//Add the table to the frame and show the frame
this.add(tabUndoExample);
this.pack();
setLocationRelativeTo(null);
}
public static void main(final String[] args) {
// Launches the application. This is required syntax.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
final UndoExample rootFrame = new UndoExample();
rootFrame.setVisible(true);
} catch (final Exception e) {
}
}
});
}
#SuppressWarnings("serial")
static class aueUndoRedoTableCellField extends AbstractUndoableEdit {
// Wrap the text edit action item as we need to add the table
// row and column information. This code is invoked when the
// code sees an undo event created and then later when the
// user requests an undo/redo.
JTable objTable = null;
UndoableEdit objUndoableEdit;
int objCol = -1;
int objRow = -1;
public aueUndoRedoTableCellField(UndoableEdit undoableEdit,
JTable table, int row, int col) {
super();
objUndoableEdit = undoableEdit;
objTable = table;
objCol = col;
objRow = row;
}
public void redo() throws CannotRedoException {
// When the user enters redo (or undo), this code sets
// that we are doing an redo (or undo), sets the cursor
// to the right location, and then does the undo (or redo)
// to the table cell.
UndoRedoManagerSetEnabled(false);
super.redo();
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.redo();
UndoRedoManagerSetEnabled(true);
}
public void undo() throws CannotUndoException {
super.undo();
UndoRedoManagerSetEnabled(false);
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.undo();
UndoRedoManagerSetEnabled(true);
}
}
static class aUndoRedo extends AbstractAction {
// This code is bound to the undo/redo keystrokes and tells
// Java what commands to run when the keys are later entered
// by the user.
private static final long serialVersionUID = 1L;
Boolean objUndo = true;
UndoManager objUndoManager = null;
final String objLocation;
public aUndoRedo(Boolean Undo, UndoManager undoManager, String location) {
super();
objUndo = Undo;
objUndoManager = undoManager;
objLocation = location;
}
#Override
public void actionPerformed(ActionEvent ae) {
try {
// See if operation allowed
if (!objUndoManager.canUndo() && objUndo
|| !objUndoManager.canRedo() && !objUndo)
return;
UndoRedoManagerSetEnabled(false);
if (objUndo) {
objUndoManager.undo();
} else {
objUndoManager.redo();
}
UndoRedoManagerSetEnabled(true);
// Catch errors and let user know
} catch (Exception e) {
UndoRedoManagerSetEnabled(true);
}
}
}
static class uelUndoRedoTableCellField implements UndoableEditListener {
// This action is called when the user changes the table's
// text cell. It saves the change for later undo/redo.
private UndoManager objUndoManager = null;
private JTable objTable = null;
public uelUndoRedoTableCellField(UndoManager undoManager,
JTable table) {
objUndoManager = undoManager;
objTable = table;
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
// Remember the edit but only if the code isn't doing
// an undo or redo currently.
if (UndoRedoManagerIsEnabled()) {
objUndoManager.addEdit(new aueUndoRedoTableCellField(e
.getEdit(), objTable, objTable.getSelectedRow(),
objTable.getSelectedColumn()));
}
}
}
static public Boolean UndoRedoManagerIsEnabled() {
// See if we are currently doing an undo/redo.
// Return true if so.
return objEnableUndoRedoActions;
}
static public void UndoRedoManagerSetEnabled(Boolean state) {
// Set the state of whether we are in undo/redo code.
objEnableUndoRedoActions = state;
}
static void UndoRedoSetKeys(JFrame frame, final String location, UndoManager undoManager) {
// Allow undo and redo to be called via these keystrokes for this dialog
final String cntl_y = "CNTL_Y";
final KeyStroke ksCntlY = KeyStroke.getKeyStroke("control Y");
final String cntl_z = "CNTL_Z";
final KeyStroke ksCntlZ = KeyStroke.getKeyStroke("control Z");
JRootPane root = frame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlZ, cntl_z);
root.getActionMap().put(
cntl_z,
new aUndoRedo(true, undoManager, location));
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlY, cntl_y);
root.getActionMap().put(
cntl_y,
new aUndoRedo(false, undoManager, location));
}
}
When you press a key, a series of things occur. The JTable, process the key stroke, it checks to see if the cell is editable (as the TableModel), it then asks the editor for the currently selected cell if the event should edit the cell (CellEditor#isCellEditable(EventObject)).
If this method returns true, the editor is prepared, the value from the TableModel is applied to the editor (ie setText is called), and the editor is added to the JTable, finally, the event which triggered the edit mode is re-dispatched to the editor, in your case the Ctrl+Z, which then triggers and undo event, returning the editor it's initial state (before setText was called).
You can try and use something like...
TableCellEditor objEditor = new DefaultCellEditor(tfTabField) {
#Override
public boolean isCellEditable(EventObject anEvent) {
boolean isEditable = super.isCellEditable(anEvent); //To change body of generated methods, choose Tools | Templates.
if (isEditable && anEvent instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) anEvent;
if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_Z) {
isEditable = false;
}
}
return isEditable;
}
};
to prevent the JTable from been placed into edit when a specific key stroke occurs
Updated
So based on Andrew's answer from JTextArea setText() & UndoManager, I devised a "configurable" UndoableEditListener which can be set to ignore undoable actions, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
public class FixedField {
public static void main(String[] args) {
new FixedField();
}
public FixedField() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class UndoableEditHandler implements UndoableEditListener {
private static final int MASK
= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
private UndoManager undoManager = new UndoManager();
private boolean canUndo = true;
public UndoableEditHandler(JTextField field) {
Document doc = field.getDocument();
doc.addUndoableEditListener(this);
field.getActionMap().put("Undo", new AbstractAction("Undo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canUndo()) {
undoManager.undo();
}
} catch (CannotUndoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, MASK), "Undo");
field.getActionMap().put("Redo", new AbstractAction("Redo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canRedo()) {
undoManager.redo();
}
} catch (CannotRedoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, MASK), "Redo");
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
if (canUndo()) {
undoManager.addEdit(e.getEdit());
}
}
public void setCanUndo(boolean canUndo) {
this.canUndo = canUndo;
}
public boolean canUndo() {
return canUndo;
}
}
public class TestPane extends JPanel {
public TestPane() {
JTextField field = new JTextField(10);
UndoableEditHandler handler = new UndoableEditHandler(field);
handler.setCanUndo(false);
field.setText("Help");
handler.setCanUndo(true);
add(field);
}
}
}
Now, you're going to have to devices your own TableCellEditor to support this, for example...
public static class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField editor;
private UndoableEditHandler undoableEditHandler;
public MyCellEditor(JTextField editor) {
this.editor = editor;
undoableEditHandler = new UndoableEditHandler(editor);
editor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
undoableEditHandler.setCanUndo(false);
editor.setText(value == null ? null : value.toString());
undoableEditHandler.setCanUndo(true);
return editor;
}
}
I am using a JTable to visualize some data. One column ist destined to show boolean data through a checkbox. I achieved this by returning Boolean.class from my overriden getColumnClass() function in my table model.
Unfortunately this results in a cell with a checkbox but without a background color appropriate for the current row.
I fixed this by using the answer from this post: JTable - Boolean Cell Type - Background
Now I was trying to increase the contrast of the alternate rows. I achieved this by setting the appropriate property of the Nimbus LAF, which I am using.
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
defaults.put("Table.alternateRowColor", new Color(217, 217, 217));
As you see, the background of the Boolean cells is still the old Nimbus Table.alternateRowColor color.
Is there a way to change this? Am I doing this completely wrong? Is there a better way to achieve alternating background color and more contrast?
EDIT
caused on
java version "1.7.0_17" Java(TM) SE Runtime Environment (build
1.7.0_17-b02) Java HotSpot(TM) Server VM (build 23.7-b01, mixed mode), OS is Ubuntu 12.04
I was (finally) able to get it to work. The secret was to change the defaults BEFORE you create anything.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class TestTable10 {
public static void main(String[] args) {
new TestTable10();
}
public TestTable10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
UIManager.getLookAndFeelDefaults().put("Table.alternateRowColor", Color.RED);
JTable table = new JTable(new MyModel());
((JComponent) table.getDefaultRenderer(Boolean.class)).setOpaque(true);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyModel extends AbstractTableModel {
#Override
public int getRowCount() {
return 10;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case 0:
return "Hello";
case 1:
return true;
}
return "?";
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? String.class : Boolean.class;
}
}
}
I'd be to use standard Renderer concept for this job, instead of playing with Nimbus Constants
Renderer works for Nimbus, override all Colors, excluding JTableHeader
code based on #camickrs Table Row Rendering
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableRowRenderingTip extends JPanel {
private static final long serialVersionUID = 1L;
public TableRowRenderingTip() {
Object[] columnNames = {"Type", "Company", "Shares", "Price", "Boolean"};
Object[][] data = {
{"Buy", "IBM", new Integer(1000), new Double(80.5), Boolean.TRUE},
{"Sell", "Dell", new Integer(2000), new Double(6.25), Boolean.FALSE},
{"Short Sell", "Apple", new Integer(3000), new Double(7.35), Boolean.TRUE},
{"Buy", "MicroSoft", new Integer(4000), new Double(27.50), Boolean.FALSE},
{"Short Sell", "Cisco", new Integer(5000), new Double(20), Boolean.TRUE}
};
DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
#Override
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Alternating", createAlternating(model));
add(tabbedPane);
}
private JComponent createAlternating(DefaultTableModel model) {
JTable table = new JTable(model) {
private static final long serialVersionUID = 1L;
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (!isRowSelected(row)) { // Alternate row color
c.setBackground(row % 2 == 0 ? getBackground() : Color.orange);
}
return c;
}
};
table.setPreferredScrollableViewportSize(table.getPreferredSize());
((JComponent) table.getDefaultRenderer(Boolean.class)).setOpaque(true);
return new JScrollPane(table);
}
public static void main(String[] args) {
try {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame.setDefaultLookAndFeelDecorated(false);
JFrame frame = new JFrame("Table Row Rendering");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TableRowRenderingTip());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
to solve this problem I used jxtable() instead jtable() and I used own prepareRenderer for row colors (you can use mKorbel's one, to put it to table in netbeans just Customize code for jxtable() component), because this solution: JTable - Boolean Cell Type - Background does not work for multi color rows for me. My platform: Windows 7 32bit, java version "1.7.0_21",
Java(TM) SE Runtime Environment (build 1.7.0_21-b11),
Java HotSpot(TM) Client VM (build 23.21-b01, mixed mode, sharing), netbeans IDE 7.3
Here is png (dont have enought reputation :D): jxtable().
Immediately after setting Nimbus L&F, add this lines:
UIManager.getLookAndFeelDefaults().put("Table:\"Table.cellRenderer\".background", Color.DARK_GRAY);
UIManager.getLookAndFeelDefaults().put("Table.background",new ColorUIResource(Color.DARK_GRAY));
UIManager.getLookAndFeelDefaults().put("Table.alternateRowColor",Color.DARK_GRAY.brighter());
Note usage of ColorUIResource for Table.background.
This fixed the checkbox background issue for me.
I have implemented a JProgressBar in a JTable.
I used renderer for the ProgressBar NOT EDITOR.
Now I tried to implement a ProgressBar set value but due to EDT its not working so I used SwingUtilties but it did not work as well.
EXPECTED BEHAVIOUR - The JProgressBar must be setting value to 80 ,
currently it is showing only 0%
public class SkillSetTableProgressBarRenderer extends JProgressBar implements
TableCellRenderer {
public SkillSetTableProgressBarRenderer() {
super(0, 100);
super.setPreferredSize(new Dimension(100, 80));
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
final JProgressBar bar = (JProgressBar) value;
if (bar.getString().equals("JAVA") || bar.getString().equals("SWING"))
super.setString("Mellow");
else
super.setString("GOOD");
setOpaque(true);
table.setOpaque(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.err.println("MAIN ANDER");
setValue(80);
bar.setValue(80);
}
});
super.setStringPainted(true);
super.setIndeterminate(true);
super.setPreferredSize(new Dimension(140, 16));
if (isSelected) {
super.setBackground(table.getSelectionBackground());
super.setForeground(table.getSelectionForeground());
// this.setBackground(table.getSelectionBackground());
} else {
super.setForeground(table.getForeground());
super.setBackground(Color.WHITE);
}
return this;
}
}
Looks like you're trying to use a ProgressBar as a CellRenderer. One thing you should know, is that CellRenders are only called when a cell is being drawn, and setting values at other moments has no effect: this is because Swing uses a flyweight pattern for renderers, and thus reuses the renderer.
To get the effect you want, you should
notify the table that the cell is to be updated, for instance by updating its underlying model (that will fire the necessary events), and
set all values you need before the getTableCellRendererComponent return, so, remove the invokeLater (the getter is called on the EDT, so you don't need to worry about threading there).
1) even is possible, don't create JComponents inside TableCellRenderer, nor re_create Object, Renderer is only for formatting cell contents
2) use SwingWorker for moving with progress in JProgressBar (if you have real important reasons, then use Runnable#Tread)
example about Runnable#Tread
import java.awt.Component;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TableWithProgressBars {
public static class ProgressRenderer extends JProgressBar implements TableCellRenderer {
private static final long serialVersionUID = 1L;
public ProgressRenderer(int min, int max) {
super(min, max);
this.setStringPainted(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
this.setValue((Integer) value);
return this;
}
}
private static final int maximum = 100;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TableWithProgressBars().createGUI();
}
});
}
public void createGUI() {
final JFrame frame = new JFrame("Progressing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Integer[] oneRow = {0, 0, 0, 0};
String[] headers = {"One", "Two", "Three", "Four"};
Integer[][] data = {oneRow, oneRow, oneRow, oneRow, oneRow,};
final DefaultTableModel model = new DefaultTableModel(data, headers);
final JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new ProgressRenderer(0, maximum));
table.setPreferredScrollableViewportSize(table.getPreferredSize());
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(new Runnable() {
#Override
public void run() {
Object waiter = new Object();
synchronized (waiter) {
int rows = model.getRowCount();
int columns = model.getColumnCount();
Random random = new Random(System.currentTimeMillis());
boolean done = false;
while (!done) {
int row = random.nextInt(rows);
int column = random.nextInt(columns);
Integer value = (Integer) model.getValueAt(row, column);
value++;
if (value <= maximum) {
model.setValueAt(value, row, column);
try {
waiter.wait(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
done = true;
for (row = 0; row < rows; row++) {
for (column = 0; column < columns; column++) {
if (!model.getValueAt(row, column).equals(maximum)) {
done = false;
break;
}
}
if (!done) {
break;
}
}
}
frame.setTitle("All work done");
}
}
}).start();
}
}
Take a look at the Swing table tutorial: renderers and editors. The renderer uses the component to make a sort of 'stamp'. Changing the component afterwards has no effect since the component is not actually contained in your UI. That is one of the reasons you can return the same component over and over again, which would not be possible if the component was actually contained in the table. This is all explained in the linked tutorial.
To solve your problem, just remove the SwingUtilities.invokeLater method call, and return a JProgressBar where the correct progress is set.
And if I see this correctly, you have a TableModel containing a JProgressBar. I hope there is a good reason for this, since this is a UI component which should normally not be included on the model side. This is based on your
final JProgressBar bar = (JProgressBar) value;
call in the renderer. I would strongly suggest to take a look at your TableModel to see whether this can be avoided