Filter Selection and Table Updates

43 views
Skip to first unread message

Matthew Bennett

unread,
Dec 21, 2018, 5:52:15 PM12/21/18
to tablefilter-swing
I have an AbstractTableModel that I am using the TableFilterHeader on.  It is getting its data from a MySQL database and works quite well (Thank you so much for this!)

My one quibble is I have a timer that I use to automatically query the database and update the table.  If a user is trying to apply a filter when this happens, they get kicked out of the filter selector.  How can I prevent it from doing this?

I am Auto creating my filters and need to do this as new items are added to database randomly.

I was thinking if there was a way I could see if one of the combo boxes was opened I could skip the scheduled update?  Any suggestions would be much appreciated, thank you!

coderazzi

unread,
Dec 24, 2018, 5:03:01 AM12/24/18
to tablefilter-swing
Hi, Matthew,
One question on the timer: it is updating the tablemodel on its own
thread, or synchronized with the AWT event dispatch thread?
Some possibility to send some working example?

Cheers,
Lu.
> --
> You received this message because you are subscribed to the Google Groups "tablefilter-swing" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to tablefilter-sw...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Matthew Bennett

unread,
Dec 24, 2018, 9:31:29 AM12/24/18
to tablefilter-swing
I am a pretty basic programmer so it's executing by whichever one happens by default, ha ha.

It's a somewhat large program but the important bits for this issue are all in a class called "MainFrame" that is what is called when the java program launches.  Line 71 is where I create the Filter Header and just below it I add a Row Sorter Listener to update some text on the screen stating how many orders are visible with the current filters.  On Line 155 is my public static void main and I create my timer on line 159.  On 162 I create an EventQueue.invokeLater to create my Mainframe window.  Line 173 is my timer function that calls the updateTableContent funciton on Line 186.  If it would be helpful I could try to upload some standalone code in a little bit that displays the basic issue.  One possible way to slightly mitigate the issue that I thought of was to check and see the rowcount of the new data was different from the current data and only trigger the fireTableDataChanged when the row counts were different (and otherwise use firetablerowsupdated), though I would still face the issue where a new row would mess up the filter selection process and I want to avoid that :)

public class MainFrame extends javax.swing.JFrame {
/**
* 
*/
private static final long serialVersionUID = 1L;
static private JTable jtbl = new JTable(); // Table that holds the response from the database  
private JFrame jfrm; // The main display Windows
private JPanel orderspnl; // Frame that holds simple text object displaying total open orders.
private JScrollPane jscrlp; // Scrollable from that holds the database table
static private EtsyTable etsyTables; // Abstract table model used to get formatting of table correct
static private SqlEtsy sql; // Sql class used to handle SQL calls
static private JLabel jlbl; // Label used to hold number of open oders
private static long delayRate = 60;
public MainFrame() throws IOException {
 
int rowCount = 0;
int totalOrders[] = new int[2];
int gap = 50;
GridLayout myLayout = new GridLayout (2,1);
// This should set the main row color and an alternate row color.  For reasons beyond me, it only sets the alternate row color.
    // Problem persists even when all other renderers are commented out.
try {
UIManager.put("Table.rowColor", new Color (0,0,0));//(219, 250, 255));
UIManager.put("Table.alternateRowColor", new Color (160, 242, 255));
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            
            break;
        }
    }
} catch (Exception e) {
    // If Nimbus is not available, fall back to cross-platform
    try {
        UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
    } catch (Exception ex) {
        // not worth my time
    }
}
// Create the main frame to display our data
jfrm = new JFrame("All Wrapped Up");

// Create a grid 2 rows by 1 column and apply it to the JFrame
jfrm.setLayout(myLayout);
jfrm.setLayout(new FlowLayout());
// Get PNG image and place it as the icon 
BufferedImage img = ImageIO.read(getClass().getResource("/icon.png"));
jfrm.setIconImage(img);
// Exit the application when it is closed
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set the JFrame to be the size of the current window
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    Rectangle maxBounds = env.getMaximumWindowBounds();
    jfrm.setBounds(maxBounds);
        
