Why does setPreferredSize() sometimes take precedent over setMinimumSize()? - java

I had an error yesterday where I added a JTable and a JPanel (with a JButton in it) to a JScrollPane. The JButton was fixed to the bottom of the table, and it added a row to the JTable when clicked.
The problem was if the table ever got bigger than the JScrollPane, it would only allow you to scroll to the bottom of the JTable; you couldn't get to the JButton anymore. Today, I made an MCVE to try and get help, but first I monkeyed with it a bit more and ended up fixing my problem, but in a way that left me with more questions than answers... Here's the MCVE I had prepared:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class MCVE extends JFrame{
private JButton addRow;
private MCVEModel tableModel;
private JTable table;
private JScrollPane pane;
private JPanel scrollPanel, panel;
public static void main (String[] args) {
new MCVE();
}
public MCVE() {
initialize();
}
public void initialize () {
this.setTitle("Halp");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setBounds(50, 50, 500, 300);
this.setResizable(false);
JPanel mainPanel = new JPanel(new GridBagLayout());
/** The JPanel everything is put into **/
scrollPanel = new JPanel();
scrollPanel.setLayout(new BoxLayout(scrollPanel, BoxLayout.Y_AXIS));
/** The JScrollPane we're using **/
pane = new JScrollPane(scrollPanel);
pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
pane.getVerticalScrollBar().setUnitIncrement(10);
/** The button which keeps getting cut off.... **/
addRow = new JButton("...");
addRow.setBackground(Color.WHITE);
addRow.setMnemonic('R');
addRow.setFocusable(false);
addRow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addNewRow();
}
});
/** I wrap the button into this panel so I can affix it to the left **/
panel = new JPanel();
panel.setMinimumSize(new Dimension(0, 20));
panel.setLayout(null);
addRow.setBounds(0, 0, 35, 15);
panel.add(addRow);
/** Faking some data to get the table to populate **/
ArrayList<List<String>> allData = new ArrayList<List<String>>();
ArrayList<String> fakeData = new ArrayList<String>();
fakeData.addAll(Arrays.asList(
new String[]{"this", "is", "just", "sample", "data"}));
for(int i = 0; i < 5; i++)
allData.add(fakeData);
List<String> columnNames = Arrays.asList(new String[] {"", "", "", "", ""});
tableModel = new MCVEModel(columnNames, allData);
table = new JTable();
table.setModel(tableModel);
/** Adding it all together **/
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
scrollPanel.add(table);
scrollPanel.add(panel);
mainPanel.add(pane, c);
this.add(mainPanel);
this.setVisible(true);
}
public void addNewRow () {
tableModel.addRow(tableModel.getRowCount(),
new String[]{"true", "", "", "false", "false"});
tableModel.fireTableRowsInserted(
tableModel.getRowCount(), tableModel.getRowCount());
}
}
/**
* Just here to keep things compilable. Seriously cut back for the MCVE,
* but still replicates the problem without any errors.
* Nothing below here should be relevant to the issue.
*/
class MCVEModel extends DefaultTableModel {
private static final long serialVersionUID = -6598574844380686148L;
private List<String> columnNames;
private List<List<String>> values;
public MCVEModel (List<String> columnNames, List<List<String>> strings) {
this.columnNames = columnNames;
this.values = strings;
}
public int getColumnCount() {
return columnNames.size();
}
public int getRowCount() {
return values == null || values.size() == 0 ? 0 : values.get(0).size();
}
public String getColumnName(int col) {
return columnNames.get(col);
}
public Object getValueAt(int row, int col) {
return values.get(col).get(row);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public Class getColumnClass(int c) {
return String.class;
}
public boolean isCellEditable(int row, int col) {
return true;
}
public void setValueAt(Object value, int row, int col) {
values.get(col).set(row, (String) value);
fireTableCellUpdated(row, col);
}
public void removeRow(int row) {
for(int i = 0; i < values.size(); i++)
values.get(i).remove(row);
this.fireTableRowsDeleted(row, row);
}
public void addRow(int row, String[] strings) {
for(int i = 0; i < values.size(); i++)
values.get(i).add(row, strings[i]);
fireTableRowsInserted(row, row);
}
}
The problem is with this line:
panel.setMinimumSize(new Dimension(0, 20));
More precisely, it's with the word "Minimum". By changing this to:
panel.setPreferredSize(new Dimension(0, 20));
I got exactly the functionality I needed. Now when the table gets too large for the JScrollPanel, we're still able to scroll down and see the JButton; it's no longer cut off.
I'm assuming this means the JPanel's parent didn't honor its minimum dimensions, but that it did honor its preferred dimensions. Why is this? I had thought setPreferredSize(), setMinimumSize(), and setMaximumSize() interacted like, "I'd prefer to be this big, but no matter what I can't be smaller than my minimum or larger than my maximum," but it seems this isn't the case. I know none of these methods should be used too frequently, but when should I use setMinimumSize() over setPreferredSize() or vice versa?

The magic is this line:
scrollPanel.add(panel);
So, scrollPanel will contain this panel. Then, JScrollPane honors the preferredSize-s. Which makes sense, since its purpose is, by using the scroll bars, to make enough room for the contained components. In other words, JScrollPane -s impementation ignores the minimumSize-s.
Update:
From an other angle, JScrollPane -s source code checks the preferredSize of its children, but not the minimumSize. There's no deep philosophy here, JScrollPane is implemented this way.

Related

Showing Old Value in JTable in swing

In this code block I'm getting old values after clicking inside scroll pane, however new values also disappear at first on fetching but they slowly disappear and old values appear in that place.
I tried a few combinations but was't able to achieve the results as expected. Also during variable declaration I made all the variables as non-static except the frame which I suppose is not relevant and recommended and I'm also embedding the components on frame itself.
//THE TABLE
table = new JTable();
//THE COLUMN
String[] columns = new String[] {
"Serial No", "BundleId", "Bundle Count", "Record Count","Status","Date"
};
//THE ROW
Object[][] data = dc.Success(date,afterDate);
final Class[] columnClass = new Class[] {
Boolean.class,String.class, String.class, String.class,String.class,String.class };
//create table model with data
DefaultTableModel model = new DefaultTableModel(data, columns) {
#Override
public boolean isCellEditable(int row, int column)
{
return false;
}
#Override
public Class<?> getColumnClass(int columnIndex)
{
return columnClass[columnIndex];
}
public Object getCellEditorValue() {
return "";
}
};
// JTable table = new JTable(model);
table.setModel(model);
//add the table to the frame
//ADD SCROLLPANE
scroll.setBounds(70, 80, 600, 400);
scroll.setViewportView(table);
frame.getContentPane().add(scroll);
// this.add(new JScrollPane(table));
// table.setAutoCreateRowSorter(true);
table.setPreferredScrollableViewportSize(new Dimension(320, 160));
TableColumn tc = table.getColumnModel().getColumn(BOOLEAN_COL);
tc.setHeaderRenderer(new SelectAllHeader(table, BOOLEAN_COL));
this.setTitle("Table Example");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
table.repaint();
If you want to notify your JTable about changes of your data, use something similar to tableModel.fireTableDataChanged().
There are other methods that can be used for updating specific ranges of data. tableModel.fireTableRowsUpdated​(firstRow, lastRow) might be of particular use to you. This is because you suggest you only update a single row when that row is clicked on. If that is not the case, stick to tableModel.fireTableDataChanged().
From your comments, I notice you are trying to fire a data change event and then changing your whole model anyway. This is not required and could actually be the source of your issues if your model is holding the old values.
Instead you should change the DataVector of the current model. To change the data on a single row you could use something like the following extract of code. There is a full example below the extract.
//Get your row's values from your database
//I'll use random values for this purpose
int col1Val = getRand(0, 9);
int col2Val = getRand(0, 9);
//Put the values in an object vector
Vector<Object> rowVals = new Vector<>();
rowVals.addElement(col1Val);
rowVals.addElement(col2Val);
//Set the object vector
model.getDataVector().setElementAt(rowVals, table.getSelectedRow());
//Notify the table the row has changed
model.fireTableRowsUpdated(table.getSelectedRow(), table.getSelectedRow());
Full example
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class JTableExample extends JFrame {
private static final String windowName = "JTable Example";
private static final long serialVersionUID = 362702020844358278L;
private JPanel tablePanel;
private JScrollPane scrollPanel;
private JTable table;
private DefaultTableModel model;
private JTableExample() {
super(windowName);
SetUp();
}
private void SetUp() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SetUpJTable();
SetUpJPanel();
add(tablePanel);
pack();
setVisible(true);
}
private void SetUpJPanel() {
tablePanel = new JPanel(new GridLayout());
tablePanel.add(scrollPanel);
}
private void SetUpJTable() {
String[] columns = new String[] {
"Something", "Something Else"
};
String[][] data = new String[][] {
{"1", "2"},
{"3", "3"}
};
model = new DefaultTableModel(data, columns) {
private static final long serialVersionUID = -3895234084030399437L;
#Override
public boolean isCellEditable(int row, int column)
{
return false;
}
};
table = new JTable(model);
table.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
//Get your row's values from your database
//I'll use random values for this purpose
int col1Val = getRand(0, 9);
int col2Val = getRand(0, 9);
//Put the values in an object vector
Vector<Object> rowVals = new Vector<>();
rowVals.addElement(col1Val);
rowVals.addElement(col2Val);
//Set the object vector
model.getDataVector().setElementAt(rowVals, table.getSelectedRow());
//Notify the table the row has changed
model.fireTableRowsUpdated(table.getSelectedRow(), table.getSelectedRow());
}
});
scrollPanel = new JScrollPane(table);
scrollPanel.setPreferredSize(new Dimension(500, 500));
}
private static int getRand(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> new JTableExample());
}
}

