Populate JTable from a Hashtable in Java - java

I have a function which gets a key from the user and generates a Hashtable (on a pattern specified by the key). After creating a Hashtable, I would like to populate a JTable so that each each column represents a key and every rows represents the values associated with the key. I tried everything but couldn't get this work. I'm not creating the table from within the constructor as I need to get input from the user.

See How to Use Tables: Creating a Table Model.
The JTable constructor used by
SimpleTableDemo creates its table
model with code like this:
new AbstractTableModel() {
public String getColumnName(int col) {
return columnNames[col].toString();
}
public int getRowCount() { return rowData.length; }
public int getColumnCount() { return columnNames.length; }
public Object getValueAt(int row, int col) {
return rowData[row][col];
}
public boolean isCellEditable(int row, int col)
{ return true; }
public void setValueAt(Object value, int row, int col) {
rowData[row][col] = value;
fireTableCellUpdated(row, col);
}
}
You basically have to wrap your hashtable in the above manner. Here's an example.
package eed3si9n.hashtabletable;
import java.awt.BorderLayout;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.JButton;
import java.awt.Dimension;
public class MainForm extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel jContentPane = null; // #jve:decl-index=0:visual-constraint="23,38"
private JScrollPane m_scrollPane = null;
private JTable m_table = null;
private Hashtable<String, String> m_hash = null;
private JButton m_btnAdd = null;
/**
* This is the default constructor
*/
public MainForm() {
super();
initialize();
m_hash = new Hashtable<String, String>();
m_hash.put("Dog", "Bow");
}
private void onButtonPressed() {
m_hash.put("Cow", "Moo");
m_table.revalidate();
}
/**
* This method initializes this
*
* #return void
*/
private void initialize() {
this.setSize(409, 290);
this.setTitle("JFrame");
this.setContentPane(getJContentPane());
}
/**
* This method initializes jContentPane
*
* #return javax.swing.JPanel
*/
private JPanel getJContentPane() {
if (jContentPane == null) {
jContentPane = new JPanel();
jContentPane.setLayout(new BorderLayout());
jContentPane.setSize(new Dimension(500, 500));
jContentPane.setPreferredSize(new Dimension(500, 500));
jContentPane.add(getM_scrollPane(), BorderLayout.NORTH);
jContentPane.add(getM_btnAdd(), BorderLayout.SOUTH);
}
return jContentPane;
}
/**
* This method initializes m_scrollPane
*
* #return javax.swing.JScrollPane
*/
private JScrollPane getM_scrollPane() {
if (m_scrollPane == null) {
m_scrollPane = new JScrollPane();
m_scrollPane.setViewportView(getM_table());
}
return m_scrollPane;
}
/**
* This method initializes m_table
*
* #return javax.swing.JTable
*/
private JTable getM_table() {
if (m_table == null) {
m_table = new JTable();
m_table.setModel(new AbstractTableModel(){
private static final long serialVersionUID = 1L;
public int getColumnCount() {
return 2;
}
public int getRowCount() {
return m_hash.size();
}
public String getColumnName(int column) {
if (column == 0) {
return "Animal";
} else {
return "Sound";
}
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return getKey(rowIndex);
} else {
return m_hash.get(getKey(rowIndex));
} // if-else
}
private String getKey(int a_index) {
String retval = "";
Enumeration<String> e = m_hash.keys();
for (int i = 0; i < a_index + 1; i++) {
retval = e.nextElement();
} // for
return retval;
}
});
}
return m_table;
}
/**
* This method initializes m_btnAdd
*
* #return javax.swing.JButton
*/
private JButton getM_btnAdd() {
if (m_btnAdd == null) {
m_btnAdd = new JButton();
m_btnAdd.setPreferredSize(new Dimension(34, 30));
m_btnAdd.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
onButtonPressed();
}
});
}
return m_btnAdd;
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainForm frame = new MainForm();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
frame.setVisible(true);
}
});
}
} // #jve:decl-index=0:visual-constraint="10,10"

