Dependent columns in a JTable - java

Hie!
I have a JTable. Columns of this JTable are rendered by JComboBox.
I would like to be able to change items of column 2 on the basis of values selected in column 1.
For example if the user selects Microsoft in column 1, then in column 2 he/she can select ado, wpf, etc.
Is it possible ?
If it is possible, than which events should be listened to do it ?

The Combo Box Table Editor provides one possible solution for this.

Maybe you can base you on this code;
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
int row = table.getSelectedRow();
int column = table.getSelectedColumn();
}
}
);
This is an intresting page: click

Just make your own TableCellEditor that preps the JComboBox's model on the call to getTableCellEditorComponent. Something like this:
class MyEditor extends DefaultCellEditor{
public MyEditor() {
super(new JComboBox());
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JComboBox combo = (JComboBox)editorComponent;
Object column1Value = table.getValueAt(row, column-1);
Object[] options = ... create options based on other value
combo.setModel(new DefaultComboBoxModel(options));
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
}

What are you using as values in your TableModel?
One solution would be to define a class, say CategoryValue, that represents a list of possible items and a selected item, and use that; then listen for TableModelEvents and when a value in column 0 changes, set the corresponding value in column 1. A simple example is below.
First, the TableModelListener:
model.addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (e.getColumn() == 0) {
int firstRow = e.getFirstRow();
int lastRow = e.getLastRow();
for (int row = firstRow; row <= lastRow; row++) { // note <=, not <
CategoryValue parentValue = ((CategoryValue) model.getValueAt(row, 0));
String parentSelection = parentValue.getSelection();
List<String> childCategories = getChildCategories(parentSelection);
CategoryValue newChildValue = new CategoryValue(childCategories);
model.setValueAt(newChildValue , row, 1);
}
}
}
});
(Implementing getChildCategories(String) depends on where your data is coming from, but it could be as simple as a Map<String, List<String>>.)
Next, the value class:
public class CategoryValue {
private final String selection;
private final List<String> categories;
public CategoryValue(List<String> categories) {
this(categories, categories.get(0));
}
public CategoryValue(List<String> categories, String selection) {
assert categories.contains(selection);
this.categories = categories;
this.selection = selection;
}
public String getSelection() {
return selection;
}
public List<String> getCategories() {
return categories;
}
#Override
public String toString() {
return selection;
}
}
Finally, a custom cell editor for the value class:
public class CategoryCellEditor extends DefaultCellEditor {
public CategoryCellEditor() {
super(new JComboBox());
}
static List<CategoryValue> allValues(List<String> categories) {
List<CategoryValue> allValues = new ArrayList<CategoryValue>();
for (String value: categories) {
allValues.add(new CategoryValue(categories, value));
}
return Collections.unmodifiableList(allValues);
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
CategoryValue categoryValue = (CategoryValue) value;
List<String> categories = categoryValue.getCategories();
List<CategoryValue> allValues = CategoryValue.allValues(categories);
ComboBoxModel cbModel = new DefaultComboBoxModel(allValues.toArray());
((JComboBox)editorComponent).setModel(cbModel);
return super.getTableCellEditorComponent(table, categoryValue,
isSelected, row, column);
}
}
All done with one event listener, and a nice bonus is that that event listener doesn't care how the table is edited/updated, or where the edits/updates come from.
Edited to add: Alternatively, represent each row of the table with some business object that captures all the choices made for a particular row, and have the CellEditor get the available choices from the business object (using the row argument to getTableCellEditorComponent() to get the business object). The event mechanism would remain the same. This has the advantage that it's probably easier to read the selected values from the business object than to scrape the table.

Related

How to make columns editable in special abstract model JTable

