I have a JTable with a number of rows, each row made up of a number of JPanels. I want one of the rows in the JTable to be unselectable. I have used a:
panel.setFocusable(false);
on this particular row. However, when pressing the down arrow key on the table, the selection disappears when it gets to the unfocusable panel. Is there a quick way of skipping the selection all together on this row and just selecting the next row?
You can achieve that with help of ListSelectionListener. Read more How to Write a List Selection Listener. Simple example:
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class TestFrame extends JFrame{
private int notSelectable = 2;
private JTable t1;
public TestFrame(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
init();
pack();
setVisible(true);
}
private void init() {
t1 = new JTable(10,1);
t1.getSelectionModel().addListSelectionListener(getListener());
t1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(t1);
}
private ListSelectionListener getListener() {
return new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if(t1.getSelectedRow() == notSelectable){
if(notSelectable+1 == t1.getRowCount()){
t1.getSelectionModel().setSelectionInterval(0, 0);
} else {
t1.getSelectionModel().setSelectionInterval(notSelectable+1, notSelectable+1);
}
}
}
};
}
public static void main(String... strings) {
new TestFrame();
}
}
Related
I have a table. If I right-click I got a JPopUpMenu but before the pop-up I want to select the row where the right-click event is done. Here is what I've tried.
path_tbl.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getPoint());
Point point = e.getPoint();
int selectedRow = path_tbl.rowAtPoint(point);
path_tbl.setRowSelectionInterval(selectedRow, selectedRow);
}
});
In that event, I cannot get any output from the console when I right-click. However, when I left-click, points are printed to the console.
java.awt.Point[x=105,y=76]
So, this event only works when I left-click. How can I make this event work with right-click?
Since you want custom mouse behavior, you should not use setComponentPopupMenu.
Instead, display the JPopupMenu yourself, using JPopupMenu’s show method:
JPopupMenu menu = /* ... */;
path_tbl.addMouseListener(new MouseAdapter() {
private void showPopupMenuFor(MouseEvent e) {
if (menu.isPopupTrigger(e)) {
Point point = e.getPoint();
int row = path_tbl.rowAtPoint(point);
if (!path_tbl.isRowSelected(row)) {
path_tbl.setRowSelectionInterval(row, row);
}
menu.show(path_tbl, e.getX(), e.getY());
}
}
#Override
public void mousePressed(MouseEvent e) {
showPopupMenuFor(e);
}
#Override
public void mouseReleased(MouseEvent e) {
showPopupMenuFor(e);
}
#Override
public void mouseClicked(MouseEvent e) {
showPopupMenuFor(e);
}
});
You must check the MouseEvent in both mousePressed and mouseReleased, because exactly when a context menu is triggered depends on the platform and the look-and-feel. (Checking in mouseClicked may or may not be necessary, but it doesn’t hurt.)
In most cases, I'm lazy, so if I don't need to do something, then I'd prefer not to. In this case, I'd prefer to make use of the existing API works as much as possible, meaning, make use of JComponent#setComponentPopupMenu, as it will take care of the "how" and "when" the popup should be shown, based on the current platform.
However, as you have discovered, by default, JTable will NOT select the row when the user presses the "right" mouse button, for that, you could just continue with your current workflow, but, checking to see if the MouseEvent is actually a "right" click.
Lucky for us, some of the original Swing developers were also "lazy" and they provided us with SwingUtilities.isRightMouseButton, yea 🎉
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(0, 10);
for (int row = 0; row < 10; row++) {
Vector data = new Vector(10);
for (int col = 0; col < 10; col++) {
String value = row + "x" + ((char) (col + 'A'));
data.add(value);
}
model.addRow(data);
}
JPopupMenu menu = new JPopupMenu();
menu.add("Hello");
menu.add("This looks interesting");
menu.add("I like bi-planes");
JTable table = new JTable(model);
table.setComponentPopupMenu(menu);
table.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
Point point = e.getPoint();
int row = table.rowAtPoint(point);
if (!table.isRowSelected(row)) {
table.setRowSelectionInterval(row, row);
}
}
}
});
add(new JScrollPane(table));
}
}
}
I want to get rid of the repeating code from an application. I tried to do it in several ways, but when I did it, the application did not work as I expected. Only putting the same code in a function had an effect.
In a nutshell, in the application I can delete records in 2 ways, either by pressing a button or by pointing to a record and deleting it with the right mouse button. Button deletion works, but I do not know how to make the mouse deletion have the same effect.
Button that deletes a record in a table.
deleteButton.addActionListener(event -> {
String name;
name = Name.getText();
try {
removeSelectedRow(table1);
pst = con.prepareStatement("delete from recipe where recipe_name = ?");
pst.setString(1, name);
pst.executeUpdate();
JOptionPane.showMessageDialog(null, "Record deleted");
Name.setText("");
Time.setText("");
Difficulty.setSelectedItem("");
Name.requestFocus();
} catch (SQLException e) {
e.printStackTrace();
}
});
here is a function that is supposed to delete with the right mouse button, as you can see it works but the code is almost identical to the previous example.
public void setDeleteButton(ActionEvent event) {
JMenuItem menu = (JMenuItem) event.getSource();
if (menu == menuItemRemove) {
removeSelectedRow(table1);
}
String name;
name = Name.getText();
try {
removeSelectedRow(table1);
pst = con.prepareStatement("delete from recipe where recipe_name = ?");
pst.setString(1, name);
pst.executeUpdate();
JOptionPane.showMessageDialog(null, "Record deleted");
Name.setText("");
Time.setText("");
Difficulty.setSelectedItem("");
Name.requestFocus();
} catch (SQLException e) {
e.printStackTrace();
}
}
function that points to a specific record
public void removeSelectedRow(JTable table) {
DefaultTableModel model = (DefaultTableModel) table1.getModel();
if (table.getSelectedRow() != -1) {
model.removeRow(table.getSelectedRow());
}
}
Okay, so this is going to require a slight shift in mindset. To make this truely flexible, you're going to need to support concepts such as "dependency inject" and "delegation".
The reason for this is, you "operation" needs a lot of information, but, we should be driving towards having a low level of cohesion or coupling between our classes. Your "operation", for example, shouldn't care "how" the row is deleted, only that when requested, it should be done.
So, lets start with some basic delegation...
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
Now, obviously, I'm overly simplifying this for more my needs, but here I've provided a "basic" level delegate and more focused delegate. Why? Because what happens if you want to provide a "insert" action? Why should it have "delete" functionality? Instead, we deliberately isolate the functionality we want to expose.
Next, we need to design our action...
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
Okay, nothing really special, which is kind of the point. It monitors the selection state so we can enable/disable the action and when triggered, we call our delegate to do the actual work. This decouples the action from the implementation, as the action doesn't need to know what type of TableModel is in use or what type of data source it might be using, it just wants to tell the delegate that it should carry out some kind of operation.
Also note, we've set up a keyboard short cut, which can be used by the JMenuItem and mnemonic support (hold down the Alt or Option key)
Okay, but that's really doing a lot for us, or is it...
Let's have a look at what a delete action might look like...
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
DefaultTableModel model = (DefaultTableModel) table.getModel();
int visibleRowIndex = table.getSelectedRow();
if (visibleRowIndex == -1) {
return;
}
int modelIndex = table.convertRowIndexToModel(visibleRowIndex);
// I'm guessing here, but if you're deleting a row, you should
// use the row data
String recordId = (String) model.getValueAt(modelIndex, 0);
try (PreparedStatement pst = getConnection().prepareStatement("delete from recipe where recipe_name = ?")) {
pst.setString(1, recordId);
// You could check the number of rows effected by this change
pst.executeUpdate();
JOptionPane.showMessageDialog(TestPane.this, "Record deleted", "Success", JOptionPane.INFORMATION_MESSAGE);
model.removeRow(modelIndex);
} catch (SQLException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(TestPane.this, "Failed to delete row from database", "Error", JOptionPane.ERROR_MESSAGE);
}
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
Now, this is just an example, but the basic idea is, we've provide implementation for both the MutableTableSupportable and TableRowDeletable interfaces (but the DeleteRowAction doesn't care about the "how") and we've implemented the removeSelectedRow functionality to delete the row from the TableModel and database.
Again, DeleteRowAction doesn't care how this is implemented, it's just delegating that responsibility, so you could have multiple DeleteRowActions which work with different TableModels and data sources all at the same time 😱
Delegation 💪
Okay, but how would all that work together? Well, actually, really easily in fact
Runnable example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
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.JTable;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import java.sql.*;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTable table;
public TestPane(JMenuBar menuBar) {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(new Object[][]{new Object[]{"Test"}}, new Object[]{"Test"});
table = new JTable(model);
add(new JScrollPane(table));
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
JOptionPane.showMessageDialog(TestPane.this, "Delete the row please", "Debug", JOptionPane.INFORMATION_MESSAGE);
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
}
}
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
}
nb: This example removes the database support, as I don't have one and instead displays a message
Okay, lets take a quick look at some of the interesting things here...
Firstly...
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
We inject the menu bar into the panel. This is done so that the panel can configure the menu bar as it needs. We could use a type of factory or another delegate here, but I'll leave that up to you to figure out.
Next...
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
We build the JMenu and add our delete row action and create a JButton, using the same Action ... for five lines of code, we've actually done a lot. We've been able to set up the text displayed by each component, the tooltip text, the accelerator key and mnemonic ... try doing that manually, and then need to change something down the track 🙄 (want to support localisation - need to make changes in one location)
But wait, we can do more!! 😱
If we add...
InputMap inputMap = table.getInputMap(WHEN_FOCUSED);
ActionMap actionMap = table.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "deleteRow");
actionMap.put("deleteRow", deleteRowAction);
to the end of the constructor, we can can provide a key binding to the user, so that when the JTable has keyboard focus and the hit they Delete/Backspace key, it will trigger the action as well!!!
Now we have four ways to trigger the action:
Press the button
Open and trigger the menu items
Use the menus keyboard "accelerator" key binding
Hit the Delete key
Actions 💪
Key bindings runnable example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
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.JTable;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JMenuBar menuBar = new JMenuBar();
JFrame frame = new JFrame();
frame.setJMenuBar(menuBar);
frame.add(new TestPane(menuBar));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTable table;
public TestPane(JMenuBar menuBar) {
setLayout(new BorderLayout());
DefaultTableModel model = new DefaultTableModel(new Object[][]{new Object[]{"Test"}}, new Object[]{"Test"});
table = new JTable(model);
add(new JScrollPane(table));
DeleteRowAction deleteRowAction = new DeleteRowAction(new TableRowDeletable() {
#Override
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
#Override
public void removeSelectedRow() {
JOptionPane.showMessageDialog(TestPane.this, "Delete the row please", "Debug", JOptionPane.INFORMATION_MESSAGE);
}
#Override
public void addListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().addListSelectionListener(listener);
}
#Override
public void removeListSelectionListener(ListSelectionListener listener) {
table.getSelectionModel().removeListSelectionListener(listener);
}
});
JMenu actionsMenu = new JMenu("Actions");
actionsMenu.add(deleteRowAction);
menuBar.add(actionsMenu);
JButton deleteButton = new JButton(deleteRowAction);
add(deleteButton, BorderLayout.SOUTH);
InputMap inputMap = table.getInputMap(WHEN_FOCUSED);
ActionMap actionMap = table.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "deleteRow");
actionMap.put("deleteRow", deleteRowAction);
}
}
public interface MutableTableSupportable {
public void addListSelectionListener(ListSelectionListener listener);
public void removeListSelectionListener(ListSelectionListener listener);
}
public interface TableRowDeletable extends MutableTableSupportable {
public int getSelectedRowCount();
public void removeSelectedRow();
}
public class DeleteRowAction extends AbstractAction {
private TableRowDeletable delgate;
public DeleteRowAction(TableRowDeletable delgate) {
putValue(SHORT_DESCRIPTION, "Delete the currently selected row");
putValue(MNEMONIC_KEY, KeyEvent.VK_D);
putValue(NAME, "Delete Row");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK));
this.delgate = delgate;
delgate.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectionDidChange();
}
});
selectionDidChange();
}
protected void selectionDidChange() {
setEnabled(delgate.getSelectedRowCount() > 0);
}
#Override
public void actionPerformed(ActionEvent e) {
delgate.removeSelectedRow();
}
}
}
But that's not all! We could also add a button to a JToolBar, because, why not!?
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 have a JTable in which the first column contains combobox with same items in each cell.If i select an item in a cell combobox i need to remove the selected item from all the other combobox in that column and also add the previous selected item to all the other combobox.How should i do that?Please help me with an example.
public class Save extends JFrame {
String[] items1 = new String[] { "Cash", "Bank1", "Bank2" ,"Bank3"};
TableCellEditor editors;
DefaultTableModel dtmFunds;
private JComboBox comboBox1;
private JTable jtblFunds;
private void loadTable(){
comboBox1=new JComboBox(items1);
comboBox1.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
int x=comboBox1.getSelectedIndex();
comboItem= e.getItem().toString();
}
}
});
editors=new DefaultCellEditor(comboBox1);
dtmFunds = new DefaultTableModel(new Object[][] {{"", " ","delete"}}, new Object[] {"Fund Store", "Amount","Action"});
jtblFunds=new JTable(dtmFunds){
public TableCellEditor getCellEditor(int row,int column){
int modelColumn=convertColumnIndexToModel(column);
if(modelColumn==0 && row<jtblFunds.getRowCount()-1)
return editors;
else
return super.getCellEditor(row,column);
}
};
jtblFunds.setModel(dtmFunds);
jtblFunds.getModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
int row=e.getFirstRow();
int column=e.getColumn();
if((column==0)&&(row<jtblFunds.getRowCount()-1)){
System.out.println("Dropdown changed "+row);
}
}
});
}
}
These are are codes i have used to add combobox to JTable column named "Fund Store".
Really, focus your efforts within the CellEditor itself, that's it's job. There is no need to extend from JTable or screw around with it.
import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
public class TestCellEditor {
public static void main(String[] args) {
new TestCellEditor();
}
public TestCellEditor() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<String> values = new ArrayList<>(5);
values.add("Bananas");
values.add("Apples");
values.add("Oranages");
values.add("Grapes");
values.add("Pears");
ComboBoxTableCellEditor editor = new ComboBoxTableCellEditor(values);
DefaultTableModel model = new DefaultTableModel(new Object[]{"Fruit"}, 5);
JTable table = new JTable(model);
table.getColumnModel().getColumn(0).setCellEditor(editor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ComboBoxTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JComboBox editor;
private List<String> masterValues;
public ComboBoxTableCellEditor(List<String> masterValues) {
this.editor = new JComboBox();
this.masterValues = masterValues;
}
#Override
public Object getCellEditorValue() {
return editor.getSelectedItem();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
DefaultComboBoxModel model = new DefaultComboBoxModel(masterValues.toArray(new String[masterValues.size()]));
for (int index = 0; index < table.getRowCount(); index++) {
if (index != row) {
String cellValue = (String) table.getValueAt(index, 0);
model.removeElement(cellValue);
}
}
editor.setModel(model);
editor.setSelectedItem(value);
return editor;
}
}
}
I'd prefer to have to two pools of values, one which is the master list and one which is the selected values, it would be easier and faster to prepare the editor each time it's invoked, but this is the basic idea...
All you need in fact is to update model of the comboBox you use for the editor.
comboBox1.setModel(new DefaultComboBoxModel(theArrayWithRemovedValue));
You have base array of combobox items (source) and result array of items where the selected one should be removed.
Just recreate the array removing value of the cell.
BTW. It's better to define cell editor standard way rather than overriding getCellEditor() method of JTable. Use e.g. public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor). You can define a custom class for the column 0.
I'd like to be able to react when currently highlighted item in a JComboBox drop down list changes. Note that I'm not looking for a way to get the currently selected item, but the highlighted one. When mouse hovers over this popup it highlights the item at mouse position, but this does not affect currently selected item, so I cannot simply listen via an ItemListener or ActionListener to achieve what I want.
I'm trying to create a component which consists of a JComboBox and a coupled tooltip which displays additional information (documentation) for the currently highlighted item.
As my first attempt I'm adding some code to my constructor (extended JComboBox):
import java.awt.BorderLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleState;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.ComboPopup;
public class SomeFrame extends JFrame {
private MyComboBox combo;
public SomeFrame() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(100,20);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
combo = new MyComboBox();
combo.setModel(new DefaultComboBoxModel(new String[]{"one", "two", "three", "four"}));
add(combo);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SomeFrame frame = new SomeFrame();
frame.setVisible(true);
}
});
}
// this is the important part
private static class MyComboBox extends JComboBox {
public MyComboBox() {
getAccessibleContext().addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (AccessibleContext.ACCESSIBLE_STATE_PROPERTY.equals(evt.getPropertyName())
&& AccessibleState.FOCUSED.equals(evt.getNewValue())
&& getAccessibleContext().getAccessibleChild(0) instanceof ComboPopup) {
ComboPopup popup = (ComboPopup) getAccessibleContext().getAccessibleChild(0);
JList list = popup.getList();
System.out.println("--> " + String.valueOf(list.getSelectedValue()));
}
}
});
}
}
}
It seems to work, but I got to this code through some shady channels and trial/error, so I'm thinking there has to be a better way of doing it. Any ideas? Is above code even production safe?
Good question and good solution - except that there seems to be bug in accessibleCombo which doesn't update its internal wiring on updateUI, that is, when toggling the LAF:
the accessible selection changes of the list are fired by an internal ListSelectionListener registered to the comboPopup list
the comboPopup is controlled by the ui-delegate and re/created in installUI
accessibleCombo doesn't update its internal list to the newly created and installed
Not much you can do about it. So I would listen directly to the list selection, then you have full control about re-wiring on LAF changes:
public static class XComboBox extends JComboBox {
private ListSelectionListener listener;
public XComboBox() {
uninstall();
install();
}
#Override
public void updateUI() {
uninstall();
super.updateUI();
install();
}
private void uninstall() {
if (listener == null) return;
getPopupList().removeListSelectionListener(listener);
listener = null;
}
protected void install() {
listener = new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
JList list = getPopupList();
System.out.println("--> " + String.valueOf(list.getSelectedValue()));
}
};
getPopupList().addListSelectionListener(listener);
}
private JList getPopupList() {
ComboPopup popup = (ComboPopup) getUI().getAccessibleChild(this, 0);
return popup.getList();
}
}
I'm trying to create a component which consists of a JComboBox and a coupled tooltip
Create a custom renderer for the combo box. Then in the renderer use the setToolTipText(...) method.
The section Specifying Tool Tips For Cells from the JTable tutorial shows how to do this for tables. The concept should be the same for a comboBox renderer.