JTable autoresize issue

I'm creating a program in which user can record and analyze the start time and end time of an activity. I think it is having an autoresize problem: at the start of the program, the table shrinks weirdly. But when I click on any cell, it becomes normal.
Below is my code
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.table.*;
import java.awt.event.*;
public class TestTimeLog {
private static int COLUMN_NUM = 4;
private int rowNum = 11;
private JTable table;
private JPanel tablePanel;
public static int FRAME_WIDTH = 700;
public static int FRAME_HEIGHT = 800;
/*
* Panel that holds the table
*/
public JPanel TimeLogPanel() {
JPanel timeLogPanel = new JPanel();
// Create border layout with horizontal gap and vertical gap between components
timeLogPanel.setLayout(new BorderLayout(20, 20));
// Set border with parameters of top, left, bottom and right spacing
timeLogPanel.setBorder(BorderFactory.createEmptyBorder(20, 40, 40, 20));
timeLogPanel.add(tablePanel, BorderLayout.CENTER);
// Create table panel and set layout
tablePanel = new JPanel();
tablePanel.setLayout(new BorderLayout());
// Add a component listener to be able to calculate the panel size, thus
// calculate width and height of column
tablePanel.addComponentListener(new ComponentListener() {
// Override component resized method and change cell width and height
#Override
public void componentResized(ComponentEvent e) {
if (tablePanel.getHeight() / rowNum >= 1) {
table.setRowHeight(tablePanel.getHeight() / rowNum);
}
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getColumnModel().getColumn(0).setPreferredWidth(tablePanel.getWidth() / 5);
table.getColumnModel().getColumn(1).setPreferredWidth(tablePanel.getWidth() / 5);
table.getColumnModel().getColumn(2).setPreferredWidth(tablePanel.getWidth() / 2);
table.getColumnModel().getColumn(3).setPreferredWidth(tablePanel.getWidth() / 10);
}
// Below methods are not used but must be there, as a requirement when adding a
// listener
#Override
public void componentMoved(ComponentEvent e) {
}
#Override
public void componentShown(ComponentEvent e) {
}
#Override
public void componentHidden(ComponentEvent e) {
}
});
// Create a new table with a table model (defined in a different method below)
table = new JTable(tableModel());
// Change appearance
table.setShowGrid(true);
table.setGridColor(Color.BLACK);
table.setSelectionBackground(Color.WHITE);
table.setSelectionForeground(Color.BLACK);
// Set a raised border
table.setBorder(new EtchedBorder(EtchedBorder.RAISED));
// Add table to JScrollPane which is added to table panel
JScrollPane scrollPane = new JScrollPane(table);
tablePanel.add(scrollPane);
return timeLogPanel;
}
/*
* This method overrides a table model so that it shows desired column names
*/
public DefaultTableModel tableModel() {
DefaultTableModel model = new DefaultTableModel(rowNum, COLUMN_NUM) {
private static final long serialVersionUID = 1L;
String[] columnNames = { "Start Time", "End Time", "Activity", "Priority" };
#Override
public String getColumnName(int index) {
return columnNames[index];
}
};
return model;
}
/*
* This main method creates a JFrame that holds the JPanel
*/
public void main(String[] args) {
// Create a new frame
JFrame guiFrame = new JFrame("Time Log Program");
// Set size of the frame
guiFrame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
// Add the panel
guiFrame.add(TimeLogPanel());
// Exit normally on closing the window
guiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Show the frame
guiFrame.setVisible(true);
}
}
Somewhere in the code, I did turn off auto resize mode. But the weird display still happens, and I've go not idea how to fix it.
I appreciate any suggestions. Thanks!
EDIT: Also, half of the time I run the program, it will show up perfectly right (a normal table), and half of other time a shrank table appears. It's so weird.