Hey guys i doing my assignment and now i have the problem with non editable cells, actually it became editable, but the result of editing didn't set at arraylist, I tried many solution from internet, but it doesn't work.
So my work like registration system which get information about guest, and then stored it into csv file. In additional function the program must let display, update, delete and searching function.
I finished all, without update,delete and searching. Can you please looking my code and help me or give the advice, link or something useful.
this is my abstract model:
public class ddispmodel extends AbstractTableModel {
private final String[] columnNames = { "FirstName", "SecondName", "Date of
birth", "Gender", "Email", "Address", "Number", "Attending","ID" };
private ArrayList<String[]> Data = new ArrayList<String[]>();
private boolean editable;
public void AddCSVData(ArrayList<String[]> DataIn) {
this.Data = DataIn;
this.fireTableDataChanged();
}
#Override
public int getColumnCount() {
return columnNames.length;// length;
}
#Override
public int getRowCount() {
return Data.size();
}
#Override
public String getColumnName(int col) {
return columnNames[col];
}
#Override
public Object getValueAt(int row, int col) {
return Data.get(row)[col];
}
public boolean isCellEditable(int row, int col) {
setValueAt(Data, row, col);
this.fireTableCellUpdated(row, col);
return true;
}
}
This is part of my main class
It is action Listener of menu item witch activate displaying function
(I didn't copy all class, because it nearly 1000 lines, but if it necessary, i can submit all code )
dlog.addActionListener(new ActionListener (){
public void actionPerformed(ActionEvent e){
CSVFileDomestic Rd = new CSVFileDomestic();
ddispmodel ddispm = new ddispmodel();
ddisp.setModel(ddispm);
File DataFile = new File("D:\\cdne4\\WorkPlace\\Domestic.csv");
ArrayList<String[]> Rs2 = Rd.ReadCSVfile(DataFile);
ddispm.AddCSVData(Rs2);
System.out.println("Rows: " + ddispm.getRowCount());
System.out.println("Cols: " + ddispm.getColumnCount());
cl.show(cp, "dispDomPanel");
}
});
and File class which convert date from csv to arraylist
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
public class CSVFileDomestic {
private final ArrayList<String[]> Rs = new ArrayList<String[]>();
private String[] OneRow;
public ArrayList<String[]> ReadCSVfile(File DataFile) {
try {
BufferedReader brd = new BufferedReader(new
FileReader(DataFile));
while (brd.ready()) {
String st = brd.readLine();
OneRow = st.split(",");
Rs.add(OneRow);
System.out.println(Arrays.toString(OneRow));
}
}
catch (Exception e) {
String errmsg = e.getMessage();
System.out.println("File not found:" + errmsg);
}
return Rs;
I am new at Java and this is my first program , please can you explain more easily
but the result of editing didn't set at arraylist,
You need to override the setValueAt(...) method of the TableModel to save the data.
It would be something like:
String[] row = data.get(row);
row[column] = value;
this.fireTableCellUpdated(row, col);
Also, the isCellEditable(...) method should NOT do any processing. It simply returns true/false for the given column. If you want all columns editable then it should just be:
public boolean isCellEditable(int row, int col) {
//setValueAt(Data, row, col);
//this.fireTableCellUpdated(row, col);
return true;
}
I finish this)) and go to do others options.
camickr thank you for help, you give me the way, which bring me to answer))
I spend nearly 6 hours for trying to do it and finally I got it.
If someone interesting, answer is
public boolean isCellEditable(int row, int col) {
return true;
}
#Override
public void setValueAt(Object aValue, int row, int col){
Data.get(row)[col]= (String) aValue;
fireTableCellUpdated(row,col);
so I just make new method setValueAT which said me camickr.
than I went to Oracle site and read about it, and after try it to do, but
it doesn't, because aValue can't be converted from object to String, and finally I initiate aValue as a String, so now it works.However it is not all, it only change arraylist. Needs new method to convert arraylist to csv file. I am working about it now, maybe after I will show it. Sorry for my theory, I am new at java, just learn how it works))

Is there any way to add objects to a JComboBox and assign a String to be shown?

