I have made a TableHeader renderer that will create a JTextfield under the Label of the header in a JTable.
The problem i got now, i never get focus/access to this JTextfield in the header.
I found out that a TableHeader renderer only draws the component and dont do the rest, like focus and stuff.
I have tryed to make a array of JTextfield that will set on the header, so i can access them on code base. Unlucky that didnt workout, i was wondering if its possible to get access to this JTextField in the header and what is the best way to do this.
Tableheader renderer:
public class TextFieldTableHeaderRenderer extends AbstractCellEditor implements TableCellRenderer {
private MyPanel component;
public TextFieldTableHeaderRenderer(){
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
component = new MyPanel(column);
((MyPanel)component).setLabelText(value.toString());
return component;
}
#Override
public Object getCellEditorValue() {
return ((MyPanel)component).getTextField();
}
MyPanel:
public class MyPanel extends JPanel {
private javax.swing.JLabel label;
private javax.swing.JTextField textField;
public MyPanel(int column) {
super();
setLayout(new java.awt.BorderLayout());
label = new javax.swing.JLabel();
textField = new javax.swing.JTextField();
setBorder(javax.swing.BorderFactory.createEtchedBorder());
label.setHorizontalAlignment(SwingConstants.CENTER);
//textField.setText("Column "+column);
add(textField, java.awt.BorderLayout.PAGE_END);
add(label, java.awt.BorderLayout.CENTER);
}
public void setLabelText( String text ){
label.setText(text);
}
public void setTextFieldText(String text){
getTextField().setText(text);
}
public javax.swing.JTextField getTextField() {
return textField;
}
/**
* #param textField the textField to set
*/
public void setTextField(javax.swing.JTextField textField) {
this.textField = textField;
}
Install on header:
for( int i=0; i < this.getxColumnModel().getColumnCount(); i++){
this.getxColumnModel().getColumn(i, true).setHeaderRenderer( new TextFieldTableHeaderRenderer() );
}
I have try to use the "EditableHeader" example from the i-net, but it makes a new JTextfield when clicking on the header.
I like to see that the user get focus on the JTextfield, enters a text and then it will filter the column.
Filtering wont be a problem, cause i have made that already.
Hopefully im clear to you guys/girls and love to hear you solution
Here's a simple approach for making editable headers:
EDIT: oops - I meant to post this in another thread. I guess I'll keep it here anyway.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class JTableEditableHeaderDemo implements Runnable
{
private JTable table;
private JTableHeader header;
private JPopupMenu renamePopup;
private JTextField text;
private TableColumn column;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new JTableEditableHeaderDemo());
}
public JTableEditableHeaderDemo()
{
table = new JTable(10, 5);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
header = table.getTableHeader();
header.addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent event)
{
if (event.getClickCount() == 2)
{
editColumnAt(event.getPoint());
}
}
});
text = new JTextField();
text.setBorder(null);
text.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
renameColumn();
}
});
renamePopup = new JPopupMenu();
renamePopup.setBorder(new MatteBorder(0, 1, 1, 1, Color.DARK_GRAY));
renamePopup.add(text);
}
public void run()
{
JFrame f = new JFrame("Double-click header to edit");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private void editColumnAt(Point p)
{
int columnIndex = header.columnAtPoint(p);
if (columnIndex != -1)
{
column = header.getColumnModel().getColumn(columnIndex);
Rectangle columnRectangle = header.getHeaderRect(columnIndex);
text.setText(column.getHeaderValue().toString());
renamePopup.setPreferredSize(
new Dimension(columnRectangle.width, columnRectangle.height - 1));
renamePopup.show(header, columnRectangle.x, 0);
text.requestFocusInWindow();
text.selectAll();
}
}
private void renameColumn()
{
column.setHeaderValue(text.getText());
renamePopup.setVisible(false);
header.repaint();
}
}
TableColumn supports setting a TableCellRenderer via setHeaderRenderer(), as shown in this example; it has no provision for setHeaderEditor(), which would be required for editing. Alternatives might include these:
Write a custom JTableHeader.
Add a row of text fields in an adjacent, conformal layout.
Use a particular row in the TableModel, as suggested in FixedRowExample.
Consider a commercial alternative; several are listed here.
Related
I'm creating a JTable in Java & I'm asked to add to the table a JCheckBox, JButton and a JComboBox. The table that I created is displaying all the information, the button is working fine and the JCheckBox is also working, the problem I'm facing is that the JComboBox is not working. I really can't figure out why. I've tried to look the problem up but I can't figure it out. Can someone help me please?
The code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
public class sinX extends JFrame {
private JTable table;
private DefaultTableModel model;
private Object[][] data;
private String[] columnNames;
private JButton button;
JComboBox comboBox;
public sinX() {
comboBox = new JComboBox();
setTitle("Programming Languages");
data = new Object[][]{{"C","Dennis Ritchie",1972,false},{"C++","Bjarne Stroustrup",1983,true},
{"Python","Guido van Rossum",1991,false},{"Java","James Gosling",1995,true},{
"JavaScript","Brendan Eich",1995,true},{"C#","Anders Hejlsberg",2001,false},
{"Scala","Martin Odersky",2003,true}};
columnNames = new String[] {"Language","Author","Year","Check Box"};
//model = new DefaultTableModel(data, columnNames);
//table = new JTable(model);
//table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
final Class[] columnClass = new Class[] {
String.class, String.class, Integer.class, Boolean.class
};
//create table model with data
DefaultTableModel model = new DefaultTableModel(data, columnNames) {
#Override
public boolean isCellEditable(int row, int column)
{
return false;
}
#Override
public Class<?> getColumnClass(int columnIndex)
{
return columnClass[columnIndex];
}
};
table = new JTable(model);
table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
button = new JButton("Remove");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
// check for selected row first
if(table.getSelectedRow() != -1) {
// remove selected row from the model
model.removeRow(table.getSelectedRow());
JOptionPane.showMessageDialog(null, "Selected row deleted successfully");
}
}
});
TableColumn year = table.getColumnModel().getColumn(2);
comboBox.addItem("A");
comboBox.addItem("B");
comboBox.addItem("C");
comboBox.addItem("D");
year.setCellEditor(new DefaultCellEditor(comboBox));
add(new JScrollPane(table), BorderLayout.CENTER);
add(button, BorderLayout.SOUTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 500);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String args[]) {
new sinX();
}
}
the problem I'm facing is that the JComboBox is not working.
year.setCellEditor(new DefaultCellEditor(comboBox));
The above code indicates you are trying to use a combo box as an editor for the given column.
public boolean isCellEditable(int row, int column)
{
return false;
}
However, you have stated in your model that none of columns are editable.
You need to return true for any column where you want to edit the data.
the JCheckBox is also working
Not really. Yes you see the boolean value is rendered as a check box. However, you can't change its value by clicking on it, unless of course you return true for that column as well.
I had an error yesterday where I added a JTable and a JPanel (with a JButton in it) to a JScrollPane. The JButton was fixed to the bottom of the table, and it added a row to the JTable when clicked.
The problem was if the table ever got bigger than the JScrollPane, it would only allow you to scroll to the bottom of the JTable; you couldn't get to the JButton anymore. Today, I made an MCVE to try and get help, but first I monkeyed with it a bit more and ended up fixing my problem, but in a way that left me with more questions than answers... Here's the MCVE I had prepared:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class MCVE extends JFrame{
private JButton addRow;
private MCVEModel tableModel;
private JTable table;
private JScrollPane pane;
private JPanel scrollPanel, panel;
public static void main (String[] args) {
new MCVE();
}
public MCVE() {
initialize();
}
public void initialize () {
this.setTitle("Halp");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setBounds(50, 50, 500, 300);
this.setResizable(false);
JPanel mainPanel = new JPanel(new GridBagLayout());
/** The JPanel everything is put into **/
scrollPanel = new JPanel();
scrollPanel.setLayout(new BoxLayout(scrollPanel, BoxLayout.Y_AXIS));
/** The JScrollPane we're using **/
pane = new JScrollPane(scrollPanel);
pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
pane.getVerticalScrollBar().setUnitIncrement(10);
/** The button which keeps getting cut off.... **/
addRow = new JButton("...");
addRow.setBackground(Color.WHITE);
addRow.setMnemonic('R');
addRow.setFocusable(false);
addRow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addNewRow();
}
});
/** I wrap the button into this panel so I can affix it to the left **/
panel = new JPanel();
panel.setMinimumSize(new Dimension(0, 20));
panel.setLayout(null);
addRow.setBounds(0, 0, 35, 15);
panel.add(addRow);
/** Faking some data to get the table to populate **/
ArrayList<List<String>> allData = new ArrayList<List<String>>();
ArrayList<String> fakeData = new ArrayList<String>();
fakeData.addAll(Arrays.asList(
new String[]{"this", "is", "just", "sample", "data"}));
for(int i = 0; i < 5; i++)
allData.add(fakeData);
List<String> columnNames = Arrays.asList(new String[] {"", "", "", "", ""});
tableModel = new MCVEModel(columnNames, allData);
table = new JTable();
table.setModel(tableModel);
/** Adding it all together **/
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
scrollPanel.add(table);
scrollPanel.add(panel);
mainPanel.add(pane, c);
this.add(mainPanel);
this.setVisible(true);
}
public void addNewRow () {
tableModel.addRow(tableModel.getRowCount(),
new String[]{"true", "", "", "false", "false"});
tableModel.fireTableRowsInserted(
tableModel.getRowCount(), tableModel.getRowCount());
}
}
/**
* Just here to keep things compilable. Seriously cut back for the MCVE,
* but still replicates the problem without any errors.
* Nothing below here should be relevant to the issue.
*/
class MCVEModel extends DefaultTableModel {
private static final long serialVersionUID = -6598574844380686148L;
private List<String> columnNames;
private List<List<String>> values;
public MCVEModel (List<String> columnNames, List<List<String>> strings) {
this.columnNames = columnNames;
this.values = strings;
}
public int getColumnCount() {
return columnNames.size();
}
public int getRowCount() {
return values == null || values.size() == 0 ? 0 : values.get(0).size();
}
public String getColumnName(int col) {
return columnNames.get(col);
}
public Object getValueAt(int row, int col) {
return values.get(col).get(row);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public Class getColumnClass(int c) {
return String.class;
}
public boolean isCellEditable(int row, int col) {
return true;
}
public void setValueAt(Object value, int row, int col) {
values.get(col).set(row, (String) value);
fireTableCellUpdated(row, col);
}
public void removeRow(int row) {
for(int i = 0; i < values.size(); i++)
values.get(i).remove(row);
this.fireTableRowsDeleted(row, row);
}
public void addRow(int row, String[] strings) {
for(int i = 0; i < values.size(); i++)
values.get(i).add(row, strings[i]);
fireTableRowsInserted(row, row);
}
}
The problem is with this line:
panel.setMinimumSize(new Dimension(0, 20));
More precisely, it's with the word "Minimum". By changing this to:
panel.setPreferredSize(new Dimension(0, 20));
I got exactly the functionality I needed. Now when the table gets too large for the JScrollPanel, we're still able to scroll down and see the JButton; it's no longer cut off.
I'm assuming this means the JPanel's parent didn't honor its minimum dimensions, but that it did honor its preferred dimensions. Why is this? I had thought setPreferredSize(), setMinimumSize(), and setMaximumSize() interacted like, "I'd prefer to be this big, but no matter what I can't be smaller than my minimum or larger than my maximum," but it seems this isn't the case. I know none of these methods should be used too frequently, but when should I use setMinimumSize() over setPreferredSize() or vice versa?
The magic is this line:
scrollPanel.add(panel);
So, scrollPanel will contain this panel. Then, JScrollPane honors the preferredSize-s. Which makes sense, since its purpose is, by using the scroll bars, to make enough room for the contained components. In other words, JScrollPane -s impementation ignores the minimumSize-s.
Update:
From an other angle, JScrollPane -s source code checks the preferredSize of its children, but not the minimumSize. There's no deep philosophy here, JScrollPane is implemented this way.
I'm using IntelliJ GUI Builder to design a GUI for my application. In it, there is a JTable inside a JScrollPane that doesn't seem to be working. Firstly, I can't get the column headers to display. Second, table clicking is not working. It acts as if I'm clicking 3 rows down from where I actually am, both in default row selection and in any MouseListeners I implement. Lastly, if the table exceeds the size of the JScrollPane, it just ignores the last X rows and doesn't provide a scroll bar to view them.
I've reworked the project a couple times now, trying extensions of AbstractTableModel, then DefaultTableModel, and lately I have tried ditching a custom TableModel altogether and just using a DefaultTableModel constructor to no avail. Here is all relevant code (some of it is auto-generated by the GUI Builder and I can't modify it directly).
BaldGUI.java (the main gui)
package client;
import client.DataTypes.Record;
import client.DataTypes.RecordSet;
import client.GuiElements.FileTree;
import client.GuiElements.RecordsTable;
import client.GuiElements.TextConsole;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
public class BaldGUI extends JFrame {
//Menu
private JMenuBar menuBar = new JMenuBar();
private JMenu fileMenu = new JMenu("File");
private JMenuItem newBatchItem = new JMenuItem("New Batch");
private JMenuItem saveBatchItem = new JMenuItem("Save Batch");
private JMenuItem loadBatchItem = new JMenuItem("Load Batch");
private static String rootDir = "C:/Users/wf1946/IdeaProjects/DocumentumLoaderTest01/data";
private JPanel mainPanel;
private JPanel LeftSideBarPanel;
private JTree fileTree;
private JButton AddFileButton;
private JButton ChangeDirectoryButton;
private JButton AddDirectoryButton;
private JCheckBox IncludeSubDirectoriesCheckBox;
private JScrollPane DataTableWrapper;
private JTable DataTable;
private JEditorPane Console;
private JScrollPane ConsoleScroller;
public BaldGUI() {
$$$setupUI$$$();
this.loadComponents();
this.AddFileButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
((RecordsTable) DataTable).addItem(new Record());
}
});
this.add(this.mainPanel);
}
private void loadComponents() {
//Menu
this.setJMenuBar(this.menuBar);
this.menuBar.add(this.fileMenu);
this.fileMenu.add(this.newBatchItem);
this.fileMenu.add(this.saveBatchItem);
this.fileMenu.add(this.loadBatchItem);
//Selection handler for the file tree
this.fileTree.addTreeSelectionListener(new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent e) {
TreePath path = e.getPath();
if (!fileTree.getModel().isLeaf(path.getLastPathComponent())) { //Directory
AddDirectoryButton.setEnabled(true);
IncludeSubDirectoriesCheckBox.setEnabled(true);
AddFileButton.setEnabled(false);
} else { //File
AddFileButton.setEnabled(true);
AddDirectoryButton.setEnabled(false);
IncludeSubDirectoriesCheckBox.setEnabled(false);
}
}
});
}
//Getters
public JEditorPane getConsole() {
return Console;
}
public JPanel getMainPanel() {
return mainPanel;
}
public JTree getFileTree() {
return fileTree;
}
public JTable getDataTable() {
return this.DataTable;
}
public JCheckBox getIncludeSubDirectoriesCheckBox() {
return IncludeSubDirectoriesCheckBox;
}
public JScrollPane getDataTableWrapper() {
return DataTableWrapper;
}
private void createUIComponents() {
this.Console = new TextConsole();
this.fileTree = new FileTree(this, new File(this.rootDir));
RecordSet rs = new RecordSet();
for (int i = 0; i < 10; i++) rs.add(new Record());
this.DataTable = new RecordsTable(new DefaultTableModel(rs.getData(), RecordsTable.colNames), this);
this.DataTableWrapper = new JScrollPane(this.DataTable);
}
/**
* Method generated by IntelliJ IDEA GUI Designer
* >>> IMPORTANT!! <<<
* DO NOT edit this method OR call it in your code!
*
* #noinspection ALL
*/
private void $$$setupUI$$$() {
createUIComponents();
mainPanel = new JPanel();
mainPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
mainPanel.setMinimumSize(new Dimension(1080, 810));
mainPanel.setPreferredSize(new Dimension(1080, 810));
LeftSideBarPanel = new JPanel();
LeftSideBarPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
LeftSideBarPanel.setMinimumSize(new Dimension(220, 35));
LeftSideBarPanel.setPreferredSize(new Dimension(220, 600));
mainPanel.add(LeftSideBarPanel);
fileTree.setPreferredSize(new Dimension(200, 530));
fileTree.setShowsRootHandles(true);
LeftSideBarPanel.add(fileTree);
AddFileButton = new JButton();
AddFileButton.setPreferredSize(new Dimension(100, 25));
AddFileButton.setText("Add File");
LeftSideBarPanel.add(AddFileButton);
ChangeDirectoryButton = new JButton();
ChangeDirectoryButton.setPreferredSize(new Dimension(100, 25));
ChangeDirectoryButton.setText("Change Root");
LeftSideBarPanel.add(ChangeDirectoryButton);
AddDirectoryButton = new JButton();
AddDirectoryButton.setPreferredSize(new Dimension(100, 25));
AddDirectoryButton.setText("Add Directory");
LeftSideBarPanel.add(AddDirectoryButton);
IncludeSubDirectoriesCheckBox = new JCheckBox();
IncludeSubDirectoriesCheckBox.setPreferredSize(new Dimension(100, 22));
IncludeSubDirectoriesCheckBox.setText("Subdirectories");
LeftSideBarPanel.add(IncludeSubDirectoriesCheckBox);
DataTableWrapper.setPreferredSize(new Dimension(845, 600));
mainPanel.add(DataTableWrapper);
DataTable.setFillsViewportHeight(true);
DataTableWrapper.setViewportView(DataTable);
ConsoleScroller = new JScrollPane();
mainPanel.add(ConsoleScroller);
Console.setEnabled(false);
Console.setPreferredSize(new Dimension(1070, 195));
ConsoleScroller.setViewportView(Console);
}
/**
* #noinspection ALL
*/
public JComponent $$$getRootComponent$$$() {
return mainPanel;
}
}
RecordsTable.java
package client.GuiElements;
import client.ActionListeners.RightClickMenuItemClick;
import client.ActionListeners.TableRightClickHandler;
import client.BaldGUI;
import client.DataTypes.Record;
import client.DataTypes.RecordSet;
import javax.swing.*;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.util.ArrayList;
//Table to store the records
public class RecordsTable extends JTable {
//Status codes returned to calling functions to indicate the success or failure of the new record
public static final int APPEND_SUCCESS_CODE = 1;
public static final int APPEND_FAIL_DUPLICATE_CODE = 2;
public static final String[] colNames = {"Status", "File", "Full Path", "Title", "Form Date",
"Form No.", "Language Code", "Filed", "Approval Date", "Filed Form No."};
private RecordSet data = new RecordSet();
//Parent form
BaldGUI parent;
//Right-click menu for table item
JPopupMenu itemRightClickMenu = new JPopupMenu();
JMenuItem itemEdit = new JMenuItem("Edit Record");
JMenuItem itemDelete = new JMenuItem("Remove Record");
public RecordsTable(DefaultTableModel model, BaldGUI form) {
super(model);
this.parent = form;
this.itemRightClickMenu.add(itemEdit);
this.itemRightClickMenu.add(itemDelete);
this.itemEdit.addMouseListener(new RightClickMenuItemClick(this, itemEdit));
this.itemDelete.addMouseListener(new RightClickMenuItemClick(this, itemDelete));
this.addMouseListener(new TableRightClickHandler(this));
this.updateTable();
}
//Attempts to add a new row to the table
//Returns APPEND_FAIL_DUPLICATE_CODE if the selected file is already in the table
//Returns APPEND_SUCCESS_CODE if the record is successfully added
public int addItem(Record newRecord) {
TextConsole tc = ((TextConsole)this.parent.getConsole());
if(this.itemInData(newRecord)) {
tc.addText(
"File " + newRecord.getFileName() + " already included.\n", TextConsole.redStyle
);
return this.APPEND_FAIL_DUPLICATE_CODE;
}
this.data.add(newRecord);
tc.addText("File " + newRecord.getFileName() + " added successfully.\n", TextConsole.greenStyle);
this.updateTable();
return this.APPEND_SUCCESS_CODE;
}
//Updates the table to display any new data
public void updateTable() {
}
//Returns true if the record is already in the table
//Record equality is defined based on the full path to the file
public boolean itemInData(Record item) {
for( Record r : data) {
if(r.equals(item)) return true;
}
return false;
}
public JPopupMenu getItemRightClickMenu() {
return itemRightClickMenu;
}
public JMenuItem getItemEdit() {
return itemEdit;
}
public BaldGUI getParent() {
return parent;
}
}
The Record type is just a basic data container, and RecordSet is just an extension of ArrayList{Record} with a method to turn the data therein into an Object[][] for the DefaultTableModel.
So, as I expected, it was a really simple, dumb mistake. In my RecordsTable class, I stored off the parent GUI (BaldGUI) as a variable called parent. I then had a method getParent() to fetch that parent, and I didn't realize that JTable comes with a method getParent() which gets the surrounding component. By overriding that method, the entire program more or less broke. I changed the method, and it works as it should.
In my current swing project I have a JList displaying all active sockets, and each cell has a JButton to close that socket. But the JButton in the cell is not clickable: listener does not get fired.
I have modified the code to minimal as follows.
private class ConnectionListRenderer extends JButton implements ListCellRenderer {
public Component getListCellRendererComponent(JList jlist, Object o, int i, boolean bln, boolean bln1) {
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//do something (close the socket in my project)
}
});
return this;
}
}
jList.setCellRenderer(new ConnectionListRenderer());
The list looks fine, but the button on in is not clickable. Am I wrong or JList just does not support JButton in the getting fired?
Here's an example that seems to work, although you don't get the same visual effect of a normal button click. Perhaps someone with better painting skill than me could improve this to simulate the visual pressed button effect.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* A JList of JButtons.
*/
public class JButtonListDemo implements Runnable
{
private JList jlist;
public static void main(String args[])
{
SwingUtilities.invokeLater(new JButtonListDemo());
}
public void run()
{
Object[] items = new ButtonItem[] {
new ButtonItem("Apple"),
new ButtonItem("Banana"),
new ButtonItem("Carrot"),
new ButtonItem("Date"),
new ButtonItem("Eggplant"),
new ButtonItem("Fig"),
new ButtonItem("Guava"),
};
jlist = new JList(items);
jlist.setCellRenderer(new ButtonListRenderer());
jlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jlist.setVisibleRowCount(5);
jlist.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent event)
{
clickButtonAt(event.getPoint());
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(jlist));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void clickButtonAt(Point point)
{
int index = jlist.locationToIndex(point);
ButtonItem item = (ButtonItem) jlist.getModel().getElementAt(index);
item.getButton().doClick();
// jlist.repaint(jlist.getCellBounds(index, index));
}
public class ButtonItem
{
private JButton button;
public ButtonItem(String name)
{
this.button = new JButton(name);
button.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println(button.getText() + " was clicked.");
}
});
}
public JButton getButton()
{
return button;
}
#Override
public String toString()
{
return button.getText();
}
}
class ButtonListRenderer extends JButton implements ListCellRenderer
{
public Component getListCellRendererComponent(JList comp, Object value, int index,
boolean isSelected, boolean hasFocus)
{
setEnabled(comp.isEnabled());
setFont(comp.getFont());
setText(value.toString());
if (isSelected)
{
setBackground(comp.getSelectionBackground());
setForeground(comp.getSelectionForeground());
}
else
{
setBackground(comp.getBackground());
setForeground(comp.getForeground());
}
return this;
}
}
}
Alternatively, you could always layout your JButtons vertically on a JPanel (using a new GridLayout(0,1) perhaps) and then put your JPanel in a JScrollPane, thus mocking a JList of JButtons.
Render's are not "real" components, they are "rubber stamps" painted onto the surface of the parent component. They have no "physical" presence.
A JList will have only one instance of the render and this is used to "stamp" each of the items from the list model onto the view.
Out of the box, JList is not editable.
Another solution would be to use two lists next to each other. The first one renders the actual list content, while the second renders buttons, you can add the two lists to a JPanel and layout them with BorderLayout (Borderlayout.CENTER and BorderLayout.EAST). Add this JPanel to the viewport of a JScrollPane.
I have a cell editor that contains a little button and then a textfield that can be used to edit the value inline
I use setSurrendersFocusOnKeystroke(true) and a focus listener in order to allow a user to start editing immediately from the keyboard, but the trouble is the fisrt key pressed seems to get consumed rather being added to the text field, how can I prevent this ?
Full self contained example below
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
public class PanelTableEditorTest extends JFrame {
private JTable table;
public PanelTableEditorTest() {
this.setLayout(new BorderLayout());
table = new JTable(10, 10);
table.getSelectionModel().setSelectionMode(
ListSelectionModel.SINGLE_SELECTION);
table.setCellSelectionEnabled(true);
table.setDefaultEditor(Object.class, new SimpleMultiRowCellEditor());
table.setSurrendersFocusOnKeystroke(true);
table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F2, 0),
"none");
table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ENTER, 0),
"startEditing");
this.add(table.getTableHeader(), BorderLayout.NORTH);
this.add(table, BorderLayout.CENTER);
pack();
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new PanelTableEditorTest();
}
});
}
public class SimpleMultiRowCellEditor extends DefaultCellEditor {
final JPanel panel;
private final JButton rowCount;
public SimpleMultiRowCellEditor() {
super(new JTextField());
this.setClickCountToStart(1);
rowCount = new JButton();
rowCount.setVisible(true);
panel = new JPanel();
panel.setOpaque(false);
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.add(rowCount);
panel.add(editorComponent);
panel.addFocusListener(new PanelFocusListener());
}
public Component getTableCellEditorComponent(
final JTable table,final Object val, final boolean isSelected,
final int row, final int column) {
rowCount.setText("1");
delegate.setValue(val);
editorComponent.requestFocusInWindow();
return panel;
}
class PanelFocusListener implements FocusListener {
public void focusGained(FocusEvent e) {
editorComponent.requestFocusInWindow();
}
public void focusLost(FocusEvent e) {
}
}
}
}
So I have found a solution, thanks to this article http://jroller.com/santhosh/entry/keyboard_handling_in_tablecelleditor , and some useful discussion abou this and how it can be applied to other components at http://forums.java.net/jive/thread.jspa?messageID=482236񵮼
Don't fully understand the solution this whole area seems to be rather a minefield
I've also added this solution Get correct editing behaviour in JTable using java DefaultCellEditor into this so that when you start editing a field using the keyboard the existing value is replaced, but not when you double click o the field.
My one confusion is that I'm not receiving a Key Event as I'd expect but just null so I've had to account for that.
Ive gone back from using setSurrenderKeystrokes(true) because this causes problems with others editors such as the straightforward textfieldeditor
import javax.swing.*;
import javax.swing.text.Caret;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.EventObject;
public class PanelTableEditorTest extends JFrame
{
private JTable table;
public PanelTableEditorTest()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e)
{
}
this.setLayout(new BorderLayout());
table = new JTable(4, 4);
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setCellSelectionEnabled(true);
table.setSurrendersFocusOnKeystroke(false);
table.setDefaultEditor(Object.class,new SimpleMultiRowCellEditor());
table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(java.awt.event.
KeyEvent.VK_F2, 0), "none");
table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(java.awt.event.
KeyEvent.VK_ENTER, 0), "startEditing");
this.add(table.getTableHeader(), BorderLayout.NORTH);
this.add(table, BorderLayout.CENTER);
pack();
setVisible(true);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new PanelTableEditorTest();
}
});
}
public class SimpleMultiRowCellEditor extends DefaultCellEditor
{
private EventObject event;
final JPanel panel;
private final JButton rowCount;
public SimpleMultiRowCellEditor()
{
super(new JTextField());
this.setClickCountToStart(1);
rowCount = new JButton();
rowCount.setVisible(true);
panel = new TableEditorPanel();
panel.setRequestFocusEnabled(true);
panel.setOpaque(false);
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.add(rowCount);
panel.add(editorComponent);
}
public boolean isCellEditable(EventObject anEvent)
{
event=anEvent;
return super.isCellEditable(anEvent);
}
public Component getTableCellEditorComponent(final JTable table, final Object val, final boolean isSelected, final int row, final int column)
{
rowCount.setText("1");
delegate.setValue(val);
if(event instanceof KeyEvent || event==null)
{
final Caret caret = ((JTextField)editorComponent).getCaret();
caret.setDot(0);
((JTextField)editorComponent).setText("");
}
return panel;
}
class TableEditorPanel extends JPanel
{
public void addNotify(){
super.addNotify();
editorComponent.requestFocus();
}
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed){
InputMap map = editorComponent.getInputMap(condition);
ActionMap am = editorComponent.getActionMap();
if(map!=null && am!=null && isEnabled()){
Object binding = map.get(ks);
Action action = (binding==null) ? null : am.get(binding);
if(action!=null){
return SwingUtilities.notifyAction(action, ks, e, editorComponent,
e.getModifiers());
}
}
return false;
}
}
}
}
add a
rowCount.setFocusable(false);
in the SimpleMultiRowCellEditor constructor, to prevent the button to retrieve focus, so that the JTextfield is the only how can have the focus on cell edition