Firstly, avoid Hashtable, go straight for Map. In this case there two potential standard implementations you mights want: LinkedHashMap can retain the order that the entries were added; TreeMap, a SortedMap/NavigableMap, will sort the results (order of which can be determined by a Comparator. Alternatively you might want a form of Map that fire events or also provides a TableModel.
If you want a one time conversion from the Map to table, then it's pretty straightforward.
public static TableModel toTableModel(Map<?,?> map) {
DefaultTableModel model = new DefaultTableModel(
new Object[] { "Key", "Value" }, 0
);
for (Map.Entry<?,?> entry : map) {
model.addRow(new Object[] { entry.getKey(), entry.getValue() });
}
return model;
}
Then just create the JTable with this pre-populated model.
(Disclaimer: I've not tested or so much as compiled this code.)
To keep the Map and TableModel synchronized is more code. Generally it's best to avoid duplicating state whereever possible. Write a class that exposes itself as both a Map and a TableModel. You could go more disjoint by having a Map that fires events and a TableModel that adapts the Map (although note that Map does not have random access based on index, so you'll need to be iether clever or slow for large maps).
Going the other way, a simpler approach would be to add the data straight to a DefaultTableModel and not using a Map at all.

Related

Cannot update JTable's values after data change using setValueAt

I've done JTable for simple schedule with visits.
It contains custom AbstractTableModel which shows three columns shown below.
The problem is that it is possible to initializate Table and to get desired look - but after data change there is no change in appearance of Table. Each button click takes data from database and sets fields in columns TYPE and STATE - depending on given hour and date of reservation.
What is more I'am able to insert new row at the end of table but cannot make visible existing value update.
I already read few similar topics but nothing helps in my case.
Thanks in advance for every suggestions.
[UPDATED] Working code illustrating the problem:
import java.awt.EventQueue;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ModelTest {
private JFrame frame;
private JTable tablePendingVisits;
private PendingVisitModel pendingVisitModel;
private JScrollPane scrollPanePendingVisits;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ModelTest window = new ModelTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public ModelTest() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 407);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
JButton btnChangeValue = new JButton("Change value at 9:00");
btnChangeValue.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
refreshTableModel();
}
});
btnChangeValue.setBounds(63, 308, 305, 23);
frame.getContentPane().add(btnChangeValue);
tablePendingVisits = new JTable();
scrollPanePendingVisits = new JScrollPane();
pendingVisitModel = new PendingVisitModel();
tablePendingVisits.setModel(pendingVisitModel);
scrollPanePendingVisits.setBounds(63, 36, 305, 246);
scrollPanePendingVisits.setViewportView(tablePendingVisits);
frame.getContentPane().add(scrollPanePendingVisits);
}
public void refreshTableModel(){
String[] sampleString = {"9:00", "Bobby", "Tables"};
// search for row with 9:00 and replace values in given columns
for (int i = 0; i < pendingVisitModel.getRowCount(); i++) {
if( sampleString[0].equals(pendingVisitModel.getValueAt(i, 0)) ) { // Change row values when both hours are equal
pendingVisitModel.setValueAt(sampleString[1], i, 1); // Change at type column
pendingVisitModel.setValueAt(sampleString[2], i, 2); // Change at status column
}
}
}
}
// Custom TableModel
class PendingVisitModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private String[] columnNames = {"HOUR", "TYPE", "STATE"};
private Vector<String[]> data = new Vector<String[]>();
public PendingVisitModel() {
for(int i = 8; i<15; i++) {
data.add(new String[]{i+":00", "-", "Free"} );
data.add(new String[]{i+":15", "-", "Free"} );
data.add(new String[]{i+":30", "-", "Free"} );
data.add(new String[]{i+":45", "-", "Free"} );
}
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.size();
}
public String getColumnName(int col) {
return columnNames[col];
}
public String getValueAt(int row, int col) {
String[] temp = data.get(row);
if(temp.length > 0 && col < 3)
return temp[col];
else
return null;
}
public void setValueAt(String[] value, int row, int col) {
String[] temp = data.get(row);
temp[col] = value[col];
data.set(row, temp);
fireTableRowsUpdated(row, row);
}
public void insertRow(String[] value) {
data.add(value);
fireTableRowsInserted(data.size(), data.size());
}
public void clearRows(){
data.clear();
fireTableDataChanged();
}
public void removeAllEntry(){
data.clear();
fireTableDataChanged();
}
public boolean isCellEditable(int row, int col) {
return false;
}
#Override
public Class<String> getColumnClass(int colNum) {
return String.class;
}
}
PendingVisitModel pendingVisitModel = new PendingVisitModel();
It looks to me like the pendingVisitModel is defined as a local variable. This is the model you add to the table.
The refreshTableModel() method is referencing a pendingVisitModelvariable, but I would guess this is an instance variable that is NOT used by the table.
Get rid of local instance of your pendingVisitModel.
fireTableDataChanged();
Also don't keep using firTableDataChanged in all your TableModel methods. The API provides other methods that are more appropriate for different events.
Edit:
public void setValueAt(String[] value, int row, int col) {
You did not override the setValueAt(...) method. The value parameter is an Object not a String array.
Whenever you override a method of a class you should use the #Override annotation before the method. This way the compiler will give you an error if you override a method incorrectly (saving hours of frustration...).
#Override
public void setValueAt(Object value, int row, int col) {

Get value jtable after filter (jtable on combobox multiple column)

You can copy the search in combobox test run and after the filter selected it still get the original value.
You can see my code and complete the data received after the data filtering help me, more than a week searching but could not do it. hope you help. Thank you very much
Form.java:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.JTextField;
public class Form implements Runnable {
private static JFrame frame;
private static DetailedComboBox combo;
private static JTextField textField;
private static DetailedComboBox comboLop;
private static JTextField textField2;
/**
* #wbp.parser.entryPoint
*/
#Override
public void run() {
// TODO Auto-generated method stub
List<List<?>> tableData = new ArrayList<List<?>>();
tableData.add(new ArrayList<String>(
Arrays.asList("0","Nam", "Phan Nam", "1")));
tableData.add(new ArrayList<String>(
Arrays.asList( "1","Long", "Dinh Hoang Long", "2")));
tableData.add(new ArrayList<String>(
Arrays.asList( "3","Luc", "Phan Cong Luc", "4")));
tableData.add(new ArrayList<String>(
Arrays.asList( "4","Hoang", "Tran Hoang", "5")));
String[] columns = new String[]{"ID","Name", "Full Name", "Capital"};
int[] widths = new int[]{0,80, 200, 100};
combo = new DetailedComboBox(columns, widths, 0);
combo.setEditable(true);
// comboNhanVien.setEditable(true);
combo.setBounds(58, 50, 154, 23);
combo.setTableData(tableData);
combo.setSelectedIndex(-1);
combo.setPopupAlignment(DetailedComboBox.Alignment.LEFT);
combo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
showDetails();
}
});
/* combo.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent arg0) {
combo.showPopup();
combo.hidePopup();
}
#Override
public void focusLost(FocusEvent arg0) {
//combo.hidePopup();
}
});*/
combo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
System.out.print(e.getKeyCode());
String value= combo.getEditor().getItem().toString();
if (value.trim().length() == 0 && e.getKeyCode() != 40) {
combo.hidePopup();
// System.out.print(e.getKeyCode());
}else {
combo.showPopup();
//System.out.print("X: "+e.getKeyCode());
}
}
});
List<List<?>> tableDataLop = new ArrayList<List<?>>();
tableDataLop.add(new ArrayList<String>(
Arrays.asList("0","Class A")));
tableDataLop.add(new ArrayList<String>(
Arrays.asList("1","Class B")));
String[] columnsLop = new String[]{"ID","Class"};
int[] widthsLop = new int[]{0,100};
comboLop = new DetailedComboBox(columnsLop, widthsLop, 0);
comboLop.setEditable(true);
// comboNhanVien.setEditable(true);
comboLop.setBounds(58, 50, 154, 23);
comboLop.setTableData(tableDataLop);
comboLop.setSelectedIndex(-1);
comboLop.setPopupAlignment(DetailedComboBox.Alignment.LEFT);
comboLop.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
showDetailsLop();
}
});
comboLop.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
System.out.print(e.getKeyCode());
String value= comboLop.getEditor().getItem().toString();
if (value.trim().length() == 0 && e.getKeyCode() != 40) {
comboLop.hidePopup();
// System.out.print(e.getKeyCode());
}else {
comboLop.showPopup();
//System.out.print("X: "+e.getKeyCode());
}
}
});
comboLop.setEditable(true);
comboLop.setBounds(58, 94, 154, 23);
JPanel p = new JPanel();
p.setLayout(null);
p.add(combo);
p.add(comboLop);
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(p, BorderLayout.CENTER);
p.add(getTextField());
p.add(getTextField2());
//frame.getContentPane().setLayout(null);
//frame.getContentPane().add(comboNhanVien);
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
/**
* Launch the application.
*/
/**
* Create the application.
*/
private static void showDetails()
{
List<? extends Object> rowData = combo.getSelectedRow();
textField.setText(rowData.get(1).toString());
// capital.setText(rowData.get(2).toString());
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Form());
}
private JTextField getTextField() {
if (textField == null) {
textField = new JTextField();
textField.setBounds(234, 52, 86, 20);
textField.setColumns(10);
}
return textField;
}
private JTextField getTextField2() {
if (textField2 == null) {
textField2 = new JTextField();
textField2.setColumns(10);
textField2.setBounds(234, 96, 86, 20);
}
return textField2;
}
private static void showDetailsLop()
{
List<? extends Object> rowData = comboLop.getSelectedRow();
textField2.setText(rowData.get(1).toString());
// capital.setText(rowData.get(2).toString());
}
}
and DetailedComboBox.java
import java.awt.Dimension;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.plaf.metal.*;
import javax.swing.table.*;
/**
* A JComboBox that has a JTable as a drop-down instead of a JList
*/
#SuppressWarnings({ "rawtypes", "serial" })
public class DetailedComboBox extends JComboBox
{
public static enum Alignment {LEFT, RIGHT}
private List<List<? extends Object>> tableData;
private String[] columnNames;
private int[] columnWidths;
private Alignment popupAlignment = Alignment.LEFT;
/**
* Construct a TableComboBox object
*/
public DetailedComboBox(String[] colNames, int[] colWidths,
int displayColumnIndex)
{
super();
this.columnNames = colNames;
this.columnWidths = colWidths;
setUI(new TableComboBoxUI());
setEditable(false);
}
/**
* Set the type of alignment for the popup table
*/
public void setPopupAlignment(Alignment alignment)
{
popupAlignment = alignment;
}
/**
* Populate the combobox and drop-down table with the supplied data.
* If the supplied List is non-null and non-empty, it is assumed that
* the data is a List of Lists to be used for the drop-down table.
* The combobox is also populated with the column data from the
* column defined by <code>displayColumn</code>.
*/
#SuppressWarnings("unchecked")
public void setTableData(List<List<? extends Object>> tableData)
{
this.tableData = (tableData == null ?
new ArrayList<List<? extends Object>>() : tableData);
// even though the incoming data is for the table, we must also
// populate the combobox's data, so first clear the previous list.
removeAllItems();
// then load the combobox with data from the appropriate column
Iterator<List<? extends Object>> iter = this.tableData.iterator();
while (iter.hasNext())
{
List<? extends Object> rowData = iter.next();
// System.out.print(rowData.get(1));
addItem(rowData.get(1));
// addItem(rowData.get(displayColumn));
}
}
public List<? extends Object> getSelectedRow()
{
List<? extends Object> data = null;
if(tableData.get(getSelectedIndex()) != null){
data=tableData.get(getSelectedIndex());
}
System.out.println(data);
return data;
}
/**
* The handler for the combobox's components
*/
private class TableComboBoxUI extends MetalComboBoxUI
{
/**
* Create a popup component for the ComboBox
*/
#Override
protected ComboPopup createPopup()
{
return new TableComboPopup(comboBox, this);
}
/**
* Return the JList component
*/
public JList getList()
{
return listBox;
}
}
/**
* The drop-down of the combobox, which is a JTable instead of a JList.
*/
private class TableComboPopup extends BasicComboPopup
implements ListSelectionListener, ItemListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private final JTable table;
private TableComboBoxUI comboBoxUI;
private PopupTableModel tableModel;
private JScrollPane scroll;
// private JList list = new JList();
// private ListSelectionListener selectionListener;
// private ItemListener itemListener;
private void selectRow()
{
int index = comboBox.getSelectedIndex();
if (index != -1)
{
int idc=table.getRowCount();
if(idc>0){
//System.out.println("idc "+idc);
//table.setRowSelectionInterval(index, index);
//table.scrollRectToVisible(table.getCellRect(index, 0, true));
}
}
}
/**
* Construct a popup component that's a table
*/
public TableComboPopup(JComboBox combo, TableComboBoxUI ui)
{
super(combo);
this.comboBoxUI = ui;
tableModel = new PopupTableModel();
table = new JTable(tableModel);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getTableHeader().setReorderingAllowed(false);
TableColumnModel tableColumnModel = table.getColumnModel();
tableColumnModel.setColumnSelectionAllowed(false);
for (int index = 0; index < table.getColumnCount(); index++)
{
TableColumn tableColumn = tableColumnModel.getColumn(index);
tableColumn.setPreferredWidth(columnWidths[index]);
}
table.removeColumn(table.getColumnModel().getColumn(0));
scroll = new JScrollPane(table);
scroll.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
ListSelectionModel selectionModel = table.getSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
selectionModel.addListSelectionListener(this);
combo.addItemListener(this);
table.addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent event)
{
// java.awt.Point p = event.getPoint();
// int row = table.rowAtPoint(p);
//int row = table.convertRowIndexToModel(table.getEditingRow());
//System.out.println("row 2: "+row);
/// comboBox.setSelectedIndex(row);
//comboBox.getEditor().setItem("Text Has Changed");
hide();
}
});
table.setBackground(UIManager.getColor("ComboBox.listBackground"));
table.setForeground(UIManager.getColor("ComboBox.listForeground"));
}
/**
* This method is overridden from BasicComboPopup
*/
#Override
public void show()
{
if (isEnabled())
{
super.removeAll();
int scrollWidth = table.getPreferredSize().width +
((Integer) UIManager.get("ScrollBar.width")).intValue() + 1;
int scrollHeight = comboBoxUI.getList().
getPreferredScrollableViewportSize().height;
scroll.setPreferredSize(new Dimension(scrollWidth, scrollHeight));
super.add(scroll);
ListSelectionModel selectionModel = table.getSelectionModel();
selectionModel.removeListSelectionListener(this);
selectRow();
selectionModel.addListSelectionListener(this);
int scrollX = 0;
int scrollY = comboBox.getBounds().height;
if (popupAlignment == Alignment.RIGHT)
{
scrollX = comboBox.getBounds().width - scrollWidth;
}
show(comboBox, scrollX, scrollY);
// table.setRowSelectionInterval(0, 0);
comboBox.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
String value= comboBox.getEditor().getItem().toString();
//System.out.println("value: " +value);
TableRowSorter<TableModel> rowSorter
= new TableRowSorter<>(table.getModel());
table.setRowSorter(rowSorter);
if (value.trim().length() == 0) {
rowSorter.setRowFilter(null);
table.setRowSelectionInterval(0, 0);
}else {
rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + value,1));
int index = comboBox.getSelectedIndex();
if (index != -1)
{
int idc=table.getRowCount();
if(idc>0){
//System.out.println("idc "+idc);
//table.setRowSelectionInterval(index, index);
//table.scrollRectToVisible(table.getCellRect(index, 0, true));
}
}
}
}
});
}
}
/**
* Implemention of ListSelectionListener
*/
public void valueChanged(ListSelectionEvent event)
{
int index = table.getSelectedRow();
int row = table.convertRowIndexToView(table.getEditingRow());
System.out.println("B "+row);
if (index != -1)
{
//System.out.print("B "+index);
comboBox.setSelectedIndex(table.getSelectedRow());
}
}
#Override
public void itemStateChanged(ItemEvent arg0) {
// TODO Auto-generated method stub
}
}
/**
* A model for the popup table's data
*/
private class PopupTableModel extends AbstractTableModel
{
/**
* Return the # of columns in the drop-down table
*/
public int getColumnCount()
{
return columnNames.length;
}
/**
* Return the # of rows in the drop-down table
*/
public int getRowCount()
{
return tableData == null ? 0 : tableData.size();
}
/**
* Determine the value for a given cell
*/
public Object getValueAt(int row, int col)
{
if (tableData == null || tableData.size() == 0)
{
return "";
}
return tableData.get(row).get(col);
}
/**
* All cells in the drop-down table are uneditable
*/
#Override
public boolean isCellEditable(int row, int col)
{
return false;
}
/**
* Pull the column names out of the tableInfo object for the header
*/
#Override
public String getColumnName(int column)
{
String columnName = null;
if (column >= 0 && column < columnNames.length)
{
columnName = columnNames[column].toString();
}
return (columnName == null) ? super.getColumnName(column) : columnName;
}
}
}
You need to be aware of the model/view index relationship between the combo box, table and model.
Your combo box will always have all the entries so its index is equivalent to the model index.
The table may be filtered or not so you need to convert its index to the model index so you can set the combo box index.
If I understand what you are attempting to do then this is the change I made to your valueChanged() method:
public void valueChanged(ListSelectionEvent event)
{
int index = table.getSelectedRow();
if (index != -1)
{
int row = table.convertRowIndexToModel(index);
comboBox.setSelectedIndex(row);
}
}