JScrollPane scrollbar does not appear until after rowSorter.toggleSortOrder() is called

I have noticed that when I have a JTable with a TableRowSorter contained by a JScrollPane, the vertical scrollbar does not appear until after I have created SortKeys for the sorter (which is done by calling toggleSortOrder() for one of the columns).
My question is really why? What do SortKeys have to do with a vertical scrollbar?
Update: Added SSCCE that opens a JFrame with a JTable inside a JScrollPane, that sits in a Container along with a "Populate" button. When the table is initially painted, there is no data and hence no need for a scroll bar. After I populate it with 20 rows, there is a need for a scroll bar, but none appears.
There are two ways to make the scroll bar appear:
Click on either of the header cells to cause a sort to occur.
Remove the commented call toggleSortOrder() in the Container's refresh() method.
// table.getRowSorter().toggleSortOrder(0);
toggleSortOrder() calls setSortKeys() calls sort() calls fireRowSorterChanged() and eventually something catches the event and adds the scroll bar.
package test;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
#SuppressWarnings("serial")
public class TestFrame extends JFrame
{
class MyTableModel extends AbstractTableModel
{
public List<String> names = new ArrayList<String>();
public int getRowCount ()
{
return names.size();
}
public int getColumnCount ()
{
return 2;
}
public String getColumnName (int columnIndex)
{
return columnIndex > 0 ? "Name" : "Number";
}
public Class<?> getColumnClass (int columnIndex)
{
return String.class;
}
public Object getValueAt (int row, int col)
{
return row < names.size() ? col == 0 ? Integer.toString(row) : names.get(row) : "";
}
public void refresh (List<String> names)
{
this.names = names;
}
}
class MyContainer extends java.awt.Container implements ActionListener
{
JTable table;
MyTableModel model = new MyTableModel();
private TableRowSorter<MyTableModel> sorter;
public MyContainer()
{
}
public void init ()
{
sorter = new TableRowSorter<MyTableModel>(model);
table = new JTable(model);
table.setBorder(BorderFactory.createEmptyBorder());
table.setRowHeight(35);
table.getTableHeader().setPreferredSize(new Dimension(200, 35));
table.setRowSorter(sorter);
table.setPreferredScrollableViewportSize(new Dimension(200, 70));
table.setFillsViewportHeight(true);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
//Create the scroll pane and add the table to it.
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setBounds(10, 10, 200, 210);
//Add the scroll pane to this panel.
add(scrollPane);
JButton btn = new JButton("Populate");
btn.setActionCommand("populate");
btn.addActionListener(this);
btn.setBounds(10, 220, 200, 35);
add(btn);
}
public void refresh (List<String> rows)
{
model.refresh(rows);
try
{
// Notify sorter that model data (possibly number of rows) has changed.
// Without this call, the sorter assumes the number of rows is the same.
table.getRowSorter().allRowsChanged();
// Do we really want to toggle the sort order every time we refresh?
// User can toggle the sort order himself by clicking on the
// appropriate header cell.
List<?> keys = table.getRowSorter().getSortKeys();
if (null == keys || keys.isEmpty())
{
// table.getRowSorter().toggleSortOrder(0);
}
} catch (Exception e)
{
e.printStackTrace();
}
table.repaint();
}
public void actionPerformed(ActionEvent e)
{
if ("populate".equals(e.getActionCommand()))
{
List<String> rows = new ArrayList<String>();
for (int ii = 0; ii < 20; ii++)
{
rows.add(String.format("%02d", new Integer(ii)));
}
refresh(rows);
}
}
MyTableModel getModel ()
{
return model;
}
}
public static void main (String args[])
{
new TestFrame();
}
MyContainer myContainer = new MyContainer();
TestFrame()
{
myContainer.init();
myContainer.table.getSelectionModel().clearSelection();
add(myContainer);
this.setSize(240, 310);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//pack();
setVisible(true);
}
}
Well, that is not really a SSCCE because you are using a custom TableModel. If you would have created a proper SSCCE you would be using the DefaultTableModel so that you are testing your code with standard JDK classes. If you did this then you would have noticed that the code would work.
So then your next step would be to try the code with your custom TableModel and you would notice that the code did not work.
So then your question on the forum would be why doesn't the code work with my custom TableModel? The point of the SSCCE is to do basic debugging to isolate where the error is happening so we have information to work with. In your original question we had no idea you where using custom classes.
Anyway, the problem is that your custom TableModel is not notifying the table when a change to the data is made. In your refresh(...) method you need to add the following after you reset the List containing the data:
fireTableRowsInserted(0, names.size()-1);
There is no need for table.repaint() in any of your code.