// Create an interface to the SQL handler and load all information from the processing_orders database into the table
sql = new SqlEtsy();
etsyTables = new EtsyTable(sql.getAllOrders());

// Make table filterable and searchable
jtbl = new JTable(etsyTables);
TableFilterHeader filterHeader = new TableFilterHeader(jtbl, AutoChoices.ENABLED);
IFilterEditor editor = filterHeader.getFilterEditor(1);
editor.setEditable(false);
// Any time the table is filtered, immediately update the displayed label text
filterHeader.getTable().getRowSorter().addRowSorterListener(new RowSorterListener() {
            @Override
            public void sorterChanged(RowSorterEvent e) {
            updateLableContent();
            }
         });
// Section controls look and feel of the table
// As a general note, I've somewhat complicated the way I call getColumnModel.  Instead of directly telling it the column number
// I have it return the column number associated with a specific header name.  The reason for this is I wanted to make it more robust in the event that
// late on down the road I copied and pasted the "removeColumn" line somewhere above this point.  If I did that, then my hard coded column numbers would be pointing to the 
// incorrect column location.  By using the column name to return the index, it allows me the freedom to move things around without it getting broken.
JTableHeader header = jtbl.getTableHeader(); // Get Table header information
header.setDefaultRenderer(new HeaderRenderer(jtbl)); // Create a new default renderer for the table header
header.setFont(new Font("Arial", Font.BOLD, 15)); // Set the font size and style for the table header
header.setAlignmentX(CENTER_ALIGNMENT); // Set header text to be centered
jtbl.setShowHorizontalLines(true); // Show lines between each row
jtbl.setRowHeight(jtbl.getRowHeight()+gap); // Adjust the row height to accommodate the text size with a little gap
// Make cells around check boxes transparent so row color can be seen.
((JComponent) jtbl.getDefaultRenderer(Boolean.class)).setOpaque(true);
jtbl.setFont(new Font("Arial", Font.PLAIN, 20)); // Sets the font and size for the table cells
MbTableCellRenderer.setCellsAlignment(jtbl, SwingConstants.CENTER); // Centers contents of table cells
MbTableCellRenderer dcRenderer = new MbTableCellRenderer(); // creates a new instance of DateCellREenderer to use with keeping center alignment with date formats  
dcRenderer.setHorizontalAlignment(SwingConstants.CENTER); // set alignment of the tablerenderer to center
// Transaction ID is only used when writing updates back to database, so remove the column
jtbl.removeColumn(jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Trans ID"))); 
jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Date Ordered")).setCellRenderer(dcRenderer); // Formats the date in a MMM dd format 
jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Ship By")).setCellRenderer(dcRenderer); // Formats the date in a MMM dd format
// Button Column is a class that takes a column and adds a button to it.  The variable created is never directly used after the creation, so surpress the warning.
@SuppressWarnings("unused")
ButtonColumn buttonColumn = new ButtonColumn(jtbl, delete, jtbl.getColumnModel().getColumnIndex("Send To Cutter"));
// End of the look and feel section
// Now that jtbl is formated and filled, add it to the scroll panel
jscrlp = new JScrollPane(jtbl);
// Create panel to hold order information
orderspnl = new JPanel();
rowCount = jtbl.getRowCount(); // get total ordered wraps (rows in table)
totalOrders = sql.getOpenOrderCount(); // get total currently open orders
// Add Order information to a label and add label to Jpanel
jlbl = new JLabel("<html><center>There are currently "+ totalOrders[1] + " open orders and <b><font color='green'>" + totalOrders[0] + "</font></b> wraps to be cut.<br/>Currently displaying " + rowCount + " entries.</html>");
jlbl.setFont(new Font("Arial", Font.PLAIN, 20));
orderspnl.add(jlbl);
// The Jpanels to the frame
jfrm.add(jscrlp);
jfrm.add(orderspnl);
jfrm.setVisible(true);
// Pack it in (aka, I have no idea what pack does comment)
jfrm.pack();

// Set window to maximized
jfrm.setExtendedState(MAXIMIZED_BOTH);
// Get the current size of the window
    Dimension size = maxBounds.getSize();
    Insets insets = jfrm.getInsets(); // Find border area
    if (insets != null) {
    //  Change size to equal the window's dimensions without the borders, also leave a little extra space on the height for the order information.
        size.height -= (insets.top + insets.bottom)+20+90;
        size.width -= (insets.left+insets.right)+20;
    }

    // Set the preferred scroll panel size to our new dimensions 
jtbl.setPreferredScrollableViewportSize(size);

}

