I got a JTable, which i applied AUTO_RESIZE_LAST_COLUMN to. It auto resizes last column when i drag the columns left or right..
However, the JTable are attached to a JPanel with a BorderLayout manager. When i resize the JFrame, the JPanel resize, and since the JTable fills the JPanel, the JTable resizes too. But when i resize the JFrame, the AUTO_RESIZE_LAST_COLUMN doesnt works, but instead it resizes all the columns.
I want it to only auto_resize the last column, when the JFrame changes size, instead of resize all columns.
code:
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class JTableResize extends JFrame {
private JTable table;
private JPanel panel;
private JScrollPane pane;
DefaultTableModel model = new DefaultTableModel();
public JTableResize() {
super("JTable - Resize Problem");
setLayout(new BorderLayout());
panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBackground(Color.red);
add(panel, BorderLayout.CENTER);
table = new JTable(model);
//panel.add(table);
model.addColumn("Resize");
model.addColumn("Problem");
model.addColumn("........");
model.addColumn("This should resize");
pane = new JScrollPane(table);
panel.add(pane);
//this is supposed to resize last column.. It works when you drag in the columns, but not when frame are resized
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
table.getTableHeader().setReorderingAllowed(false);
table.setShowVerticalLines(false);
for (int i = 0; i <= 50; i++) {
model.addRow(new Object[] {i, i, i, i});
}
}
public static void main(String[] args) {
JTableResize jtr = new JTableResize();
jtr.setSize(500, 500);
jtr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jtr.setVisible(true);
}
}
Check out the API documentation from the doLayout() method of JTable.
Before the layout begins the method gets the resizingColumn of the tableHeader. When the method is called as a result of the resizing of an enclosing window, the resizingColumn is null. This means that resizing has taken place "outside" the JTable and the change - or "delta" - should be distributed to all of the columns regardless of this JTable's automatic resize mode.
So this behaviour is not support by default.
Overriding the doLayout() method of the JTable and setting the "resizing column" to the last column seems to do the trick:
#Override
public void doLayout()
{
if (tableHeader != null)
{
TableColumn resizingColumn = tableHeader.getResizingColumn();
// Viewport size changed. Increase last columns width
if (resizingColumn == null)
{
TableColumnModel tcm = getColumnModel();
int lastColumn = tcm.getColumnCount() - 1;
tableHeader.setResizingColumn( tcm.getColumn( lastColumn ) ) ;
}
}
super.doLayout();
}
Related
I have a JTable for which the renderer returns a JPanel composed of multiple JLabel instances. One of those JLabels can contain HTML used among other things to split the output over multiple lines using <br/> tags.
To show the multiple lines in the table, the renderer calls in the getTableCellRendererComponent method
table.setRowHeight(row, componentToReturn.getPreferredSize().height);
to dynamically update the row height, based on the contents. This only works correctly if componentToReturn indicates a correct preferred size.
It looks however that the getPreferredSize returns bogus values. The preferred height of the returned component is smaller than the sum of the heights of the labels inside the component.
Here is a little program illustrating this behaviour (without using a JTable)
import java.awt.*;
import javax.swing.*;
public class SwingLabelTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
LabelPanel renderer = new LabelPanel();
Component component = renderer.getComponent(false);
//asking for a bigger component will not
//update the preferred size of the returned component
component = renderer.getComponent(true);
}
});
}
private static class LabelPanel {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public LabelPanel() {
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
public Component getComponent( boolean aMultiLineProperty ) {
titleLabel.setText("Title");
if ( aMultiLineProperty ){
propertyLabel.setText("<html>First line<br/>Property: value</html>");
} else {
propertyLabel.setText("Property: value");
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
return compositePanel;
}
}
}
As I am aware that the previous example is a bit far-fetched, here an example which includes a JTable
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class SwingTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
DefaultTableModel tableModel = new DefaultTableModel(0, 1);
JTable table = new JTable(tableModel);
table.setDefaultRenderer(Object.class, new DataResultRenderer());
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
JFrame testFrame = new JFrame("TestFrame");
testFrame.getContentPane().add(new JScrollPane(table));
testFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
testFrame.setSize(new Dimension(300, testFrame.getPreferredSize().height));
testFrame.setVisible(true);
}
});
}
private static class DataResultRenderer implements TableCellRenderer {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public DataResultRenderer() {
JPanel labelPanel = new JPanel();
labelPanel.setOpaque(false);
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
titleLabel.setText("Title");
if ( row == 2 ){
propertyLabel.setText("<html>Single property: value</html>");
} else {
String text = "<html>";
text += "First property<br/>";
text += "Second property<br/>";
text += "Third property:value";
text += "</html>";
propertyLabel.setText(text);
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
table.setRowHeight(row, compositePanel.getPreferredSize().height);
return compositePanel;
}
}
}
I am looking for a way to update the row height of the table to ensure that the multi-line content is completely visible, without knowing up front how many lines each row will contain.
So either I need a solution to retrieve the correct preferred size, or my approach is completely wrong and then I need a better one.
Note that the above examples are simplified. In the real code, the "renderer" (the code responsible for creating the component) is decorated a few times. This means that the outer renderer is the only with access to the JTable, and it has no knowledge about what kind of Component the inner code returns.
Because setRowHeight() "Sets the height, in pixels, of all cells to rowHeight, revalidates, and repaints," the approach is unsound. Absent throwing an exception, profiling shows 100% CPU usage as an endless cascade of repaints tries to change the row height repeatedly. Moreover, row selection becomes unreliable.
Some alternatives include these:
Use TablePopupEditor to display multi-line content on request from a TableCellEditor.
Update an adjacent multi-line panel from a TableModelListener, as shown here.
I have this code for show some data inside a JTable:
public class ShowResults extends JFrame {
public ShowResults(List<String> list) {
setSize(1000, 1000);
setLocationRelativeTo(null); //center
setVisible(true);
String[][] table_data = new String[pics.size()][table_header.length];
for (int i = 0; i < pics.size(); i++) {
//Fill table with data
}
JTable table = new JTable(new DefaultTableModel(table_data, table_header)) {
#Override
public boolean isCellEditable(int row, int column) {
//disable table editing
return false;
}
};
JScrollPane scroll_pane = new JScrollPane(table);
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
table.getTableHeader().setReorderingAllowed(false);
table.getTableHeader().setResizingAllowed(false);
this.setLayout(new FlowLayout());
this.add(scroll_pane);
}
}
But table doesn't look as i expect. I would that scroll bar in only vertical (now is horizontal) and header of table show entire header names (now table is wrapped for some reason i don't know).
How can i do?
I would that scroll bar in only vertical (now is horizontal) and header of table show entire header names (now table is wrapped for some reason i don't know).
The reason is you set a new FlowLayout as the content pane's layout manager. This layout manager honors the components preferred size and therefore prevents the scroll pane resizes as you wish. I'd leave the default layout manager, which is BorderLayout, and add the scroll pane to the CENTER location.
On the other hand, you should make your frame visible after adding all your components to its content pane.
I would use a grid layout. Also try removing the scroll_pane for now and see if that makes a difference. I ran into problems using JScrollPane myself.
It is difficult to describe what happens so here is a SSCCE:
public class TableInScrollPaneTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TableInScrollPaneTest();
}
});
}
public TableInScrollPaneTest() {
JFrame frame = new JFrame();
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
LayoutManager layout;
layout = new MigLayout("wrap 1, fill", "fill, grow", "[fill, grow 1][fill, grow 2]");
//layout = new GridLayout(2,1);
JPanel panel = new JPanel(layout);
JPanel placeHolder = new JPanel();
JPanel placeHolder2 = new JPanel();
placeHolder.setBackground(Color.GRAY);
placeHolder2.setBackground(Color.LIGHT_GRAY);
JTable table = this.createTable();
JScrollPane tableScrollPane = new JScrollPane(table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//table.setPreferredScrollableViewportSize(new Dimension(5000,200));
panel.add(placeHolder, "");
panel.add(tableScrollPane, "");
//panel.add(placeHolder2, "");
frame.setContentPane(panel);
frame.setVisible(true);
}
private JTable createTable() {
String[] columnNames = "Name 1,Name 2,Name 3,Name 4,Name 5".split(",");
int rows = 30;
int cols = columnNames.length;
String[][] data = new String[rows][cols];
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
data[i][j] = "R"+i+" C"+j;
}
}
JTable table = new JTable(data, columnNames);
return table;
}
}
I want the scrollPane to use all available space when the frame gets resized by the user. But additionally I want another panel (placeHolder) to grow, too. For example the heigth of the frame divided by 3, the placeHolder gets 1 part and the scrollPane gets 2 parts. The GridLayout is kinda doing what I need, but I'm looking for a solution with MigLayout.
If you uncomment panel.add(placeHolder2, ""); and comment the line above it works.
EDIT1:
I tried using the sizegroup contraint. Now the placeHolder and the scrollPane have the same size, but I want the scrollPane take twice as much space. (Additionally in my programm I dont have a placeholder like this, but rather there are some labels, checkboxes and textfields. So sizegroup probably is not what I need except there are more ways to use it)
I found the solution here:
MigLayout confused by JTable objects contained in JScrollBar objects
and here:
How to set JScrollPane to show only a sepecific amount of rows
table.setPreferredScrollableViewportSize(null);
or
table.setPreferredScrollableViewportSize(table.getPreferredSize());
I don't know what's the difference between this code snippets, but they both do what I need.
I was wondering the following: I have a MainWindow component (which contains a frame (JFrame)) and several other JPanels. Where one JPanel, let's say gridPanel uses the gridLayout as LayoutManager. Now my problem is that I want to adjust (set the size of rows / set the size of columns) after the window has been resized. Can someone tell me how I can achieve actions that can be triggered after resizing the frame as I am not familiar with the listeners involved.
It should be the done on the most "standard" coding practices. Thank you for your response and answers!
If you want your grid to "fill up" a container, or fill up the JFrame, then key is to use proper layout managers to hold the GridLayout-using container. For instance if you add the GridLayout-using container to another container that uses FlowLayout, then the GridLayout-using container will not change size if its holding container changes size. However if you add the GridLayout-using container to another container that uses BorderLayout and to its BorderLayout.CENTER position, then the GridLayout-using container will resize as the BorderLayout-using parent container resizes.
Example:
import java.awt.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class ExpandingGrid extends JPanel {
private static final int GAP = 5;
public ExpandingGrid() {
// create a BorderLayout-using JPanel
JPanel borderLayoutPanel = new JPanel(new BorderLayout());
borderLayoutPanel.setBorder(BorderFactory.createTitledBorder("BorderLayout Panel"));
borderLayoutPanel.add(createGridPanel(), BorderLayout.CENTER); // add a Grid to it
// create a FlowLayout-using JPanel
JPanel flowLayoutPanel = new JPanel(new FlowLayout());
flowLayoutPanel.setBorder(BorderFactory.createTitledBorder("FlowLayout Panel"));
flowLayoutPanel.add(createGridPanel()); // add a grid to it
// set up the main JPanel
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
setLayout(new GridLayout(1, 0, GAP, 0)); // grid with 1 row
// and add the borderlayout and flowlayout using JPanels to it
add(borderLayoutPanel);
add(flowLayoutPanel);
}
// create a JPanel that holds a bunch of JLabels in a GridLayout
private JPanel createGridPanel() {
int rows = 5;
int cols = 5;
JPanel gridPanel = new JPanel(new GridLayout(rows, cols));
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// create the JLabel that simply shows the row and column number
JLabel label = new JLabel(String.format("[%d, %d]", i, j),
SwingConstants.CENTER);
// give the JLabel a border
label.setBorder(BorderFactory.createEtchedBorder());
gridPanel.add(label); // add to the GridLayout using JPanel
}
}
return gridPanel;
}
private static void createAndShowUI() {
JFrame frame = new JFrame("ExpandingGrid");
frame.getContentPane().add(new ExpandingGrid());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
Also, if this is not helpful, then you may wish to elaborate more on your problem and post code, preferably an SSCCE.
That's why I wanted to only adjust the columns.
Maybe the Wrap Layout is what you are looking for.
I am putting a JTable into a JScrollPane
But When I set JTable Auto Resizeable, then it won't have horizontal scroll bar.
if I set AUTO_RESIZE_OFF, then the Jtable won't fill the width of its container when the column width is not big enough.
So how can I do this:
when the table is not wide enough, expand to fill its container width
when the table is wide enough, make it scrollable.
Thanks
You need to customize the behaviour of the Scrollable interface.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableHorizontal extends JFrame
{
public TableHorizontal()
{
final JTable table = new JTable(10, 5)
{
public boolean getScrollableTracksViewportWidth()
{
return getPreferredSize().width < getParent().getWidth();
}
};
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
final JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
}
public static void main(String[] args)
{
TableHorizontal frame = new TableHorizontal();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setSize(400, 300);
frame.setVisible(true);
}
}
The above code basically sizes the component at its preferred size or the viewport size, whichever is greater.
If for some reason customising JTable is not an option (e.g. it might be created in third-party code), you can achieve the same result by setting it to toggle between two different JTable AUTO_RESIZE modes whenever the containing viewport is resized, e.g.:
jTable.getParent().addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(final ComponentEvent e) {
if (jTable.getPreferredSize().width < jTable.getParent().getWidth()) {
jTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
} else {
jTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
}
}
});
I found that all that is needed is to include
table = new JTable(model);
// this enables horizontal scroll bar
table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
and then when the required viewport width and height have been calculated, include
frame.getContentPane().add(new JScrollPane(table))
table.setPreferredScrollableViewportSize(new Dimension(width,height));
If you set the Layout of its container to BorderLayout with a BorderLayout.CENTER layout constraint, then the JTable will auto resize to fit its container.
If you want to make a component scrollable, you can wrap the JTable with a JScrollPane.
setLayout(new BorderLayout());
add(new JScrollPane(new JTable()), BorderLayout.CENTER);