I want to add objects to a JComboBox but show a String on the JComboBox for each object.
For example, in the following html code
<select>
<option value="1">Item 1</option>
<option value="2">Item 2</option>
<option value="3">Item 3</option>
<option value="4">Item 4</option>
</select>
in the first item, the String that is shown is "Item 1", but the value of the item is "1".
Is there a form to do something like that with a JComboBox?
Start by taking a look at How to Use Combo Boxes, in particular Providing a Custom Renderer
Basically, you want to define your object which will be contained within the combo box...
public class MyObject {
private String name;
private int value;
public MyObject(String name, int value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public int getValue() {
return value;
}
}
Then create a custom ListCellRenderer that knows how to renderer it...
public class MyObjectListCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
if (value instanceof MyObject) {
value = ((MyObject)value).getName();
}
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
return this;
}
}
Then populate you combo box and apply the cell renderer...
JComboBox box = new JComboBox();
box.addItem(new MyObject(..., ...));
//...
box.setRenderer(new MyObjectListCellRenderer());
You could, equally, override the toString method of your object, but I tend to like to avoid this for display purposes, as I like the toString method to provide diagnostic information about the object, but that's me
If your combo box model contains objects, their toString() method will be used by default to display them in the combo box. If the toString() method displays what you want, you don't have anything to do.
Otherwise, you just need to set a cell renderer to customize the way each object is displayed (and that doesn't limit you to text: you can also change the font, color, icon, etc.).
This is all described in the Swing tutorial.
For example, in the following html code
For something simple like this, where you have an "ID", "Value" type of data, I do like the approach of a custom Object who's purpose in life is to provide a custom toString() method. See Combo Box With Hidden Data for such an reusable object.
Many people in the forums do recommend a custom renderer. Unfortunately using a custom renderer breaks the default functionality of the comobo box. See Combo Box With Custom Renderer for more information as a solution.
Yes, that can be done using the object type as the parameter for the JComboBox generic, like this:
public class TestFrame extends JFrame {
// This will be the JComboBox's item class
private static class Test {
private Integer value;
private String label;
public Test(Integer value, String label) {
this.setValue(value);
this.setLabel(label);
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
// The "toString" method will be used by the JComboBox to generate the label for the item
#Override
public String toString() {
return getLabel();
}
}
public static void main(String[] args) {
TestFrame frmMain = new TestFrame();
frmMain.setSize(300, 50);
frmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Here you declare a JComboBox that
// uses the type "Test" for item elements
JComboBox<Test> cmbCombo = new JComboBox<TestFrame.Test>();
for (int i = 0; i < 10; i++) {
// Add some elements for the combo
cmbCombo.addItem(new Test(i, String.format("This is the item %d", i + 1)));
}
// Listen to changes in the selection
cmbCombo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JComboBox<Test> cmbCombo = (JComboBox<Test>) e.getSource();
// The selected element is a "Test" instance, just cast it to the correct type
Test test = (Test) cmbCombo.getSelectedItem();
// Manipulate the selected object at will
System.out.printf("The selected value is '%d'\n", test.getValue());
}
});
frmMain.add(cmbCombo);
frmMain.setVisible(true);
}
}

JTable row sorter - IllegalArgumentException: Invalid SortKey