// You know what this is
public static void main(String args[]) {
// Create a timer to execute on a scheduled basis (1 second? 2 Seconds?)
// Timer will be used to update table data
Timer timer = new Timer();

// Create the window with all it's innards
java.awt.EventQueue.invokeLater(new Runnable () {
public void run() {
try {
new MainFrame();
} catch (IOException e) {
e.printStackTrace();
}
}
});
timer.scheduleAtFixedRate(new TimerTask() {
  @Override
  public void run() {
updateTableContent();
  }
}, delayRate*1000, delayRate*1000); // Timer executes 2 seconds after first called and at a fixd rate of every two seconds after timer is finished.
}
/**
* Updates the table
*/
public static void updateTableContent() {
int row = jtbl.getSelectedRow(); // Get the currently selected row so we can pick up where we left off in the table after the refresh
etsyTables.update_table(sql.getAllOrders()); // Tell the table to update its data
updateLableContent();
((EtsyTable)jtbl.getModel()).fireTableDataChanged(); // Tell the table that its data may have changed and to check for updates.
// Try to return to the previously selected row.  If filters have been applied it may not be available any longer.
try {
jtbl.setRowSelectionInterval(row, row);
} catch (Exception e) {
}
}
private static void updateLableContent() {
int rowCount = jtbl.getRowCount(); // get total ordered wraps (rows in table)
int[] totalOrders = new int[2];
totalOrders = sql.getOpenOrderCount();// get total currently open orders
jlbl.setText("<html><center>There are currently "+ totalOrders[1] + " open orders and <b><font color='green'>" + totalOrders[0] + "</font></b> wraps to be cut.<br/>Currently displaying " + rowCount + " entries.</html>");
}
// Think about changing the action name.  It doesn't delete the row, it sends a command to cut the wrap.  It just took me forever to get this worked out
// and I'm in no rush to fix it!
// This action is triggered when the "Send to Cutter" button is pressed
final Action delete = new AbstractAction()
{

/**
* 
*/
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent e) {
// Get the selected row and use it to get the size, mode, and buyer information about the row the button was pressed in.
int row = jtbl.getSelectedRow();
int pcSize = (Integer) jtbl.getValueAt(row, jtbl.getColumnModel().getColumnIndex("Size"));
String pcModel = (String) jtbl.getValueAt(row, jtbl.getColumnModel().getColumnIndex("Model"));
String buyer = (String) jtbl.getValueAt(row, jtbl.getColumnModel().getColumnIndex("Buyer"));
// Confirm that the correct button was pressed
int dialogResult = JOptionPane.showConfirmDialog (null, "Confirm Cutting " + pcSize + " Quart " + pcModel + " for " + buyer + "?","Warning",0);
if(dialogResult == JOptionPane.YES_OPTION){
Runtime rt = Runtime.getRuntime();
try {
// opens Inkscape with the correct svg and send it to the cutter
Process pr = rt.exec("\"C:\\Program Files\\Inkscape\\inkscape.exe\" --verb=org.ekips.filter.plot.noprefs \"\\\\192.168.25.37\\All Wrapped Up\\SVGs\\SVGs for Cutting\\Latest for Scripting\\" + pcModel + " - " + pcSize + " Quart.svg\" --verb=FileClose");
System.out.println(pr);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
};
}



Matthew Bennett

unread,
Dec 24, 2018, 10:24:46 AM12/24/18
to tablefilter-swing
I have created the very basic following code that showcases what I'm seeing.  I have the update rate set to 5 seconds (set by delayRate) so it happens fairly quickly.  If you open one of the drop down boxes, wait for the table update, and the user will be kicked out of the drop down selection box on the update. 

MainFrame.java
package mb.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;


import javax.swing.UIManager.*;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.table.JTableHeader;


import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.IFilterEditor;
import net.coderazzi.filters.gui.TableFilterHeader;










/**
 *  The MainFrame class provides an interface for the user to see current order status of Etsy Orders
 *  It handles all the front end rendering and control
 *
 */

public class MainFrame extends javax.swing.JFrame {
/**
* 
*/
private static final long serialVersionUID = 1L;
static private JTable jtbl = new JTable(); // Table that holds the response from the database  
private JFrame jfrm; // The main display Windows
private JPanel orderspnl; // Frame that holds simple text object displaying total open orders.
private JScrollPane jscrlp; // Scrollable from that holds the database table
static private EtsyTable etsyTables; // Abstract table model used to get formatting of table correct
static private JLabel jlbl; // Label used to hold number of open oders
private static long delayRate = 5;
public MainFrame() throws IOException {
 
int rowCount = 0;
int totalOrders[] = new int[2];
int gap = 50;
GridLayout myLayout = new GridLayout (2,1);
// This should set the main row color and an alternate row color.  For reasons beyond me, it only sets the alternate row color.
try {
UIManager.put("Table.rowColor", new Color (0,0,0));//(219, 250, 255));
UIManager.put("Table.alternateRowColor", new Color (160, 242, 255));
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            
            break;
        }
    }
} catch (Exception e) {
    // If Nimbus is not available, fall back to cross-platform
    try {
        UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
    } catch (Exception ex) {
        // not worth my time
    }
}
// Create the main frame to display our data
jfrm = new JFrame("All Wrapped Up");

// Create a grid 2 rows by 1 column and apply it to the JFrame
jfrm.setLayout(myLayout);
jfrm.setLayout(new FlowLayout());
// Get PNG image and place it as the icon 
//BufferedImage img = ImageIO.read(getClass().getResource("/icon.png"));
//jfrm.setIconImage(img);
// Exit the application when it is closed
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set the JFrame to be the size of the current window
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle maxBounds = env.getMaximumWindowBounds();
        jfrm.setBounds(maxBounds);
        
// Create an interface to the SQL handler and load all information from the processing_orders database into the table
        String[] columns = new String[] {
                "Id", "Name", "Hourly Rate", "Part Time"
            };
        Object[][] data = new Object[][] {
            {1, "John", 40.0, false },
            {2, "Rambo", 70.0, false },
            {3, "Zorro", 60.0, true },
        };
        
        etsyTables = new EtsyTable(data);
        jtbl = new JTable(etsyTables);

// Make table filterable and searchable
//jtbl = new JTable(etsyTables);
TableFilterHeader filterHeader = new TableFilterHeader(jtbl, AutoChoices.ENABLED);
IFilterEditor editor = filterHeader.getFilterEditor(1);
editor.setEditable(false);
// Any time the table is filtered, immediately update the displayed label text
filterHeader.getTable().getRowSorter().addRowSorterListener(new RowSorterListener() {
            @Override
            public void sorterChanged(RowSorterEvent e) {
            updateLableContent();
            }
         });
// Section controls look and feel of the table
// As a general note, I've somewhat complicated the way I call getColumnModel.  Instead of directly telling it the column number
// I have it return the column number associated with a specific header name.  The reason for this is I wanted to make it more robust in the event that
// late on down the road I copied and pasted the "removeColumn" line somewhere above this point.  If I did that, then my hard coded column numbers would be pointing to the 
// incorrect column location.  By using the column name to return the index, it allows me the freedom to move things around without it getting broken.
JTableHeader header = jtbl.getTableHeader(); // Get Table header information
//header.setDefaultRenderer(new HeaderRenderer(jtbl)); // Create a new default renderer for the table header
header.setFont(new Font("Arial", Font.BOLD, 15)); // Set the font size and style for the table header
header.setAlignmentX(CENTER_ALIGNMENT); // Set header text to be centered
jtbl.setShowHorizontalLines(true); // Show lines between each row
jtbl.setRowHeight(jtbl.getRowHeight()+gap); // Adjust the row height to accommodate the text size with a little gap
// Make cells around check boxes transparent so row color can be seen.
((JComponent) jtbl.getDefaultRenderer(Boolean.class)).setOpaque(true);
jtbl.setFont(new Font("Arial", Font.PLAIN, 20)); // Sets the font and size for the table cells
//MbTableCellRenderer.setCellsAlignment(jtbl, SwingConstants.CENTER); // Centers contents of table cells
//MbTableCellRenderer dcRenderer = new MbTableCellRenderer(); // creates a new instance of DateCellREenderer to use with keeping center alignment with date formats  
//dcRenderer.setHorizontalAlignment(SwingConstants.CENTER); // set alignment of the tablerenderer to center
// Transaction ID is only used when writing updates back to database, so remove the column
// jtbl.removeColumn(jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Trans ID"))); 
//jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Date Ordered")).setCellRenderer(dcRenderer); // Formats the date in a MMM dd format 
//jtbl.getColumnModel().getColumn(jtbl.getColumnModel().getColumnIndex("Ship By")).setCellRenderer(dcRenderer); // Formats the date in a MMM dd format
// Set table widths numbers were decided largely by trial and error for a minimum 1300 pixel wide screen

// Button Column is a class that takes a column and adds a button to it.  The variable created is never directly used after the creation, so surpress the warning.
//@SuppressWarnings("unused")
//ButtonColumn buttonColumn = new ButtonColumn(jtbl, delete, jtbl.getColumnModel().getColumnIndex("Send To Cutter"));
// End of the look and feel section
// Now that jtbl is formated and filled, add it to the scroll panel
jscrlp = new JScrollPane(jtbl);
// Create panel to hold order information
orderspnl = new JPanel();
rowCount = jtbl.getRowCount(); // get total ordered wraps (rows in table)
totalOrders[0] = 3;
totalOrders[1] = 2;//sql.getOpenOrderCount(); // get total currently open orders
//etsyTables.update_table(sql.getAllOrders()); // Tell the table to update its data
updateLableContent();
((EtsyTable)jtbl.getModel()).fireTableDataChanged(); // Tell the table that its data may have changed and to check for updates.
// Try to return to the previously selected row.  If filters have been applied it may not be available any longer.
try {
jtbl.setRowSelectionInterval(row, row);
} catch (Exception e) {
}
}
private static void updateLableContent() {
int rowCount = jtbl.getRowCount(); // get total ordered wraps (rows in table)
int[] totalOrders = new int[2];
totalOrders[0] = 4;
totalOrders[1] = 2;//sql.getOpenOrderCount();// get total currently open orders

EtsyTable.java
package mb.gui;

import java.util.ArrayList;
import java.util.List;

import javax.swing.table.AbstractTableModel;


import mb.gui.MainFrame;

/**
 *  The EtsyTable class creates a custom table model with the handlers I need to display and set MySQL table data.
 *
 */
public class EtsyTable extends AbstractTableModel{


private static final long serialVersionUID = 1L;

// Column names are  hard coded for the table since I know these are the only column names that will exist within the table.
// There may be a way to dynamically get and set these at runtime, however I found it unnecessary for the scope of this project
public final String[]COLUMN_NAMES= {"Id", "Name", "Hourly Rate", "Part Time" };
// This array list will hold all the information from the SQL query on all order data
private Object[][]orders;
/**
* Method will take an array list and store it for manipulation and retrieval in the local orders ListArray
* 
* @param orders List that holds table data
*/
public EtsyTable(Object[][]orders) {
this.orders=orders;
}

/**
* Sets whether a cell can be edited or not
* 
* For future consideration.  I've been a bit lazy and hard coded the conditions for cells I want to be able to edit 
* Now clearly I don't really understand how this function works as I never call it and yet it correctly allows only
*   the cells with check boxes to be edited, but I really have no idea why.
* 
* @param row The specified table row
* @param col The specified table column
* @param return returns if the cell can be edited
*/
public boolean isCellEditable(int row, int col)
{ 
String colName = getColumnName(col);
if ((colName == "Sticky Note")||(colName == "Cut")||(colName == "Magnet")||(colName == "Boxed")|| (colName == "Shipped")|| (colName == "Send To Cutter") ){
return true; 
} else {
return false;
}
}
/**
* @param value The value of the edit, is of type "Object" to allow any datatype to use this one function rather than recreating it for each type
* @param row The specified table row
* @param col The specified table column
*/
public void setValueAt(Object value, int row, int col) 
{
boolean updateSuccessful = false;
// Create SQL connection, we do not need to call the close function as it will automatically be called as part of the writeCellContents
//SqlEtsy sql = new SqlEtsy();
// Get the column name so we can update the correct column in the sql table
String colName = getColumnName(col);
// We only need to execute this if the column being edited is one of the check box columns
if ((colName == "Sticky Note")||(colName == "Cut")||(colName == "Magnet")||(colName == "Boxed")|| (colName == "Shipped") ){
//String trans_id = orders.get(row).getTransId(); // Get the transaction id, it is used to correctly update the associated MySQL table row
//updateSuccessful = sql.writeCellContents(colName, trans_id,((boolean) value)); // Call the SQL update function

// After the cell has been updated, get the updated values from the table and show in the table
// This is included as one could potentially set the update time to a high value which would delay in showing the
// updated check box for quite a while.
// Only call if the SQL update was successful
if (updateSuccessful) {
MainFrame.updateTableContent();
// These two lines have been commented out as I test to see if the new updateTableContents function works as expected
// Now instead of firing updates from multiple points, I update it in one function and call it where required.
//this.update_table(sql.getAllOrders()); 
    //this.fireTableDataChanged(); 
}
}
 }

/**
* 
* @param orders Updated table information from MySQL
* @return updated list
* 
* Initially I had it set up as:
* public List<OrderTracking> update_Table(List<OrderTracking>orders)
* however after thinking about it I realized that I was not sure if I actually need to return anything.
* I have commented out the return line and set the return to void, so far is appears to be working. 
*/
public void update_table(Object[][]orders) {
this.orders = orders;
}
/**
* Basic overrides for AbstractTable Model
*/
@Override
public int getColumnCount() {
return COLUMN_NAMES.length;
}

@Override
public int getRowCount() {
return orders.length;
}


@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return orders.length;
}
@Override
public String getColumnName (int column) {
return COLUMN_NAMES[column];
}
// End of basic overrides

/**
* 
* Method searches the column names array for a specific column.  If the column exists it returns the string value.
* 
* @param column Name of column to search for
* @return the column name if found otherwise a blank string
*/
public String getColumnNames (String column) {
int index = -1;
for (int i=0;i<COLUMN_NAMES.length;i++) {
if (COLUMN_NAMES[i].equals(column)) {
index = i;
}
}
if (index == -1) {
return "";
} else {
return COLUMN_NAMES[index];
}
}
/**
*  Method returns the class type of each column.  It is required to get check boxes to show instead of displaying true/false
*  
* @param column the column's index
* @return the class of the column's datatypes
*/
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;
    }




}

