Upon loading JTable dynamically, before selected JComboBox appears visible - java

I use:
Java 10 SE
Java Swing
Eclipse IDE
I have JTable, the contents gets loaded at runtime dynamically. It has some JComboBoxes. If I select the JComboBox, and then attempt to reload the table, the JComboBox appears visible at the time when the table loading is in progress.
Besides that, if the JComboBox's contents gets updated (elsewhere in different table, when the combo supposed to reflect that new contents), that new contents does not get visible staright away after loading the JTable dynamically.
The snap-shot sample of the app:
That's, the table being loaded at runtime up, and in the middle you have vsisble JComboBox persistent from the previous selection.
How to:
Get rid off that persistent JComboBox
Make the data visible instantly, upon update under the combo, once you load the table dynamically
I have the public final class TableColumnEditor extends DefaultCellEditor{
which returns the JComboBox on a specific column:
else if(row == ROW_2_DEVS_WORK_WEEKEND) {
ProjectMetrics metrics = new ProjectMetrics();
JComboBox<String> combo = new JComboBox<>();
combo.setBackground(Color.WHITE);
for(String devs : metrics.identifyDevsThatWorkAtWeekend()) {
combo.addItem(devs);
}
return combo;
}
I have the public final class TableColumnRenderer extends DefaultTableCellRenderer{
which makes sure that the view displays the JComboBox under that specific column:
else if(row == ROW_2_DEVS_WORK_WEEKEND) {
ProjectMetrics metrics = new ProjectMetrics();
JComboBox<String> combo = new JComboBox<>();
combo.setBackground(Color.WHITE);
for(String devs : metrics.identifyDevsThatWorkAtWeekend()) {
combo.addItem(devs);
break;
}
return combo;
}
The table gets loaded dynamically right here (non-essential things removed):
public static void reloadTableDynamically(JTable metricsTable){
DefaultTableModel model = (DefaultTableModel)metricsTable.getModel();
if(projectData.isEmpty()) {
metricsTable.clearSelection();
int rowCount = model.getRowCount();
for(int item = (rowCount - 1); item >= 0; item--) {
model.removeRow(item);//clears previous rows
}
metricsTable.repaint();
return;
}
model.getDataVector().clear();
int rowCount = constantRows + ((devsTask.size() == 0) ? 1 : devsTask.size());
try {
new Thread(()-> {
int lastRowID = 0;
int devsTaskID = 0;
for(int item = 0; item < rowCount; item++) {
Object[] input = null;
if(item == 0) {
input = new Object[] {"", metrics.getProjectDateRange(), "" };
}//similar branches removed
else {
devsTaskID++;
input = new Object[] {"", devsTask.get(devsTaskID).getDeveloper(), ""};
}
model.addRow(input);
metricsTable.scrollRectToVisible(new java.awt.Rectangle(metricsTable.getCellRect(lastRowID++, 0, true)));
metricsTable.repaint();
try {
Thread.sleep(Config.getInstance().getReloadInOutTable());
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
metricsTable.scrollRectToVisible(new java.awt.Rectangle(metricsTable.getCellRect(projectData.size() - 1, 0, true)));
metricsTable.repaint();//so that to reach the last row
}).start();
}
catch(Exception e) {
}
}
What do you think?

Well, I figured out how to overcome this problem.
Firstly, the JComboBox gets updated on EDT(Event Despatch Thread).
/**
* #param combo The JComboBox ref.
* #param toDisplay The value to add to it
*/
public static void updateComboBoxOnEventDespatchThread(JComboBox<String> combo, String toDisplay) {
Runnable doComboUpdate = new Runnable() {
public void run() {
combo.addItem(toDisplay);
}
};
SwingUtilities.invokeLater(doComboUpdate);
}
Under the JTable column editor:
else if(row == ROW_2_DEVS_WORK_WEEKEND) {
ProjectMetrics metrics = new ProjectMetrics();
JComboBox<String> combo = new JComboBox<>();
combo.setBackground(Color.WHITE);
Runnable doComboInsert = new Runnable() {
public void run() {
int id = 0;
for(String devs : metrics.identifyDevsThatWorkAtWeekend()) {
UIutils.updateComboBoxOnEventDespatchThread(combo, "("+ ++id +") " + devs);
}
}
};
SwingUtilities.invokeLater(doComboInsert);
return combo;
}
But the main fix, without which both issues do not go away, is following.
That is, I noticed that in order for data to appear under the table instantly, firstly, you need to select any other unrelated table's cell.
That is, the Java thread, which loads the JTable at runtime, does need to have this:
if(model.getRowCount() > 0) {
metricsTable.selectAll();
}
That's probably a hack, but it works for me!

Related

How to add Images to a dynamically created JTable

I have a JTable which I create dynamically from List<String> objects. I do this probably completely wrong but it works. The only thing I can't get to work is adding Images to some of the cells.
All it does is, it adds the ImageIcon Object name as String to the cells. See my code below.
private static Image doneImage = getIconImage("doneImage");
private static Image notDoneImage = getIconImage("notDoneImage");
private DefaultTableModel model = new DefaultTableModel(){
#Override
public Class<?> getColumnClass(int column){
if ((column & 1) != 0 ){
return ImageIcon.class;
}else{
return String.class;
}
}
};
initTables();
JTable table = new JTable();
table.setModel(model);
private void initTables(){
model.addRow(new Object[]{});
int rowsToAdd = 0;
int rowCount = 0;
int columnId = 0;
for(HouseObject aHouse : houses){
for(RoomObject aRoom : aHouse.getRooms()){
model.addColumn(null);
model.addColumn(aRoom.getId());
model.setValueAt(aRoom.getId(), 0, columnId);
if (rowCount < aRoom.getEvents().size()){
rowsToAdd = aRoom.getEvents().size() - model.getRowCount();
for(int i = 0; i <= rowsToAdd; i++){
model.addRow(new Object[]{});
}
rowCount = model.getRowCount();
}
for(int i = 0; i < aRoom.getEvents().size(); i++){
model.setValueAt(aRoom.getEvents().get(i).getId(), i+1, columnId);
for(String houseDone : housesDone){
if(aRoom.getEvents().get(i).getId().contains(houseDone)){
model.setValueAt(doneImage , i+1, columnId+1); // this does not work
}else{
model.setValueAt(notDoneImage, i+1, columnId+1);
}
}
}
columnId = columnId+2;
}
}
}
You need to install renderer for your table
Here is the renderer:
public class IconTableCellRenderer extends DefaultTableCellRenderer {
#Override
protected void setValue(Object value) {
if (value instanceof Icon) {
setText(null);
setIcon((Icon) value);
} else {
super.setValue(value);
}
}
}
And so you must install it:
JTable table = new JTable();
table.setModel(model);
table.setDefaultRenderer(ImageIcon.class, new IconTableCellRenderer());
I have a JTable which I create dynamically from List objects.
Well you can't just add Strings to the table since then image will need to be added as an ImageIcon. So you would need a List so you can add String and Icon values.
Then you need to override the getColumnClass(...) method of your TableModel to return Icon.class for the column that contains the Icon. The table will then use the appropriate renderer for the Icon.
See: How to set icon in a column of JTable? for a working example.

Trying to add item listener to a JCheckBox object

I am trying add an item listener to a checkbox to see if its been checked, and if it is, to be added to a list of SQL table names to be selected. Inversely, if it is not selected then remove it from the list. I cannot add a listener though to any checkbox because "they are not effitively final". What can I do/is there a better way to attack it?
My method:
public JPanel drawChecks(){
ArrayList<String> list = MainFrame.grabSQLTableNames();
int index = list.size();
int rows = 1;
while(index > 1){
rows++;
index = index - 3;
}
GridLayout c = new GridLayout(rows, 3);
JPanel panel = new JPanel(c);
JCheckBox check[] = new JCheckBox[list.size()];
for(int x = 0; x < list.size(); x++){
check[x] = new JCheckBox(list.get(x));
check[x].setVisible(true);
check[x].addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (check[x].getState == true){
//do something
}
}
});
panel.add(check[x]);
}
Get the source of the event using the getSource method of the ItemEvent
public void itemStateChanged(ItemEvent e) {
JCheckBox checkBox = (JCheckBox)e.getSource();
if ( checkBox.isSelected() ){
//do something
}
}
For future reference, please read the following for tips on posting code examples for asking questions on stack overflow: https://stackoverflow.com/help/mcve

How to link two Jlists

I have two Jlists of vectors filled with data extracted from a mysql db. What I want is when the user select an item (menu) from Jlist1 (which I called menuList) Jlist2 (which I called productList) must display the products of that menu and other things, such as the ability to insert a new product in THAT menu or in a new menu just created.
I've accomplished this task in a way which I think is weak, by using some boolean variables which tells if the user is inserting a product in an existing menu or in a newly created one. Please, can you suggest me a better solution (if exists)? Here is an extract of the most significant part of the code, this is the method which saves a new product in the db:
private void bAddProdActionPerformed(java.awt.event.ActionEvent evt) {
//If new menu is saved, get the new menu's Id
if (newMenuIsSaved == true) {
Product newProduct = new Product();
newMenuId = DBConnection.getNewMenuId();
newProduct.setMenuId(newMenuId);
newProduct.setProductName(productName.getText());
if (checkPPriceValidity(productPrice.getText(), newProduct)) {
int result = DBConnection.insertProduct(newProduct);
if (result == 1) {
reloadProductList();
disableProductButtons();
}
}
} else {
Product newProduct = new Product();
//If a new menu wasn't saved, get the menuId from the selected one (from menuList):
Menu selectedMenu = (Menu) menuList.getSelectedValue();
newProduct.setMenuId(selectedMenu.getMenuId());
newProduct.setProductName(productName.getText());
if (checkPPriceValidity(productPrice.getText(), newProduct)) {
int result = DBConnection.insertProduct(newProduct);
if (result == 1) {
reloadProductList();
newMenuIsSaved = false;
disableProductButtons();
bNewProduct.setEnabled(true);
}
}
}
}
And here is the method reloadProductList():
private void reloadProductList() {
modelProductList.clear();
if (newMenuIsSaved) {
Vector<Product> productVoices = DBConnection.fillProductList(newMenuId);
for (int i = 0; i < productVoices.size(); i++) {
modelProductList.addElement((Product) productVoices.get(i));
}
} else {
Vector<Product> productVoices = DBConnection.fillProductList(selectedMenuId);
for (int i = 0; i < productVoices.size(); i++) {
modelProductList.addElement((Product) productVoices.get(i));
}
}
}
Thank you very much.
One way is to add a ListSelectionListener to menuList and have the handler set the other list's model to display the details for the selected row.

JTable alway can not get correct rowIndex for inner ComboBox

I have a JTable which its one column cell is JComboBox.
But when try to get row count when click the table JComboBox cell, I found the row index always return error value (alway is the last click row index).
public class TableComboBoxTest extends JFrame {
private JTable table;
private DefaultTableModel tableModel;
private Object[][] tableCells;
private final String[] TABLE_COLUMNS = {"No.1"};
private final String[] YES_NO_SELECTION = {"Yes", "No"};
public TableComboBoxTest() {
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
tableModel = new DefaultTableModel(tableCells, TABLE_COLUMNS);
table = new JTable(tableModel);
DefaultCellEditor cellEditor = null;
JComboBox selA = new JComboBox(YES_NO_SELECTION);
cellEditor = new DefaultCellEditor(selA);
cellEditor.setClickCountToStart(1);
table.getColumn(TABLE_COLUMNS[0]).setCellEditor(cellEditor);
JScrollPane jsp = new JScrollPane();
jsp.getViewport().add(table, null);
pane.add(jsp, BorderLayout.CENTER);
TableCellEditor tce = null;
addRow("Yes");
outputDefaultSelection(0, 0);
addRow("No");
outputDefaultSelection(1, 0);
System.out.println("");
selA.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
JComboBox cb = (JComboBox) e.getSource();
String sel = (String) cb.getSelectedItem();
int rowIndex = table.getSelectedRow();
rowIndex = table.convertRowIndexToModel(rowIndex);
if (rowIndex == -1) {
return;
}
outputDefaultSelection(rowIndex, 0);
System.out.println("Select: " + sel + " at " + rowIndex);
}
}
});
}
private void addRow(String v1) {
Vector<String> vec = new Vector<String>();
vec.add(v1);
tableModel.addRow(vec);
tableModel.fireTableDataChanged();
}
private void outputDefaultSelection(int row, int col) {
TableCellEditor tce = table.getCellEditor(row, col);
System.out.println("Default " + row + "-" + col + " Selection: " + tce.getCellEditorValue());
System.out.println("Default " + row + "-" + col + " Value: " + table.getModel().getValueAt(row, col));
}
public static void main(String[] args) {
TableComboBoxTest stt = new TableComboBoxTest();
stt.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
stt.setSize(200, 100);
stt.setVisible(true);
}
}
Default 0-0 Selection: Yes
Default 0-0 Value: Yes
Default 1-0 Selection: Yes
Default 1-0 Value: No*
When Click on first row and select "Yes", no change event trigger.
When Click on second row, change event trigger! and row number is wrong: 0
Default 0-0 Selection: No
Default 0-0 Value: Yes
Select: No at 0*
When continue to click on first row, change event trigger! and row number is wrong: 1
Default 1-0 Selection: Yes
Default 1-0 Value: No
Select: Yes at 1
How can I get correct clicking cell number?
And for the itemStateChanged process, I also found if cell set value is same with default column value ("Yes"), when click it event will not be trigger. But if cell set value to "No", click it will cause change event. That means model data is different with default selected data. How to make them consistent?
Thanks~
That means model data is different with default selected data. How to make them consistent?
It just means that the model has not yet been updated with the newly selected value from the combo box.
This can be demonstrated by using the following:
final String sel = (String) cb.getSelectedItem();
final int rowIndex = table.convertRowIndexToModel(table.getSelectedRow());
if (rowIndex == -1) {
return;
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
outputDefaultSelection(rowIndex, 0);
System.out.println("Select: " + sel + " at " + rowIndex);
}
});
Now, the display code will be added to the end of the Event Dispatch Thread, which means it will be executed after all other events are finished executing so the TableModel will now be updated.
However, that is not the best solution. If you want to know when data has been changed in a cell then add a TableModelListener to the TableModel. Don't use an ItemListener.