JTable column widths only changing after two requests

In my application, I have a two column table which will be dynamically populated through the life of the program. I have a method called updateTableWidths which, when called, does the following:
makes the first column just as wide as it needs to be to fit cell contents
makes the second column just as wide as it needs to be to fit cell contents
if the entire table is wider than its container, enables a horizontal scrollbar
otherwise, lets last column stretch to fill container
It seems to work great, but for some really strange reason it needs to be called twice (but only in some cases, i.e. when table widths are growing rather than shrinking). The SSCCE below illustrates my dilemma.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
#SuppressWarnings("serial")
public class TableResizeTest extends JComponent {
private JFrame frame;
private JScrollPane scrollPane;
private DefaultTableModel tableModel;
private JTable table;
private JPanel panel;
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TableResizeTest window = new TableResizeTest();
window.frame.setVisible(true);
window.frame.requestFocusInWindow();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public TableResizeTest() {
initialize();
}
private void initialize() {
frame = new JFrame("Test");
frame.setBounds(300, 300, 200, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tableModel = new DefaultTableModel(new Object[]{"Col_1", "Col_2"},0);
table = new JTable(tableModel);
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
table.setAlignmentY(Component.TOP_ALIGNMENT);
table.setAlignmentX(Component.LEFT_ALIGNMENT);
table.setBorder(new EmptyBorder(0, 0, 0, 0));
table.setFillsViewportHeight(true);
table.setTableHeader(null);
table.setEnabled(false);
table.getColumnModel().setColumnMargin(0);
frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
panel = new JPanel();
frame.getContentPane().add(panel);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JButton btnFillTableShort = new JButton("Fill table short");
btnFillTableShort.setAlignmentX(Component.CENTER_ALIGNMENT);
panel.add(btnFillTableShort);
JButton btnFillTableLong = new JButton("Fill table long");
btnFillTableLong.setAlignmentX(Component.CENTER_ALIGNMENT);
panel.add(btnFillTableLong);
JButton btnUpdateTableWidths = new JButton("Update table widths");
btnUpdateTableWidths.setAlignmentX(Component.CENTER_ALIGNMENT);
panel.add(btnUpdateTableWidths);
btnUpdateTableWidths.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateTableWidths();
}
});
btnFillTableLong.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fillTableLong();
}
});
btnFillTableShort.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fillTableShort();
}
});
scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(200, 100));
scrollPane.setMaximumSize(new Dimension(32767, 100));
frame.getContentPane().add(scrollPane);
scrollPane.setAlignmentY(Component.TOP_ALIGNMENT);
scrollPane.setBorder(new LineBorder(new Color(0, 0, 0)));
frame.pack();
}
public void fillTableShort() {
tableModel.setRowCount(0);
tableModel.addRow(new Object[]{"short", "short"});
}
public void fillTableLong() {
tableModel.setRowCount(0);
tableModel.addRow(new Object[]{"looooooooooooooooooooong", "looooooooooooooooooooong"});
}
public void updateTableWidths() {
int col_1_width = 0;
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, 0);
Component comp = table.prepareRenderer(renderer, row, 0);
col_1_width = Math.max (comp.getPreferredSize().width, col_1_width);
}
col_1_width += table.getIntercellSpacing().width;
col_1_width += 10;
table.getColumn("Col_1").setMinWidth(col_1_width);
table.getColumn("Col_1").setMaxWidth(col_1_width);
System.out.println("col_1_width was set to " + col_1_width);
int col_2_width = 0;
for (int row = 0; row < table.getRowCount(); row++) {
TableCellRenderer renderer = table.getCellRenderer(row, 1);
Component comp = table.prepareRenderer(renderer, row, 1);
col_2_width = Math.max (comp.getPreferredSize().width, col_2_width);
}
col_2_width += table.getIntercellSpacing().width;
int tableWidth = col_2_width + col_1_width;
if (scrollPane.getVerticalScrollBar().isVisible()) {
tableWidth += scrollPane.getVerticalScrollBar().getWidth();
}
if (tableWidth > scrollPane.getWidth()) {
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
System.out.println("Auto resize was set to AUTO_RESIZE_OFF");
} else {
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
col_2_width = scrollPane.getWidth() + col_1_width;
System.out.println("Auto resize was set to AUTO_RESIZE_LAST COLUMN");
}
table.getColumn("Col_2").setPreferredWidth(col_2_width);
table.getColumn("Col_2").setMinWidth(col_2_width);
System.out.println("col_2_width was set to " + col_2_width + "\n");
}
}
Here is the sequence of steps you can follow to reproduce the behavior:
1) Launch the app
2) Click button "Fill table short"
3) Click button "Update table widths" (in this case only one click is needed)
4) Click button "Fill table long"
5) Click button "Update table widths" (first click)
6) Click button "Update table widths" (second click)
After the second click, it displays properly. I have some debug info that prints to the console, and for both the first and second click, the method seems to be doing exactly the same thing:
col_1_width was set to 152
Auto resize was set to AUTO_RESIZE_OFF
col_2_width was set to 142
So it's not like there is some obvious difference happening the second time around as a result of something that was changed during the first run. And as I mentioned earlier, this only happens when table columns have grown, rather than shrunk.
I am really stumped here. Any ideas?
You probably need to specify the preferred size for the first column as well to work properly with the scroll. Try adding this line:
table.getColumn("Col_1").setPreferredWidth(col_1_width);
in the the block that handles Col_1 calculations in updateTableWidths method. It solved the issue in the posted SSCCE.
EDIT:
There is a dependency on maxWidth and minWidth in setPreferredWidth. Below is the code for setPreferredWidth. So the order may matter.
public void setPreferredWidth(int preferredWidth) {
int old = this.preferredWidth;
this.preferredWidth = Math.min(Math.max(preferredWidth, minWidth), maxWidth);
firePropertyChange("preferredWidth", old, this.preferredWidth);
}
Also exploring setMaxWidth indicates that it may set preferred width as well:
public void setMaxWidth(int maxWidth) {
int old = this.maxWidth;
this.maxWidth = Math.max(minWidth, maxWidth);
if (width > this.maxWidth) {
setWidth(this.maxWidth);
}
if (preferredWidth > this.maxWidth) {
setPreferredWidth(this.maxWidth);
}
firePropertyChange("maxWidth", old, this.maxWidth);
}
Try adding TableColumn#setWidth and/or TableColumn#setPreferredWidth
ie
table.getColumn("Col_1").setWidth(col_1_width);
table.getColumn("Col_1").setPreferredWidth(col_1_width);
//...///
table.getColumn("Col_2").setWidth(col_2_width);
table.getColumn("Col_2").setPreferredWidth(col_2_width);

