Working with layout managers - java

I'm trying to make a kitchen display system using a FlowLayout and I'm trying to figure out a way to add another panel on the 2nd row when the first row is already full. The width of the GUI will change according to user preference. When wider, it should show more of the components per row.

Approach - Variable width with WrapLayout
The GridLayout solution presumes the GUI requires 6 components per row.
For as many cols as needed to fill the width, & then show the components in as many rows as required, look to WrapLayout.
Approach - Variable width with JList
A JList can also be used here, since it seems all the components consist of a single (GUI) object.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.*;
public class ListComponents {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
String[] listData = {
"Component 1", "Component 2", "Component 3",};
final DefaultListModel<String> model
= new DefaultListModel<String>();
for (String datum : listData) {
model.addElement(datum);
}
JList list = new JList(model);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
list.setCellRenderer(new ObjectCellRenderer());
Action addAction = new AbstractAction("Add New") {
#Override
public void actionPerformed(ActionEvent e) {
model.addElement("New Component");
}
};
JButton addNew = new JButton(addAction);
JPanel ui = new JPanel(new BorderLayout(3, 3));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
ui.add(new JScrollPane(list), BorderLayout.CENTER);
ui.add(addNew, BorderLayout.PAGE_START);
JFrame f = new JFrame("Component List");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setContentPane(ui);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
}
class ObjectCellRenderer extends DefaultListCellRenderer {
Border border = new EmptyBorder(20, 5, 20, 5);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
label.setBorder(border);
return label;
}
}
Approach - Fixed width with GridLayout
Using a GridLayout, when we know we always want a fixed number per row, regardless of width.
JPanel mainPanel = new JPanel(new GridLayout(0,6));
See new GridLayout(rows,cols):
Creates a grid layout with the specified number of rows and columns. All components in the layout are given equal size.
One, but not both, of rows and cols can be zero, which means that any number of objects can be placed in a row or in a column.
See this code for an example using a GridLayout(0,2) for the labels beneath Add Another Label