Can someone help me with this?
It was working until I changed something trying to optimaze it... damn!
This is my table model:
class MyTableModel extends DefaultTableModel {
private String[] columnNames = {"Last Name","First Name","Next Visit"}; //column header labels
Object[][] data = new Object[100][3];
public void reloadJTable(List<Customer> list) {
for(int i=0; i< list.size(); i++){
data[i][0] = list.get(i).getLastName();
data[i][1] = list.get(i).getFirstName();
if (list.get(i).getNextVisit()==null) {
data[i][2] = "NOT SET";
} else {
String date = displayDateFormat.format(list.get(i).getNextVisit().getTime());
data[i][2] = date;
}
model.addRow(data);
}
}
public void clearJTable() {
model.setRowCount(0);
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
/*
* JTable uses this method to determine the default renderer/
* editor for each cell. If we didn't implement this method,
* then the last column would contain text ("true"/"false"),
* rather than a check box.
*/
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
/*
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int row, int col) {
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
if (col < 2) {
return false;
} else {
return true;
}
}
}
and this is how I implement the JTable:
// these declarations are all private shared across the model
JTable customerTbl;
MyTableModel model;
List<Customer> customers = new ArrayList<Customer>();
SimpleDateFormat displayDateFormat = new SimpleDateFormat ("EEE dd-MM-yyyy 'at' hh:mm");
//JTable configuration
model = new MyTableModel();
customerTbl = new JTable(model);
model.reloadJTable(customers);
customerTbl.setAutoCreateRowSorter(true); //enable row sorters
DefaultRowSorter sorter = ((DefaultRowSorter)customerTbl.getRowSorter()); //default sort by Last Name
ArrayList list = new ArrayList();
list.add( new RowSorter.SortKey(0, SortOrder.ASCENDING));
sorter.setSortKeys(list); //EXCEPTION HERE
sorter.sort();
customerTbl.getColumnModel().getColumn(0).setPreferredWidth(100); //set Last Name column preferred width
customerTbl.getColumnModel().getColumn(1).setPreferredWidth(80); //set First Name column preferred width
customerTbl.getColumnModel().getColumn(2).setPreferredWidth(150); //set Last Visit column preferred width
I'm getting the following exception triggered on:
sorter.setSortKeys(list);
Exception in thread "main" java.lang.IllegalArgumentException: Invalid SortKey
at javax.swing.DefaultRowSorter.setSortKeys(Unknown Source)
at com.vetapp.customer.CustomersGUI.<init>(CustomersGUI.java:128)
at com.vetapp.main.VetApp.main(VetApp.java:31)
I believe it has to do with the TableColumnModel which is not created correctly...
The main problem is that the column count is returning 0 from the TableModel's super class DefaultTableModel. You need to override this method
#Override
public int getColumnCount() {
return columnNames.length;
}
Another side but potentially fatal issue is the fact that getColumnClass is returning the class of elements within the TableModel. This will throw a NullPointerException if the table is empty. Use a class literal instead such as String.class.
Maintaining a separate backing data array is unnecessary for DefaultTableModel. It has already has its own data vector. This approach is used when extending AbstractTableModel.

Making an addRows() method for a Custom JTable TableModel

My explanation below rambles, boiling down, is there a way I can add a Row without firing off an event, such that I can add multiple rows and fire an event to update all of them at once? Without having to add code to contain the table data in the custom model?
I have a custom TableModel which extends from DefaultTableModel so that I can use DefaultTableModel to keep track of data for me, whilst still having some custom methods of my own.
The issue is, I was thinking it might be faster for me to have an "addRows(String[][] val)" method, when I wish to add multiple rows. I could then fire a single event, probably fireTableDataChanged() to update the rows all at once. For example, my current method:
JTable table1 = new JTable(new dgvTableModel(new String[] {<values>},0, new String[] {<values>}));
table1.addRow(new String[] {<values here>});
table1.addRow(new String[] {<values here>});
table1.addRow(new String[] {<values here>});
I would then repeat the above as many times as necessary. The issue is, each of those will fire off a seperate event. It would be much faster (I think), if I could do this using my custom table model:
JTable table1 = new JTable(new dgvTableModel(new String[] {<values>},0, new String[] {<values>}));
table1.addRows(new String[][] {{<values1 here}, {values2 here}, . . .}});
and then in the table model:
public void addRows(String[][] values) {
for (String[] vals : values)
super.addRow(vals);
}
fireTableDataChanged();
}
I can code this in easily. The issue is again, that "super.addRow(vals);" line will fire an event each time through. Is there a way, without adding code to have my model contain the table data itself, to prevent that event being fired each time I add a row? Such that it waits for the fireTableDataChanged() call in the addRows method?
For reference, the code for my custom table model:
import java.awt.Color;
import java.util.ArrayList;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
public class dgvTableModel extends DefaultTableModel {
//private DataTable tableVals = new DataTable();
private ArrayList<Color> rowColors;
//private ArrayList<Object[]> data = new ArrayList<>();
//default constructor has no data to begin with.
private int[] editableColumnNames;
public dgvTableModel(String[] colNames, int rowCount)
{
super(colNames, rowCount);
}
public dgvTableModel(String[] colNames, int rowCount, String[] editableColNames)
{
super(colNames, rowCount);
//this.tableVals.setColNames(colNames);
if (editableColNames!=null && editableColNames.length >0)
{
editableColumnNames = new int[editableColNames.length];
int count = 0;
for (int i =0; i< editableColNames.length;i++)
{
for (String val : colNames)
{
if (val.equalsIgnoreCase(editableColNames[i]))
{
editableColumnNames[count] = i;
count+=1;
break;
}
}
}
}
}
public dgvTableModel(String[] colNames, int rowCount, String[] editableColNames, boolean colorChanges)
{
super(colNames, rowCount);
Color defColor = UIManager.getDefaults().getColor("Table.background");
if (editableColNames!=null && editableColNames.length >0)
{
editableColumnNames = new int[editableColNames.length];
int count = 0;
if (colorChanges)
{
rowColors = new ArrayList<>();
}
for (int i =0; i< colNames.length;i++)
{
if (colorChanges)
{
rowColors.add(defColor);
}
for (String val : editableColNames)
{
if (val.equalsIgnoreCase(colNames[i]))
{
editableColumnNames[count] = i;
count+=1;
break;
}
}
}
}
else if (colorChanges)
{
rowColors = new ArrayList<>();
for (String val : colNames)
{
rowColors.add(defColor);
}
}
}
#Override
public boolean isCellEditable(int row, int column)
{
if(editableColumnNames!=null && editableColumnNames.length >0)
{
for (int colID : editableColumnNames)
{
if (column==colID)
return true;
}
}
return false;
}
public void setRowColor(int row, Color c)
{
rowColors.set(row, c);
fireTableRowsUpdated(row,row);
}
public Color getRowColor(int row)
{
return rowColors.get(row);
}
#Override
public Class getColumnClass(int column)
{
return String.class;
}
#Override
public String getValueAt(int row, int column)
{
return super.getValueAt(row, column).toString();
}
}
Surely firing one event to display every row, is faster than firing one event for each row?
'AbstractTableModel.fireTableDataChanged()' is used to indicate to the model (and the JTable UI which is notified by the model) that all possible data in the table may have changed and needs to be checked. This can (with emphasis on can) be an expensive operation. If you know which rows have been added, just use the 'AbstractTableModel.fireTableRowsInserted(int firstRow, int lastRow)' method instead. This will ensure only the effect rows are seen as changed. Take a look at all the fire* methods in AbstractTableModel. You can really exercise fine grained control over which rows, cells, etc are seen as dirty.
Then again what your doing might be premature optimalization. Unless you have fiftythousand records in your JTable this is probably not going to be noticable. But if you have a massive amount of records in your JTable you might be beter of lazy loading them anyway.

Java Swing | extend AbstractTableModel and use it with JTable | several questions

I followed Oracle's model for implementing an AbstractTableModel
http://download.oracle.com/javase/tutorial/uiswing/examples/components/TableDemoProject/src/components/TableDemo.java
I did this because my table has to contain 3 columns, and the first has to be a JCheckBox.
Here's my code:
public class FestplattenreinigerGraphicalUserInterfaceHomePagePanelTableModel extends AbstractTableModel {
private String[] columnNames = {"Auswahl",
"Dateiname",
"Pfad"};
private Object[][] data = {
{new Boolean(true), "datei.tmp",
"/home/user/tmp"}
};
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public boolean isCellEditable(int row, int col) {
if (col == 0) {
return true;
} else {
return false;
}
}
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
Here are my questions:
How does JTable (new JTable(FestplattenreinigerGraphicalUserInterfaceHomePagePanelTableModel)) know what the column names are and their values are? Since there's no contructor in my AbstractTableModel?! Is it becaue columnNames and data must be named like they are and JTable accesses them?
How can i put new Values in my JTable? Since columnNames and data are arrays. Can i replace them with vectors? If so, how do I init these vectors? In a constructor in myAbsTableModel?
I think it's very easy to get a solution but this Table handling isn't trivial to me, so thank u very much!
All Swing components come with default model implementations. I suggest you understand how to use them first before trying to create your own. For a JTable its called the DefaultTableModel. Read the API for methods to dynamically add/remove rows of data from the model. Here is a simple example to get you started:
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableBasic extends JPanel
{
public TableBasic()
{
String[] columnNames = {"Date", "String", "Integer", "Boolean"};
Object[][] data =
{
{new Date(), "A", new Double(1), Boolean.TRUE },
{new Date(), "B", new Double(2), Boolean.FALSE},
{new Date(), "C", new Double(9), Boolean.TRUE },
{new Date(), "D", new Double(4), Boolean.FALSE}
};
JTable table = new JTable(data, columnNames)
{
// Returning the Class of each column will allow different
// renderers and editors to be used based on Class
public Class getColumnClass(int column)
{
for (int row = 0; row < getRowCount(); row++)
{
Object o = getValueAt(row, column);
if (o != null)
return o.getClass();
}
return Object.class;
}
};
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollPane = new JScrollPane( table );
add( scrollPane );
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("TableBasic");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new TableBasic() );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
First, I think you need to learn a bit more about Java, especially inheritance (I'm referencing your constructor problem.
Answers to your questions :
define your column names via a private final static attribute, assuming your column names don't change.
since your class extends AbstractTableModel, you can define a constructor for it, where you'll pass the data. Redefine the getValueAt method to allow the model to use the data you're passing.
Some more advice :
don't do what you're doing in getColumnClass. Normally, all elements in a column will have the same class, so do a switch on the column index to get the classes.
to add a JCheckBox in one of your columns, you'll have to use a custom TableCellRenderer
The JTable determines how many columns by calling getColumnCount() on your column model. It then iterates and calls getColumnName(idx) for each column. Your class tells it the column name -- look at your implementation of those methods.
You can store your data in whatever format you want. The JTable calls methods on your table model to retrieve that data. If you want to add new items to your model, you can implement an addItem(Object o) method to your model; just be sure to call fireTableRowsInserted() for each new item.

Categories