JPanel in JComboBox dropdown but not in the editor

What I want seems relatively simple, and I'm almost there. This will eventually be used to extend TableCellEditor, so the size is important. What I want is something like this:
With a combination of custom ComboBoxEditors and ListCellRenderers I've been able to get something like this:
Which has the inconveniences of:
Cutting off any components beyond the original width of the JComboBox
Forcing the height of the JComboBox to be the height of the JPanel in the drop-down
Allowing only one (1) click modification of the form before the drop-down disappears.
I'd like to have the drop-down stay visible until the user clicks the editor or the JComboBox looses focus to another control, and then have the value in the editor update. There will only ever be one (1) JPanel in the drop-down and I don't want the editor to be able to actually edit the string displayed.
My question is similar to #ErkanHaspalut 's question here but neither response is satisfying. I'd previously made a similar attempt by embedding a JPanel in a JPopupMenu and adding it to a JTextField but had similar issues about the popup disappearing prematurely.
I've tried forcing the size of the JComboBox both by setting the setMaximumSize height value (which has no effect) and with
Rectangle tmp = cboTest.getBounds();
tmp.height = 24;
cboTest.setBounds(tmp);
which simply shows the top 24 lines of the JComboBox. A minimum compilable example would be
/*
* Program to test having a JPanel in the JComboBox's drop-down but not the JComboBox's editor.
*/
package testdropdownsubform;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javafx.application.Application;
import javafx.stage.Stage;
import javax.swing.ComboBoxEditor;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicComboBoxEditor;
/**
* #author Masked Coder
*/
public class Dim {
public Long DimWidth;
public Long DimHeight;
public Dim () {
DimWidth = 1L;
DimHeight = 1L;
}
#Override
public String toString() {
return DimWidth.toString() + "\' x " + DimHeight.toString() + "\'";
}
}
public class DimPanel extends javax.swing.JPanel {
public DimPanel() {
spnDimWidth = new javax.swing.JSpinner();
spnDimHeight = new javax.swing.JSpinner();
spnDimWidth.setModel(new javax.swing.SpinnerNumberModel(Long.valueOf(1L), Long.valueOf(0L), null, Long.valueOf(1L)));
spnDimWidth.setPreferredSize(new java.awt.Dimension(50, 24));
addComponent(spnDimWidth);
lblTween.setText(" x ");
addComponent(lblTween);
spnDimHeight.setModel(new javax.swing.SpinnerNumberModel(Long.valueOf(1L), Long.valueOf(0L), null, Long.valueOf(1L)));
spnDimHeight.setPreferredSize(new java.awt.Dimension(50, 24));
addComponent(spnDimHeight);
}
private javax.swing.JSpinner spnDimWidth;
private javax.swing.JLabel lblTween;
private javax.swing.JSpinner spnDimHeight;
}
public class DimListCellRenderer implements ListCellRenderer {
private DimPanel dpDim;
public DimListCellRenderer(DimPanel newDimPanel) {
dpDim = newDimPanel;
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Dim dValue = (Dim) value;
dpDim.setDim(dValue);
return dpDim;
}
}
public class DimComboBoxEditor extends BasicComboBoxEditor implements ComboBoxEditor {
JTextField txtDim = new JTextField();
Dim Item = new Dim();
public DimComboBoxEditor() {
txtDim.setEnabled(false);
txtDim.setOpaque(true);
}
#Override
public Component getEditorComponent() {
txtDim.setText(Item.toString());
return txtDim;
}
#Override
public void setItem(Object anObject) {
Item = (Dim) anObject;
}
#Override
public Object getItem() {
return Item;
}
#Override
public void selectAll() {
txtDim.selectAll();
}
#Override
public void addActionListener(ActionListener l) {
txtDim.addActionListener(l);
}
#Override
public void removeActionListener(ActionListener l) {
txtDim.removeActionListener(l);
}
}
public class MainTestForm extends javax.swing.JFrame {
public MainTestForm() {
lblPrevComponent = new javax.swing.JLabel();
chkPrevComponent = new javax.swing.JCheckBox();
lblTest = new javax.swing.JLabel();
cboTest = new JComboBox<testdropdownsubform.DicePanel>();
lblNextComponent = new javax.swing.JLabel();
scpNextComponent = new javax.swing.JScrollPane();
txaNextComponent = new javax.swing.JTextArea();
btnForceHeight = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
lblPrevComponent.setText("Prev. Component");
chkPrevComponent.setText("jCheckBox1");
lblTest.setText("Dimension");
cboTest.setEditable(true);
cboTest.setEditor(new DimComboBoxEditor());
cboTest.setRenderer(new DimListCellRenderer(new DimPanel()));
cboTest.addItem(new Dim());
lblNextComponent.setText("Next Component");
txaNextComponent.setColumns(20);
txaNextComponent.setRows(5);
scpNextComponent.setViewportView(txaNextComponent);
btnForceHeight.setText("Force");
btnForceHeight.setToolTipText("Force test combobox height");
btnForceHeight.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnForceHeightActionPerformed(evt);
}
});
.addComponent(lblPrevComponent)
.addComponent(chkPrevComponent))
.addComponent(lblTest)
.addComponent(cboTest)
.addComponent(lblNextComponent)
.addComponent(scpNextComponent)
.addComponent(btnForceHeight))
}
private void btnForceHeightActionPerformed(java.awt.event.ActionEvent evt) {
Rectangle tmp = cboTest.getBounds();
tmp.height = 24;
cboTest.setBounds(tmp);
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new MainTestForm().setVisible(true);
}
});
}
private javax.swing.JButton btnForceHeight;
private javax.swing.JComboBox cboTest;
private javax.swing.JCheckBox chkPrevComponent;
private javax.swing.JLabel lblNextComponent;
private javax.swing.JLabel lblPrevComponent;
private javax.swing.JLabel lblTest;
private javax.swing.JScrollPane scpNextComponent;
private javax.swing.JTextArea txaNextComponent;
}
public class TestDropdownSubform extends Application {
#Override
public void start(Stage primaryStage) {
MainTestForm mtfMain = new MainTestForm();
mtfMain.setVisible(true);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
My question is, am I missing a detail, or can anyone see a better way to accomplish this? Thanks in advance for your advice.
Edit: Declaring the JComboBox as
JComboBox cboTest = new JComboBox<DimPanel>() {
private boolean layingOut = false;
#Override
public void doLayout(){
try{
layingOut = true;
super.doLayout();
}finally{
layingOut = false;
}
}
#Override
public Dimension getSize(){
Dimension dim = super.getSize();
if(!layingOut) {
dim.width = Math.max(dim.width, dpThis.getPreferredSize().width);
}
return dim;
}
};
fixes the width of the drop-down. Declaring it as
JComboBox cboTest = new JComboBox<testdropdownsubform.DicePanel>() {
private boolean layingOut = false;
#Override
public void doLayout(){
try{
layingOut = true;
super.doLayout();
}finally{
layingOut = false;
}
}
#Override
public Dimension getSize(){
Dimension dim = super.getSize();
if(!layingOut) {
Dimension dim2 = dpThis.getPreferredSize();
dim.width = Math.max(dim.width, dim2.width);
// dim.height = dim2.height;
}
return dim;
}
#Override
public DimPanel getPrototypeDisplayValue() {
DimPanel tmpPanel = new DimPanel();
if(isCalledFromComboPopup()) {
//
}
else {
Dimension r = dcbeEditor.getPreferredSize();
tmpPanel.setPreferredSize(r);
}
return tmpPanel;
}
/**
* Hack method to determine if called from within the combo popup UI.
*/
public boolean isCalledFromComboPopup() {
try {
final Throwable t = new Throwable();
t.fillInStackTrace();
StackTraceElement[] st = t.getStackTrace();
// look only at top 5 elements of call stack
int max = Math.min(st.length, 5);
for (int i=0; i<max; ++i) {
final String name = st[i].getClassName();
System.out.println(i + ") " + name);
return ((name != null) && name.contains("ComboPopup"));
}
} catch (final Exception e) {
// if there was a problem, assume not called from combo popup
}
return false;
}
};
fixes the editor size but now the drop-down is the width and height of the editor.
So, I ended up going on a different track, creating my own pseudo-combobox with a JTextField and a JButton in a JPanel. I believe I've quite handily genericized the result into four related classes. First, you need an implementation of AbstractPopupPanel (which quite handily loads into the NetBeans designer if you're using it):
package complexcombobox;
/**
*
* #author MaskedCoder
*
* #param E - the object to be edited in the panel
*/
public abstract class AbstractPopupPanel<E> extends javax.swing.JPanel {
/**
* Creates new form PopupPanel
*/
public AbstractPopupPanel() {
initComponents();
}
public abstract boolean isChanged();
public abstract E getValue();
public abstract void setValue(E newValue);
private void initComponents() {
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
}
}
With that created to your satisfaction, we need to place it in a pop-up menu:
package complexcombobox;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
/**
*
* #author MaskedCoder
*
* #param E - the object to be edited
* #param P - the extension of AbstractPopupPanel that will display the object as you wish
*/
public class ComplexPopup<E, P extends AbstractPopupPanel<E>> extends JPopupMenu {
/*
* Interface to notify user of editing
*/
public interface EditListener<E> {
// return the object's value to be edited.
// if the user returns null, the existing Dice is used.
public E beforeEdit();
// receives the new value
// called *only* when the object has actually changed.
public void afterEdit(E newValue);
}
/*
*internal variables
*/
private P thisPanel;
private EditListener<E> userListener;
// private Class<E> eClass;
// private Class<P> pClass;
private PopupMenuListener myPopupListener = new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
if(userListener != null) {
E tmpSize = userListener.beforeEdit();
if(tmpSize != null) thisPanel.setValue(tmpSize);
}
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
if(userListener != null) {
// if(thisPanel.isChanged())
userListener.afterEdit((E) thisPanel.getValue());
}
}
#Override
public void popupMenuCanceled(PopupMenuEvent e) {
popupMenuWillBecomeInvisible(e);
}
};
/*
* Constructors
*/
public ComplexPopup(E iniValue, P iniDropdownPanel, EditListener<E> iniListener) {
super();
init(iniValue, iniDropdownPanel, iniListener);
}
private void init(E iniValue, P iniDropdownPanel, EditListener<E> iniListener) {
thisPanel = iniDropdownPanel;
thisPanel.setValue(iniValue);
add(thisPanel);
userListener = iniListener;
this.addPopupMenuListener(myPopupListener);
}
/*
* Public Properties
*/
public E getValue() {
return (E) thisPanel.getValue();
}
public void setValue(E newObjectSize) {
thisPanel.setValue(newObjectSize);
}
public EditListener getUserListener() {
return userListener;
}
public void setEditListener(EditListener newListener) {
userListener = newListener;
}
/*
* functional extensions
*/
public void afterEdit(E newValue) {
if(userListener != null) {
if(thisPanel.isChanged())
userListener.afterEdit((E) thisPanel.getValue());
}
}
}
This places the form in a JPopupMenu and allows access to the object to be edited as well as notification before and after editing. Now, I couldn't co-opt the JComboBox interface without getting deeper into the mechanics than I wanted to go, so I fudged the combo box, as I mentioned:
package complexcombobox;
/**
*
* #author MaskedCoder
*
* #param <I> the Item (Object) to be edited
* #param <Q> the AbstractPopupPanel to be used to do the editing.
*/
public class ComplexComboBox<I, Q extends AbstractPopupPanel<I>> extends javax.swing.JPanel {
private ComplexPopup<I, Q> thePopup;
private ComplexPopup.EditListener<I> myListener = new ComplexPopup.EditListener<I>() {
#Override
public I beforeEdit() {
return null; // no changes so, just let it ride.
}
#Override
public void afterEdit(I newValue) {
txtEditor.setText(thePopup.getValue().toString());
}
};
/**
* Creates new form ObjectSizeComboBox
*/
public ComplexComboBox(I iniValue, Q iniPanel) {
initComponents();
thePopup = new ComplexPopup<I, Q>(iniValue, iniPanel, myListener);
}
public I getValue() {
return thePopup.getValue();
}
public void setValue(I newValue) {
thePopup.setValue(newValue);
}
private void initComponents() {
txtEditor = new javax.swing.JTextField();
btnShowPanel = new javax.swing.JButton();
setBackground(new java.awt.Color(255, 255, 255));
setMinimumSize(new java.awt.Dimension(40, 19));
txtEditor.setEditable(false);
txtEditor.setToolTipText("");
txtEditor.setOpaque(false);
btnShowPanel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/testpopupsubform/art/dropdownarrow.png"))); // NOI18N
btnShowPanel.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnShowPanelActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(txtEditor, javax.swing.GroupLayout.DEFAULT_SIZE, 115, Short.MAX_VALUE)
.addGap(1, 1, 1)
.addComponent(btnShowPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(btnShowPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(txtEditor)
);
}
private void btnShowPanelActionPerformed(java.awt.event.ActionEvent evt) {
thePopup.show(txtEditor, 0, txtEditor.getHeight());
}
private javax.swing.JButton btnShowPanel;
private javax.swing.JTextField txtEditor;
}
This seems to work very well in a cell editor for a table, provided you make the row heights tall enough. That's no different than a regular JComboBox editor, though:
package complexcombobox;
import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;
/**
*
* #author MaskedCoder
*
* #param <E> the Element (Object) to be edited
* #param <P> the AbstractPopupPanel to be used to do the editing.
*/
public class ComplexCellEditor<E, P extends AbstractPopupPanel<E>> extends AbstractCellEditor
implements TableCellEditor {
private ComplexComboBox<E, P> ComplexEditor;
/*
* Constructors
*/
public ComplexCellEditor(E iniValue, P iniPanel) {
super();
ComplexEditor = new ComplexComboBox<E, P>(iniValue, iniPanel);
}
/*
* AbstractCellEditor Extensions
*/
#Override
public boolean stopCellEditing() {
return super.stopCellEditing();
}
/*
* TableCellEditor Implementation
*/
#Override
public E getCellEditorValue() {
return ComplexEditor.getValue();
}
#Override
public Component getTableCellEditorComponent(JTable iniTable, Object iniValue, boolean isSelected, int row, int column) {
ComplexEditor.setValue((E) iniValue);
return ComplexEditor;
}
}
Three things I'm not entirely satisfied with:
- I'd like to grab the JComboBox down-arrow icon from the system rather than relying on a project resource
- I've had to pass through the AbstractPopupPanel instance which means that the coder has to create the panel themselves and then hand it to the constructor for ComplexComboBox or ComplexCellEditor. I'd rather have created the panel internally, in ComplexPopup and managed it myself. I ran into difficulty instantiating Generic constructors. Pass-through does allow the coder more control but I don't think that's needed and I'd like it to be optional at least.
My knowledge of Generics isn't advanced but this seems to do the trick.

Seperate Char & Int into different JTable columns [duplicate]

I have a JTable populated with a custom DataModel (pasted below) and when I call the populate() method, it appears to populate the table with duplicate data - each row is filled with the same value over and over again. However, on closer inspection (by simply println()ing the 'data' field), the data model isn't at fault - it holds correct data, in the format I expect. What gives?
import java.util.ArrayList;
import javax.swing.table.AbstractTableModel;
#SuppressWarnings("serial") // we don't expect this app to ever use serialized classes. EVER.
public class CollectionDataModel extends AbstractTableModel {
private ArrayList<ArrayList<String>> data;
public CollectionDataModel() {
data = new ArrayList<ArrayList<String>>();
}
#Override
public int getColumnCount() {
if(data.isEmpty()) return 0;
return data.get(0).size();
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
if(rowIndex > getRowCount()) return null;
if(columnIndex > getColumnCount()) return null;
return data.get(rowIndex).get(columnIndex);
}
public void populate(Collection c) {
data.clear();
for(Item i : c.getItems()) {
ArrayList<String> row = new ArrayList<String>();
for(Property p : i.getProperties().values()) {
row.add(p.toString());
}
data.add(row);
}
fireTableDataChanged();
}
}
Here's a complete example that may prove helpful. As the sample Map is unmodifiable, I refer you to #mKorbel's example on how to override isCellEditable() and setValueAt().
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
/** #see https://stackoverflow.com/questions/9132987 */
public class EnvTableTest extends JPanel {
public EnvTableTest() {
this.setLayout(new GridLayout());
this.add(new JScrollPane(new JTable(new EnvDataModel())));
}
private static class EnvDataModel extends AbstractTableModel {
private Map<String, String> data = System.getenv();
private String[] keys;
public EnvDataModel() {
keys = data.keySet().toArray(new String[data.size()]);
}
#Override
public String getColumnName(int col) {
if (col == 0) {
return "Key";
} else {
return "Value";
}
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int col) {
if (col == 0) {
return keys[row];
} else {
return data.get(keys[row]);
}
}
}
private void display() {
JFrame f = new JFrame("EnvTableTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new EnvTableTest().display();
}
});
}
}
You could try to make the changes of populate more atomic.
public void populate(Collection c) {
ArrayList<ArrayList<String>> data2 = new ArrayList<ArrayList<String>>();
for(Item i : c.getItems()) {
ArrayList<String> row = new ArrayList<String>();
for(Property p : i.getProperties().values()) {
row.add(p.toString());
}
data2.add(row);
}
data = data2;
fireTableDataChanged();
}
I am guessing that populate is called again before a prior populate call finished. And probably c is changed during its iteration.
1) your TableModel is un_completed, I miss there lots or required methods for JTable's life_cycle, starting with TableHeader etc.
2) since there are lots of AbstactTableModels based on HashMap, I'd suggest to return arrays type implemented in API directly
Vector<Vector<Object or String>> data;
String[][] or Object[][]
instead of
ArrayList<ArrayList<String>> data;
simple explanations is that XxxList returs column and Vector or String[] returns Row
3) I'd suggest to use DefaultTableModel directly then you'll never need to solve duplicates or missed column/row