You can use a FlowLayout. From the tutorial How to Use FlowLayout:
The FlowLayout class puts components in a row, sized at their preferred size. If the horizontal space in the container is too small to put all the components in one row, the FlowLayout class uses multiple rows. If the container is wider than necessary for a row of components, the row is, by default, centered horizontally within the container.
EDIT If you want your layout to scroll vertically when there are too many rows, you can use a JScrollPane with horizontal scrolling disabled. You can do that with:
js.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
(where js is the JScrollPane that contains your FlowLayout panel).
EDIT 2 Well, the above isn't enough. You also need to set a viewport view on the JScrollPane that will track the JScrollPane width. Here's a class that does this (taken from here):
static class MyPanel extends JPanel implements Scrollable{
#Override
public Dimension getPreferredScrollableViewportSize() {
return this.getPreferredSize();
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 50;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 80;
}
/*
* (non-Javadoc)
* #see javax.swing.Scrollable#getScrollableTracksViewportWidth()
*/
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
You would use this by adding your current FlowLayout layout to an instance of MyPanel (instead of directly to the JScrollPane) and then calling
js.setViewportView(myPanelInstance);

Related

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.

Dynamically adding to JScrollPane without removing child components

I'm currently adding JPanels to a JScrollPane by pressing a button:
However, I can't get the panels to stack on top of each other when adding more than 1 panel to the scroll pane. The previously added panel in the scroll pane seems to be getting removed when I call getViewport().add().
Is there a way to get a JScrollPane to save it's children and make my GUI look like this when adding multiple JPanels with a button press?
MainWindow class:
JScrollPane trackerPanel = new JScrollPane();
trackerPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
trackerPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
trackerPanel.setPreferredSize(new Dimension(500, 400));
frame.getContentPane().add(BorderLayout.NORTH, trackerPanel);
frame.setVisible(true);
//method: called from EnterButtonListener to create and append TrackerTile to GUI
public void addTrackerTile() {
incrementTrackerPanelCounter();
TrackerTile trackerTile = new TrackerTile();
trackerPanel.getViewport().add(trackerTile);
}
TrackerTile class:
public class TrackerTile extends JPanel implements Scrollable {
public Dimension preferredSize = new Dimension(794, 100);
public TrackerTile() {
setBorder(BorderFactory.createLineBorder(Color.BLACK));
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(794, 100);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
The basic logic to create the GUI should be:
JPanel somePanel = new JPanel( some layout );
JScrollPane scrollPane = new JScrollPane( somePanel );
frame.add(scrollPane. BorderLayout.CENTER);
Then in the ActionListener that adds new panels you just do:
somePanel.add( new TrackerTile() );
So its just like adding components to a panel. The only difference is that the panal has been added to a scroll pane.
There is no need to implement Scrollable. But if you really want to customize the Scrollable properties, then you need to implement Scrollable on the "somePanel", not the individual components you add to "somePanel".
Or another option is to use the Scrollable Panel which implements the Scrollable interface and has methods that allow you to customize the behaviour.

JScrollPane scrolling issue

Initially all the components are aligning only in the Horizontal direction in a single row.
Then I set the size of scroll enabled panel as below
main.setPreferredSize(scroll.getViewport().getSize());
main.setMaximumSize(new Dimension(scroll.getViewport().getWidth(),Integer.MAX_VALUE));
And it worked the components start aligning in multiple lines,and not going beyond the screen horizontally. But why the vertical scroll is not happening, In fact the components are getting overridden, the height is also getting fixed as of viewport size even the maxsize is defined.
Please help.........
What i need is only vertical scrolling..... The components should not go beyond screen horizontally, but can go beyond screen vertically.
UPDATE with the code: Now all I want is the buttons should not go beyond the screen horizontally instead can use vertical scroll if the whole window is occupied.
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("JAVA GUI");
JPanel main = new JPanel();
JScrollPane scroll = new JScrollPane(main);
BoxLayout box = new BoxLayout(main, BoxLayout.Y_AXIS);
main.setLayout(box);
main.add(new JLabel("row 1"));
JPanel panel1 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel1.add(new JButton("b"+i));
}
JPanel panel2 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel2.add(new JButton("b"+i));
}
JPanel panel3 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel3.add(new JButton("b"+i));
}
Border border = BorderFactory.createLineBorder(Color.black,10);
panel1.setBorder(border);
panel2.setBorder(border);
panel3.setBorder(border);
main.add(panel1);
main.add(new JLabel("row2"));
main.add(panel2);
main.add(new JLabel("row3"));
main.add(panel3);
frame.setContentPane(scroll);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.setVisible(true);
}
}
Scrollbars only appear when the preferred size of the component is greater than the size of the scrollpane.
A FlowLayout will wrap components, but it will not change the preferred size.
Check out the Wrap Layout which extends FlowLayout and will recalculate the preferred size for you when components wrap.
As camickr pointed out in https://stackoverflow.com/a/21816061 , the main problem here is that the FlowLayout does not properly compute the preferred size of the component. The WrapLayout that he linked solves this issue basically. But in this case, this is still not sufficient, because the preferred size of the panels will simply be "as wide as necessary to show all buttons". In order to really wrap the buttons, you also have to replace your main panel with a panel that implements the Scrollable interface and returns true in Scrollable#getScrollableTracksViewportWidth()
class ScrollablePanel extends JPanel implements Scrollable
{
#Override
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return 1;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return 1;
}
#Override
public boolean getScrollableTracksViewportWidth()
{
return true;
}
#Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
}
So in your code:
JPanel main = new ScrollablePanel(); // Use ScrollablePanel here
JScrollPane scroll = new JScrollPane(main);
...
JPanel panel1 = new JPanel(new WrapLayout()); // Use WrapLayout here
for(int i=0;i<200;i++){
panel1.add(new JButton("b"+i));
}

Adding rows of JTextFields to a JScrollPane