Add and remove from one Table To another

I am facing a problem with the functionality of removing a row(s) form one table view to another table view currently I have two button one add row form the bottom table to the above and the next add from the above the the botton. the button that add from bottom to top called add and the button that add from top to bottom called remove in both once the row is copied across it get deleted from the original table.
The problem I am facing is:
when I add all the row from the bottom table to the top table
if I sort Column 0 in top table ,then try to remove the row from the top table to the bottom table
the row do not get deleted from the top table although it is added in the bottom table.
Could you please Help me solve this problem or advice me on a better way?
Here is my current code:
Remove button:
btnRemove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
spTable.removeAll();
x = resultTable.getSelectedRows();
String[] cName= {"Column 0","Column 1","Column 2","Column 3","Column 4","Column 5","Column 6","Column 7","Column 8"};
if(rTable.getRowCount()==0){
model=addToNew(resultTable,x,cName,model);
}else{ model =addToExisting(resultTable,rTable, x, model);
}
deletRows(x,resultTable);
rTable.setModel(model);
JScrollPane spS = new JScrollPane(rTable);
rTable.getColumnModel().getColumn(1).setPreferredWidth(290);
spS.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
spS.setPreferredSize(new Dimension(800,200));
rTable.setFillsViewportHeight(true);
spTable.add(spS);
validate();
}});
Add button:
btnAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
panel.removeAll();
x = rTable.getSelectedRows();
String[] cName= {"Column 0","Column 1","Column 2","Column 3","Column 4","Column 5","Column 6","Column 7","Column 8"};
if(resultTable.getRowCount()==0){
model=addToNew(rTable,x,cName,model);
}else{ model =addToExisting(rTable,resultTable, x, model);
}
deletRows(x,rTable);
resultTable.setModel(model);
JScrollPane spS = new JScrollPane(resultTable);
resultTable.getColumnModel().getColumn(1).setPreferredWidth(290);
spS.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
spS.setPreferredSize(new Dimension(800,170));
panel.add(spS);
resultTable.setFillsViewportHeight(true);
validate();}});
deletRow
public void deletRows(int[] selected, JTable t){
for(int i = selected.length-1;i>=0;i--){
int y = selected[i];
((DefaultTableModel)t.getModel()).removeRow(y);
validate();}}
/**
* this method allow to add rows to an New table
*/
public DefaultTableModel addToNew(JTable t1,int[] selected,String[] ColName, DefaultTableModel m){
String[] name = ColName;
int col = name.length;
m =new DefaultTableModel();
for(int i= 0;i<col;i++){
m.addColumn(name[i]);
}
Object[] data= new Object[col];
for(int i =0; i<selected.length;i++){
int y= selected[i];
for (int z =0; z<col;z++){
if( t1.getValueAt(y, z)!= null){
String value = t1.getValueAt(y, z).toString();
data[z]= value;
}else{
data[z]=null;
}
}
m.addRow(data);
}
return m;
}
/* this method allow to add rows to an Existing table */
public DefaultTableModel addToExisting(JTable t1,JTable t2, int[] selected, DefaultTableModel m){
m =(DefaultTableModel)t2.getModel();
Object[] data= new Object[m.getColumnCount()];
for(int i =0; i<selected.length;i++){
int y= selected[i];
for (int z =0; z<m.getColumnCount();z++){
if( t1.getValueAt(y, z)!= null){
String value = t1.getValueAt(y, z).toString();
data[z]= value;
}else{
data[z]=null;
}
}
m.addRow(data);
}
return m;
}

Categories