Sluggish Performance Using JTable Displaying Streaming Data

The code I'm referencing is proprietary and requires a multicast server, so I can't post an SSCCE snippet. I understand this may preclude any helpful insight which would elicit viable responses...
I'm compiling with Java 7 u 9.
I'm currently using a JTable in a GUI app that listens to multicast data, and displays it as it arrives. When scrolling the table or resizing columns, the app responds excruciatingly slow.
I thought I structured my code appropriately.
I used a wrapper class, and in it's main() function, it creates an instance of itself, which processes command line arguments, creates listeners, creates the JFrame and calls the class that returns a JTable. This is all done outside of the event dispatch thread.
Then, in the next line, I used the invokeLater() method to create a thread that handles all of the GUI rendering. It creates a JScrollPane, adds the JTable to the scroll pane, sets the scroll bars, sets the viewport, sets the scroll mode, and adds the JScrollPane to a JFrame . This is all handled within the event dispatch thread.
The rows typically populate fairly quick, with the occasional screen freeze (some of the rows contain 30 or more lines), but the responsiveness seems acceptable. However, when scrolling or resizing columns, the response is very slow.
All of the examples I've seen, including the SwingX SwingLabs demo all refer to an initial dataset that is loaded up front. I need an example of using a JTable with streaming data.
Can anyone point me to such an example/demo?
This is my main() snippet...
public static void main(String args[])
{
final JXTrapTableGUI ttg = new JXTrapTableGUI(args);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
ttg.createAndShowGUI();
}
});
}
PS. I want to thank each and everyone who responded. I've been pulled off this project until March 11th, but I will review all responses on that date.
I do not think that JTable works well with streaming data at all. All of the optimization techniques you mentioned, like keeping processing off of the Event Dispatch Thread, are irrlevant if the TableModel does not contain a real list but instead some connection to a data stream.
Without seeing how you attempted to handle this, it's hard to know exactly why it's slow. But here is how I would make it repsonsive:
Create a ListModel that stores a List - not a reference to a stream, just a plain List.
Have another Thread capture Multicast Data from the stream, lets call it DataStreamCollector.
Then launch a Thread that runs on a timer (javax.swing.Timer) that checks with the DataStreamCollector and updates the ListModel as needed.
My design here is assuming that UI responsiveness is more important than 100% synchronization with the data stream. Adjusting the timers should let you trade off having an up-to-date table with having a responsive UI.
Somewhere lost in Oracles clean_up to the trash (old Suns tutorials),
This project was called ChristmastTree, is about JTable & Performance,
Standard Java code before crazy & messy SwingWorker invoked from black hole called Executor
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.table.*;
/**
* CTTable extends JTable doing the following: <ul> <li>The UI is forced to be
* CTTableUI so that a customer CellRendererPane can be installed.
* <li>getCellRenderer is overriden to return the TableCellRenderer passed into
* the constructor. <li>tableChanged is overriden to pass the call to super only
* if the cell is visible. </ul>
*/
public class CTTable extends JTable {
private static final long serialVersionUID = 1L;
private CTTableCellRenderer renderer;
public CTTable(CTTableCellRenderer renderer) {
super();
this.renderer = renderer;
renderer.setFont(getFont());
}
#Override
public void updateUI() {
super.updateUI();
//Force the UI to be an instanceof CTTableUI. This approach will not work
//if you need to support more than one look and feel in your application.
setUI(new CTTableUI());
}
#Override
public void setFont(Font font) {
super.setFont(font);
if (renderer != null) {
renderer.setFont(font);
}
}
#Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderer;
}
#Override
public void tableChanged(TableModelEvent e) {
if (e instanceof VisibleTableModelEvent && !((VisibleTableModelEvent) e).isVisible(this)) {
return;// Do nothing if this cell isn't visible.
}
super.tableChanged(e);
}
private static class CTTableUI extends BasicTableUI {
#Override
public void installUI(JComponent c) {
super.installUI(c);// Overriden to install our own CellRendererPane
c.remove(rendererPane);
rendererPane = new CTCellRendererPane();
c.add(rendererPane);
}
}
/**
* CTCellRendererPane overrides paintComponent to NOT clone the Graphics
* passed in and NOT validate the Component passed in. This will NOT work if
* the painting code of the Component clobbers the graphics (scales it,
* installs a Paint on it...) and will NOT work if the Component needs to be
* validated before being painted.
*/
private static class CTCellRendererPane extends CellRendererPane {
private static final long serialVersionUID = 1L;
private Rectangle tmpRect = new Rectangle();
#Override
public void repaint() {
// We can safely ignore this because we shouldn't be visible
}
#Override
public void repaint(int x, int y, int width, int height) {
}
#Override
public void paintComponent(Graphics g, Component c, Container p, int x,
int y, int w, int h, boolean shouldValidate) {
if (c == null) {
if (p != null) {
Color oldColor = g.getColor();
g.setColor(p.getBackground());
g.fillRect(x, y, w, h);
g.setColor(oldColor);
}
return;
}
if (c.getParent() != this) {
this.add(c);
}
c.setBounds(x, y, w, h);
// As we are only interested in using a JLabel as the renderer,
//which does nothing in validate we can override this to do nothing,
//if you need to support components that can do layout, this will
//need to be commented out, or conditionally validate.
shouldValidate = false;
if (shouldValidate) {
c.validate();
}
boolean wasDoubleBuffered = false;
JComponent jc = (c instanceof JComponent) ? (JComponent) c : null;
if (jc != null && jc.isDoubleBuffered()) {
wasDoubleBuffered = true;
jc.setDoubleBuffered(false);
}//Don't create a new Graphics, reset the clip and translate the origin.
Rectangle clip = g.getClipBounds(tmpRect);
g.clipRect(x, y, w, h);
g.translate(x, y);
c.paint(g);
g.translate(-x, -y);
g.setClip(clip.x, clip.y, clip.width, clip.height);
if (wasDoubleBuffered) {
jc.setDoubleBuffered(true);
}
c.setBounds(-w, -h, 0, 0);
}
}
}
.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
/**
* A custom TableCellRenderer that overrides a handful of methods: <ul>
* <li>isOpaque and setBackground are overridden to avoid filling the
* background, if possible. <li>firePropertyChange is overridden to do nothing.
* If you need to support HTML text in the renderer than this should NOT be
* overridden. <li>paint is overridden to forward the call directly to the UI,
* avoiding the creation of a Graphics. This will NOT work if you need the
* renderer to contain other childre or the Graphics is clobbered as part of
* painting the UI. </ul>
*/
public class CTTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private Color background;
private Color foreground;
private Color editableForeground;
private Color editableBackground;
private Border focusBorder;
public CTTableCellRenderer() {
focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
editableForeground = UIManager.getColor("Table.focusCellForeground");
editableBackground = UIManager.getColor("Table.focusCellBackground");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
boolean negative = (value != null && ((Integer) value).intValue() < 0);
// Reset the background based on the sign of the value.
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
if (!negative) {
setBackground(null);
} else {
setBackground(Color.red);
}
}//NOTICE that we do NOT set the font here, because CTTable knows about
//us, it will set the font as appropriate.
if (hasFocus) {
setBorder(focusBorder);
if (table.isCellEditable(row, column)) {
setForeground(editableForeground);
setBackground(editableBackground);
}
} else {
setBorder(noFocusBorder);
}
setValue(value);
return this;
}
#Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// As long as you don't have any HTML text, this override is ok.
}
#Override// This override is only appropriate if this will never contain
// any children AND the Graphics is not clobbered during painting.
public void paint(Graphics g) {
ui.update(g, this);
}
#Override
public void setBackground(Color c) {
this.background = c;
}
#Override
public Color getBackground() {
return background;
}
#Override
public void setForeground(Color c) {
this.foreground = c;
}
#Override
public Color getForeground() {
return foreground;
}
#Override
public boolean isOpaque() {
return (background != null);
}
#Override // This is generally ok for non-Composite components (like Labels)
public void invalidate() {
}
#Override // Can be ignored, we don't exist in the containment hierarchy.
public void repaint() {
}
}
.
import javax.swing.table.*;
import java.util.*;
/**
* CTTableModel, a TableModel, models a set of Datas as the rows. The data is
* stored in a List of Lists. As the changes come in against a particular Data
* object we also contain a map from Data to row. This can obviously be made
* faster by pushing the row to the Data, but this may not be feasable in
* applications of this sort.
*/
public class CTTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
/**
* Maps from Data to an integer id giving the row of the data.
*/
private Map rowMap;
/**
* Number of columns to display.
*/
private int columns;
/**
* A List of Lists.
*/
private java.util.List rowData;
/**
* If true, batch cell updates using sharedModelEvent.
*/
private boolean batchChange;
/**
* Shared model event.
*/
private VisibleTableModelEvent sharedModelEvent;
public CTTableModel(int columns) {
this.columns = columns;
// Notice how they are not synchronized, we do NOT access this class
// from another thread, and therefore do not have to worry about
// synchronization.
rowData = new ArrayList();
rowMap = new HashMap();
}
public void addRow(Data rowID) {
int row = rowData.size();
rowMap.put(rowID, new Integer(row));
ArrayList colData = new ArrayList();
for (int counter = 0; counter < columns; counter++) {
colData.add(null);
}
rowData.add(colData);
fireTableRowsInserted(row, row);
}
/**
* Toggles batch updates. When true and model changes are notified using a
* VisibleTableModelEvent.
*
* #param batch
*/
public void setBatchUpdates(boolean batch) {
this.batchChange = batch;
if (sharedModelEvent == null) {
sharedModelEvent = new VisibleTableModelEvent(this);
}
sharedModelEvent.reset();
}
public boolean getBatchUpdates() {
return batchChange;
}
/**
* Sets the display value for a particular Data item at a particular cell.
* If notify is true listeners are notified, otherwise no listeners are
* notified.
*
* #param rowID
* #param col
* #param data
* #param notify
*/
public void set(Data rowID, int col, Object data, boolean notify) {
int row = ((Integer) rowMap.get(rowID)).intValue();
((java.util.List) rowData.get(row)).set(col, data);
if (notify) {
if (batchChange) {
sharedModelEvent.set(row, col);
fireTableChanged(sharedModelEvent);
} else {
fireTableCellUpdated(row, col);
}
}
}
#Override
public int getRowCount() {
return rowData.size();
}
#Override
public int getColumnCount() {
return columns;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return ((java.util.List) rowData.get(rowIndex)).get(columnIndex);
}
}
.
/**
* Unique ID for the data.
*/
public class Data {
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*
* #param x
*/
#Override
public boolean equals(Object x) {
return (this == x);
}
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*/
#Override
public int hashCode() {
return super.hashCode();
}
}
.
import java.util.ArrayList;
/**
* DataChange is used to associate a Data Object with a column identifier that
* has changed. To avoid loads of garbage per update DataChanges are cached and
* reused.
*/
public class DataChange {
private static ArrayList sharedDataChanges = new ArrayList();
private Data data;
private int col;
private int hashCode;
/**
* Obtains a DataChange for the specified Data and column.
*
* #param data
* #param col
* #return
*/
public static DataChange getDataChange(Data data, int col) {
synchronized (sharedDataChanges) {
int size = sharedDataChanges.size();
if (size > 0) {
DataChange change = (DataChange) sharedDataChanges.remove(size - 1);
change.data = data;
change.col = col;
return change;
}
}
return new DataChange(data, col);
}
/**
* Indicates the DataChange is no longer needed and can be reused.
*
* #param change
*/
public static void releaseDataChange(DataChange change) {
synchronized (sharedDataChanges) {
sharedDataChanges.add(change);
}
}
DataChange(Data data, int col) {
this.data = data;
this.col = col;
hashCode = (data.hashCode() | col);
}
public Data getData() {
return data;
}
public int getColumn() {
return col;
}
#Override
public int hashCode() {
return hashCode;
}
public boolean equals(DataChange dc) {
if (dc == this) {
return true;
}
DataChange o = (DataChange) dc;
return (o.data == data && o.col == col);
}
}
.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* The Main controller, responsible for wiring everything together. Pressing
* return in any of the fields will trigger recreation of everything.
*/
public class Main implements ActionListener {
// properties: columnCount, rowCount, updateSleepTime, eqSleepTime,
// threshold, generateSleep, generatorBatchCount
private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
private JTextField columnCount;
private JTextField rowCount;
private JTextField updateSleepTime;
private JTextField eqSleepTime;
private JTextField threshold;
private JTextField generateSleep;
private JTextField generatorBatchCount;
private JFrame frame;
static JLabel totalUpdateTime;
static JLabel notifyTime;
static JLabel paintTime;
static JLabel updateCount;
private JTable table;
private UpdateThread updateThread;
private GeneratorThread generatorThread;
private CTTableModel tableModel;
private static int NUM_COLUMNS = 40;// Initial values for the 7 properties.
private static int NUM_ROWS = 3000;
private static int UPDATE_SLEEP_TIME = 500;
private static int EQ_SLEEP_TIME = 10;
private static int UPDATE_ALL_THRESHOLD = 400000;
private static int GENERATOR_SLEEP_TIME = 40;
private static int BATCH_SIZE = 1000;
Main() {
frame = new JFrame();
frame.getContentPane().setLayout(new GridBagLayout());
columnCount = add("Columns: ", NUM_COLUMNS);
rowCount = add("Rows: ", NUM_ROWS);
updateSleepTime = add("Update Sleep: ", UPDATE_SLEEP_TIME);
eqSleepTime = add("EQ Sleep: ", EQ_SLEEP_TIME);
threshold = add("Update All Threshold: ", UPDATE_ALL_THRESHOLD);
generateSleep = add("Generator Sleep: ", GENERATOR_SLEEP_TIME);
generatorBatchCount = add("Batch Size: ", BATCH_SIZE);
table = new CTTable(new CTTableCellRenderer());
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane sp = new JScrollPane(table);
frame.getContentPane().add(sp, new GridBagConstraints(0, 3, 6, 1, 1, 1,
GridBagConstraints.WEST, GridBagConstraints.BOTH, EMPTY_INSETS, 0, 0));
ChangeListener changeListener = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
BoundedRangeModel m = (BoundedRangeModel) (e.getSource());
if (updateThread != null) {
updateThread.setUpdatesEnabled(!(m.getValueIsAdjusting()));
}
}
};
sp.getVerticalScrollBar().getModel().addChangeListener(changeListener);
sp.getHorizontalScrollBar().getModel().addChangeListener(changeListener);
totalUpdateTime = new JLabel(" ");
notifyTime = new JLabel(" ");
paintTime = new JLabel(" ");
updateCount = new JLabel(" ");
JPanel statusPanel = new JPanel(new GridBagLayout());
frame.getContentPane().add(statusPanel, new GridBagConstraints(0, 4, 6, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(totalUpdateTime, new GridBagConstraints(0, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(notifyTime, new GridBagConstraints(1, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(paintTime, new GridBagConstraints(2, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(updateCount, new GridBagConstraints(3, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
frame.setTitle("Christmas Tree Demo Application");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 1000, 800);
frame.setVisible(true);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
reset();
}
#Override
public void actionPerformed(ActionEvent ae) {
reset();
}
private JTextField add(String name, int defaultValue) {
Container parent = frame.getContentPane();
int row = parent.getComponentCount() / 6;
int col = parent.getComponentCount() % 6;
parent.add(new JLabel(name), new GridBagConstraints(col, row, 1, 1, 0, 0,
GridBagConstraints.WEST, 0, EMPTY_INSETS, 0, 0));
JTextField tf = new JTextField(Integer.toString(defaultValue));
tf.addActionListener(this);
parent.add(tf, new GridBagConstraints(col + 1, row, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
return tf;
}
private void reset() {
System.out.println("Columns: " + getInt(columnCount));
System.out.println("Rows: " + getInt(rowCount));
System.out.println("Update Sleep: " + getInt(updateSleepTime));
System.out.println("EQ Sleep: " + getInt(eqSleepTime));
System.out.println("Update All Threshold: " + getInt(threshold));
System.out.println("Generator Sleep: " + getInt(generateSleep));
System.out.println("Batch Size: " + getInt(generatorBatchCount));
if (updateThread != null) {
System.out.println("interrupting!");
updateThread.interrupt();
generatorThread.interrupt();
}
int cols = getInt(columnCount);
tableModel = new CTTableModel(cols);
ArrayList<Data> data = new ArrayList<Data>();
for (int counter = getInt(rowCount) - 1; counter >= 0; counter--) {
Data dataID = new Data();
data.add(dataID);
tableModel.addRow(dataID);
for (int colCounter = 0; colCounter < cols; colCounter++) {
if (colCounter % 2 == 0) {
tableModel.set(dataID, colCounter,
new Integer(counter * 100 + colCounter), false);
} else {
tableModel.set(dataID, colCounter,
new Integer(counter * -100 + colCounter), false);
}
}
}
table.setModel(tableModel);
generatorThread = new GeneratorThread(data, getInt(generateSleep),
getInt(generatorBatchCount), getInt(columnCount));
updateThread = new UpdateThread(generatorThread, tableModel,
getInt(updateSleepTime), getInt(eqSleepTime), getInt(threshold));
generatorThread.start();
updateThread.start();
}
private int getInt(JTextField tf) {
try {
return Integer.parseInt(tf.getText());
} catch (NumberFormatException nfe) {
System.out.println("exception getting int: " + nfe);
}
return 0;
}
public static void main(String[] args) {
Main main = new Main();
}
}
... will be continue
rest of methods
import java.awt.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
/**
* Thread responsible for publishing changes to the Model. Sleeps for a defined
* amount of time, waits for no activity in the UI and then users invokeAndWait
* to publish changes.
*/
public class UpdateThread extends Thread {
private int sleepTime;
private int eqSleepTime;
private int threshhold;
private boolean updatesEnabled;
private Runnable publishRunnable;
private Runnable emptyRunnable;
private GeneratorThread generator;
private CTTableModel model;
private Map<?, ?> lastData;
private long notifyTime;
private long paintTime;
private int updateCount;
private boolean done;
public UpdateThread(GeneratorThread generator, CTTableModel model,
int sleepTime, int eqSleepTime, int threshhold) {
super();
setPriority(Thread.MIN_PRIORITY);
this.sleepTime = sleepTime;
this.eqSleepTime = eqSleepTime;
updatesEnabled = true;
this.threshhold = threshhold;
this.generator = generator;
this.model = model;
publishRunnable = new Runnable() {
// Runnable used to publish changes to the event dispatching thread
#Override
public void run() {
publishChangesOnEventDispatchingThread();
}
};
// Empty runnable, used to wait until the event dispatching thread
// has finished processing any pending events.
emptyRunnable = new Runnable() {
#Override
public void run() {
}
};
}
#Override
public void interrupt() {
done = true;
super.interrupt();
}
#Override
public void run() {
while (!isInterrupted() && !done) {
try {
sleep(sleepTime);
publishChanges();
} catch (InterruptedException ie) {
}
}
System.out.println("UpdateThread done");
}
/**
* Publishes changes on the event dispatching thread when the system isn't
* busy. This blocks the caller until the changes have been published.
*/
private void publishChanges() {
synchronized (this) {// Wait until the user isn't scrolling
while (!updatesEnabled) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
// And wait until there are no pending events.
while (queue.peekEvent() != null) {
try {
sleep(eqSleepTime);
} catch (InterruptedException ie) {
}
}
final long start = System.currentTimeMillis();
try {
SwingUtilities.invokeAndWait(publishRunnable);
// publish the changes on the event dispatching thread
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
try {
// Wait until the system has completed processing of any events we
// triggered as part of publishing changes.
SwingUtilities.invokeAndWait(emptyRunnable);
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
final long end = System.currentTimeMillis();
try {// Update the display
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
Main.totalUpdateTime.setText("Total: "
+ Integer.toString((int) (end - start)));
Main.notifyTime.setText("Notify: "
+ Integer.toString((int) notifyTime));
Main.paintTime.setText("Paint: "
+ Integer.toString((int) paintTime));
Main.updateCount.setText("Updated: "
+ Integer.toString((int) updateCount));
}
});
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {
}
}
/**
* Does the actual publishing of changes.
*/
private void publishChangesOnEventDispatchingThread() {
long start = System.currentTimeMillis();
model.setBatchUpdates(true);
Map<?, ?> data = generator.getData();
boolean notify = !(data.size() > threshhold ||
(lastData != null && lastData.size() + data.size() > threshhold));
updateCount = data.size();
if (lastData != null) {
updateCount += lastData.size();
}//Reset the data for the last set of changes we did, this forces the cells to change color.
if (lastData != null) {
publishData(lastData, true, notify);
Iterator<?> dataIterator = lastData.keySet().iterator();
while (dataIterator.hasNext()) {
DataChange.releaseDataChange((DataChange) dataIterator.next());
}
lastData.clear();
}
publishData(data, false, notify);// Publish the current set of data.
model.setBatchUpdates(false);
if (!notify) {
model.fireTableDataChanged();
}
lastData = data;
long end = System.currentTimeMillis();
notifyTime = (end - start);
start = System.currentTimeMillis();
RepaintManager.currentManager(null).paintDirtyRegions();
end = System.currentTimeMillis();
paintTime = (end - start);
}
/**
* Publish the passed in set of data.
*/
private void publishData(Map<?, ?> data, boolean negate, boolean notify) {
Iterator<?> dataIterator = data.keySet().iterator();
while (dataIterator.hasNext()) {
DataChange change = (DataChange) dataIterator.next();
Object value = data.get(change);
if (negate) {
value = new Integer(((Integer) value).intValue() * -1);
}
model.set(change.getData(), change.getColumn(), value, notify);
}
}
/**
* If enable is true, we are allowed to publish changes, otherwise we
* aren't.
*
* #param enable
*/
public void setUpdatesEnabled(boolean enable) {
synchronized (this) {
updatesEnabled = enable;
if (updatesEnabled) {
notify();
}
}
}
public boolean getUpdatesEnabled() {
return updatesEnabled;
}
}
.
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
/**
* VisibleTableModelEvent adds the method isVisible to test if the cell
* identified by the event is visible.
*/
public class VisibleTableModelEvent extends TableModelEvent {
private static final long serialVersionUID = 1L;
private Point tmpPoint;
// This implementation caches the information for one JTable, it is
// certainly possible to cache it for more than one should
// you have this need.
private boolean valid;
private int firstVisRow;
private int lastVisRow;
private int firstVisCol;
private int lastVisCol;
public VisibleTableModelEvent(TableModel source) {
super(source, 0, 0, 0, UPDATE);
tmpPoint = new Point();
}
/**
* Resets the underlying fields of the TableModelEvent. This assumes no ONE
* is going to cache the TableModelEvent.
*
* #param row
* #param col
*/
public void set(int row, int col) {
firstRow = row;
lastRow = row;
column = col;
}
/**
* Invoked to indicate the visible rows/columns need to be recalculated
* again.
*/
public void reset() {
valid = false;
}
public boolean isVisible(JTable table) {
if (!valid) {// Determine the visible region of the table.
Rectangle visRect = table.getVisibleRect();
tmpPoint.x = visRect.x;
tmpPoint.y = visRect.y;
firstVisCol = table.columnAtPoint(tmpPoint);
firstVisRow = table.rowAtPoint(tmpPoint);
tmpPoint.x += visRect.width;
tmpPoint.y += visRect.height;
lastVisCol = table.columnAtPoint(tmpPoint);
if (lastVisCol == -1) {
lastVisCol = table.getColumnCount() - 1;
}
if ((lastVisRow = table.rowAtPoint(tmpPoint)) == -1) {
lastVisRow = table.getRowCount();
}
valid = true;
}
return (firstRow >= firstVisRow && firstRow <= lastVisRow && column
>= firstVisCol && column <= lastVisCol);
}
}

Categories