coderazzi

unread,
Dec 24, 2018, 6:12:11 PM12/24/18
to tablefilter-swing
Hi, Mathew,
Like many UIs, Java Swing is not thread safe. That means that you need
to ensure that any updates affecting Swing components are performed
exclusively by the AWT event dispatcher thread (some exceptions apply,
but this is the general rule).
You are in fact already doing that when you create the MainFrame; you wrote:

java.awt.EventQueue.invokeLater(new Runnable () {
public void run() {
try {
new MainFrame();
} catch (IOException e) {
e.printStackTrace();
}
}
});

This is the way to let the AWT thread run your code. You can read a
good tutorial about this here:
https://dzone.com/articles/multi-threading-java-swing.

In your code, you create a java.util.Timer and request it to execute
some code periodically. The only thing that you need to change is to
execute that code by the AWT thread. Something like:

timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
java.awt.EventQueue.invokeLater(new Runnable () {
public void run() {
try {
updateTableContent();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}, delayRate*1000, delayRate*1000); // Timer executes 2 seconds after
first called and at a fixd rate of every two seconds after timer is
finished.

Of course, I could probably get ride of scheduleAtFixedRate (it will
depend now on the GUI activities, so no sense on trying to execute it
at fixed periods). But, much better, I would use java.swing.Timer
instead of the java.util.Timer.

In any case, remember that your application freezes while you are
invoking actions on the AWT thread, you should minimize any code there
-things like SQL queries do not belong to the AWT thread, and should
be executed on separated threads-, and only use the AWT thread to do
the synchronization work (like firing events on which rows have been
updated, etc).

Hope this helps!!!

Lu.

Matthew Bennett

unread,
Dec 25, 2018, 4:46:36 PM12/25/18
to tablefilter-swing
Okay, I have implemented the suggestions you made and updated my main function to:

public static void main(String args[]) {
// Create a timer to execute on a scheduled basis (1 second? 2 Seconds?)
// Timer will be used to update table data
//Timer timer = new Timer();
        
timer = new Timer((int) (delayRate*1000), new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
java.awt.EventQueue.invokeLater(new Runnable () {
            public void run() {
 
            updateTableContent();
            timer.stop();
            timer.restart();
          }
});
}
});
 
timer.start();

// Create the window with all it's innards
java.awt.EventQueue.invokeLater(new Runnable () {
public void run() {
try {
new MainFrame();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}

And I'll update my main program to get the SQL updates out of the AWT thread.  However, in this test program you can see that calling the table update still causes the filter selection drop down to close if you are trying to select a filter when the update is triggered.

I was thinking a possible solution would be to add a listener to the combobox to see when its active, but going through the source code I can't seem to find where the combobox/drop-down box is created.

coderazzi

unread,
Dec 25, 2018, 10:34:01 PM12/25/18
to tablefilter-swing
Hi, Matthew,
Indeed, I see what you are describing. I have checked the code and the
popups are explicitly hidden in case of a model update.
I cannot recall the reasoning behind -my guess is that I was trying to
avoid the situation where the user was unable to pick up a choice
because the choices were constantly changing-. In any case, seems okay
to update this behavior and disable this popup hiding.
What I have done is to update the code to include a new filter
settings (hidePopupsOnTableUpdates) which is false by default.
I have updated the code in bitbucket, but it is still beta; can you
download the latest sources from
https://bitbucket.org/coderazzi/tablefilter-swing and try it?
I have extended the TableFilter Example that is distributed with the
sources to include this setting, plus the possibility to start / stop
a thread that automatically updates the table model.

Please let me know what you think

L.

Matthew Bennett

unread,
Dec 27, 2018, 9:24:42 AM12/27/18
to tablefilter-swing
Oh wow, thank-you so much for this quick fix!  So far everything is looking good and the drop downs remain visible during refreshes.  Thank you again!

coderazzi

unread,
Dec 27, 2018, 8:09:43 PM12/27/18
to tablefilter-swing
Nice, I will release it then asap,
Best,

Lu.
Reply all
Reply to author
Forward
0 new messages