I need some advice on which Swing Components to choose in order to achieve the following:
I have something like a "table" structure, that every time that I click the "Add" button, another "row" should be inserted on the "table". Each row is composed of 2 JTextField. The problem I am having with the GridLayout (the layout used in the pictures below) is that if I add another row, then the heights of the text fields will be shortened and I don't want that (picture on the right), I want to preserve the same height for every row.
What I would like to happen is to have the extra row appear below the last one, so that I could use the JScrollPane and scroll to see it.
Should I use another layout rather than the GridLayout? Maybe the AbsoluteLayout or even using the Table Component?
Thanks.
I would use a JTable and set the row height to whatever you desire. For example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class TableEg {
private static final int ROW_HEIGHT = 40;
private static final String[] TABLE_COLUMNS = {"Foo", "Bar"};
private static void createAndShowGui() {
final DefaultTableModel tableModel = new DefaultTableModel(TABLE_COLUMNS, 2);
JTable table = new JTable(tableModel );
table.setRowHeight(ROW_HEIGHT);
JScrollPane scrollpane = new JScrollPane(table);
JButton addRowBtn = new JButton(new AbstractAction("Add Row") {
#Override
public void actionPerformed(ActionEvent arg0) {
tableModel.addRow(new String[]{"", ""});
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(addRowBtn);
JFrame frame = new JFrame("TableEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(scrollpane, BorderLayout.CENTER);
frame.getContentPane().add(btnPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Let's say that you add your JTextField in a Jpanel, and you Jpanel in a JScrollPane. Now you would set a preferredSize for you JScrollPane (since you want a fixed displayable area), but you modify the height of the JPanel dinamically based in the amount of JTextField you are placing at that time.
In other words, if you have 2 JTextField in a Jpanel set with GridLayout of 2 columns; you JTextField would take the entire height of the Jpanel; but if you include 2 more they would have to make room for this two and therefore shortening.
Notice that you have 2 rows in a JPanel set to "100" height each row would use "50". if you add another row but also modify the height of the JPanel to "150" each JTextField would still take "50" pixels.
I am a newbie, and it is a possibility that i am not 100% right; if that it is the case "Sorry". I just wanted help.
Try this....
JScrollPane scroll = new JScrollPane(Your_Table);
This will make your table to scroll....

Resize Swing component when content has changed dynamically

I have, the same issue with two components JTextField and JComboBox, I assume the solution I'm looking for would solve it for all components.
I have set the size of the components to default thus their size fits the initial content I supplied to it. when I change the content of a component to exceeds the region of the component, I cannot see the whole text, and I would like my component to resize to fit the text.
How can I accomplish that?
Update:
The pack() on the frame only enlarged the text field, how can I do the same and enlarge the combo box?
Update:
private class ComboBoxRenderer extends JLabel implements ListCellRenderer {
private static final long serialVersionUID = 752379460716217273L;
Dimension maxSize=new Dimension();
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
Dimension size = getPreferredSize();
if(maxSize.width<size.width)
maxSize.width=size.width;
if(maxSize.height<size.height)
maxSize.height=size.height;
resolutionDescriptor_ComboBox.setPreferredSize(maxSize);
return this;
}
}
this works, not very efficient, but it is a first step, thing is, it does not take the button image into size considerations, so some of the text is still not shown, but the component resizes, do you have any suggestions?
Adam.
Answer:
This did the trick together with a pack(), no revalidation needed.
private class ComboBoxRenderer extends JLabel implements ListCellRenderer {
private static final long serialVersionUID = 752379460716217273L;
Dimension maxSize=new Dimension();
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
Dimension size = getPreferredSize();
if(maxSize.width<size.width) {
maxSize.width=size.width;
resolutionDescriptor_ComboBox.setPrototypeDisplayValue(value.toString());
}
if(maxSize.height<size.height)
maxSize.height=size.height;
return this;
}
}
make sure you design something more efficient then this...
Update:
and there is no need for the pack()!
Adam.
JComboBox has setPrototypeDisplayValue(Object) method that is used to calculate component's preferred width based on the length of the parameter. Try that.
And instead of pack() use doLayout() together with some revalidate() or repaint()
Do a pack() on the frame
To resize the combo box you can try:
comboBox.setModel( comboBox.getModel() );
I believe this should cause the preferred size of the combo box to be recalculated. Of course you would then need to do the pack() again.
Edit:
Added a simple SSCCE that shows this works:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ComboBoxTest3 extends JFrame implements ActionListener
{
JComboBox comboBox;
JTextField textField;
public ComboBoxTest3()
{
String[] tabs = {"one", "two", "three", "four", "five", "six", "seven" };
DefaultComboBoxModel model = new DefaultComboBoxModel(tabs);
comboBox = new JComboBox( model );
textField = new JTextField("hello");
add(comboBox, BorderLayout.WEST );
add(textField, BorderLayout.EAST );
JButton button = new JButton("Pack");
button.addActionListener( this );
add(button, BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent e)
{
textField.setText("hello there!");
comboBox.addItem("some longer text");
comboBox.setModel( comboBox.getModel() );
pack();
}
public static void main(String[] args)
{
JFrame frame = new ComboBoxTest3();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
}

Categories

Resources