Accessing a JTextField in JTableHeader

I have made a TableHeader renderer that will create a JTextfield under the Label of the header in a JTable.
The problem i got now, i never get focus/access to this JTextfield in the header.
I found out that a TableHeader renderer only draws the component and dont do the rest, like focus and stuff.
I have tryed to make a array of JTextfield that will set on the header, so i can access them on code base. Unlucky that didnt workout, i was wondering if its possible to get access to this JTextField in the header and what is the best way to do this.
Tableheader renderer:
public class TextFieldTableHeaderRenderer extends AbstractCellEditor implements TableCellRenderer {
private MyPanel component;
public TextFieldTableHeaderRenderer(){
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
component = new MyPanel(column);
((MyPanel)component).setLabelText(value.toString());
return component;
}
#Override
public Object getCellEditorValue() {
return ((MyPanel)component).getTextField();
}
MyPanel:
public class MyPanel extends JPanel {
private javax.swing.JLabel label;
private javax.swing.JTextField textField;
public MyPanel(int column) {
super();
setLayout(new java.awt.BorderLayout());
label = new javax.swing.JLabel();
textField = new javax.swing.JTextField();
setBorder(javax.swing.BorderFactory.createEtchedBorder());
label.setHorizontalAlignment(SwingConstants.CENTER);
//textField.setText("Column "+column);
add(textField, java.awt.BorderLayout.PAGE_END);
add(label, java.awt.BorderLayout.CENTER);
}
public void setLabelText( String text ){
label.setText(text);
}
public void setTextFieldText(String text){
getTextField().setText(text);
}
public javax.swing.JTextField getTextField() {
return textField;
}
/**
* #param textField the textField to set
*/
public void setTextField(javax.swing.JTextField textField) {
this.textField = textField;
}
Install on header:
for( int i=0; i < this.getxColumnModel().getColumnCount(); i++){
this.getxColumnModel().getColumn(i, true).setHeaderRenderer( new TextFieldTableHeaderRenderer() );
}
I have try to use the "EditableHeader" example from the i-net, but it makes a new JTextfield when clicking on the header.
I like to see that the user get focus on the JTextfield, enters a text and then it will filter the column.
Filtering wont be a problem, cause i have made that already.
Hopefully im clear to you guys/girls and love to hear you solution
Here's a simple approach for making editable headers:
EDIT: oops - I meant to post this in another thread. I guess I'll keep it here anyway.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class JTableEditableHeaderDemo implements Runnable
{
private JTable table;
private JTableHeader header;
private JPopupMenu renamePopup;
private JTextField text;
private TableColumn column;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new JTableEditableHeaderDemo());
}
public JTableEditableHeaderDemo()
{
table = new JTable(10, 5);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
header = table.getTableHeader();
header.addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent event)
{
if (event.getClickCount() == 2)
{
editColumnAt(event.getPoint());
}
}
});
text = new JTextField();
text.setBorder(null);
text.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
renameColumn();
}
});
renamePopup = new JPopupMenu();
renamePopup.setBorder(new MatteBorder(0, 1, 1, 1, Color.DARK_GRAY));
renamePopup.add(text);
}
public void run()
{
JFrame f = new JFrame("Double-click header to edit");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private void editColumnAt(Point p)
{
int columnIndex = header.columnAtPoint(p);
if (columnIndex != -1)
{
column = header.getColumnModel().getColumn(columnIndex);
Rectangle columnRectangle = header.getHeaderRect(columnIndex);
text.setText(column.getHeaderValue().toString());
renamePopup.setPreferredSize(
new Dimension(columnRectangle.width, columnRectangle.height - 1));
renamePopup.show(header, columnRectangle.x, 0);
text.requestFocusInWindow();
text.selectAll();
}
}
private void renameColumn()
{
column.setHeaderValue(text.getText());
renamePopup.setVisible(false);
header.repaint();
}
}
TableColumn supports setting a TableCellRenderer via setHeaderRenderer(), as shown in this example; it has no provision for setHeaderEditor(), which would be required for editing. Alternatives might include these:
Write a custom JTableHeader.
Add a row of text fields in an adjacent, conformal layout.
Use a particular row in the TableModel, as suggested in FixedRowExample.
Consider a commercial alternative; several are listed here.

Categories

Resources