ScrollPane expanding when components added - java

I've spent a while creating an sscce from my larger program, I hope it's small enough!
I've a JSplitPane with a table on top, and below is a JPanel.
The bottom panel contains smaller JPanels, or 'entries'. As the number of entries grows, the bottom SplitPane takes up the space of the top pane.
Dimension dim = getPreferredSize();
setPreferredSize(dim);
In the first class, uncommenting this code solves the problem, but I've no idea why. I'd like to understand how to deal with resizing better if anyone can help? (I've also got to deal with the text area expanding horizontally, which is one of the reasons some of the constraints might look a little odd)
Here's the bottom panel:
package example;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
public class BottomPanel extends JPanel{
private JLabel summary = new JLabel();
private EntryPanel entryPanel = new EntryPanel();
public BottomPanel() {
//Dimension dim = getPreferredSize();
//setPreferredSize(dim);
setLayout(new GridBagLayout());
entryPanel.setBorder(BorderFactory.createLineBorder(Color.RED));
setComponents();
}
private void setComponents() {
Border test = BorderFactory.createLineBorder(Color.BLUE);
JScrollPane jsp = new JScrollPane(entryPanel);
JPanel dummy1 = new JPanel();
JPanel dummy2 = new JPanel();
JPanel dummy3 = new JPanel();
dummy1.setBorder(test);
dummy2.setBorder(test);
dummy3.setBorder(test);
int row = 0;
add(dummy1, new GBC(0,row).setWeight(0,0));
// new row
row++;
add(summary, new GBC(1,row).setAnchor(GBC.LINE_START));
// new row
row++;
add(jsp, new GBC(1,row).setWeight(50,70).setFill(GBC.BOTH));
// new row
row++;
add(dummy2, new GBC(2,row).setWeight(0,0));
}
private class EntryPanel extends JPanel{
private List<JPanel> entries = new ArrayList<>();
private int row = 0;
private EntryPanel(){
setLayout(new GridBagLayout());
for(int x = 0; x < 20; x++)
createEntryItem("TEST", "test text");
}
private void createEntryItem(String s, String text){
Border test = BorderFactory.createLineBorder(Color.GREEN);
JPanel entryItem = new JPanel();
entryItem.setBorder(test);
entryItem.setLayout(new GridBagLayout());
JLabel title = new JLabel(s);
JTextArea details = new JTextArea(text);
entryItem.add(title, new GBC(0,0).setAnchor(GBC.LINE_START));
entryItem.add(details, new GBC(0,1).setAnchor(GBC.LINE_START));
entryItem.add(new JPanel(), new GBC(1,0).setWeight(100,0));
entries.add(entryItem);
displayEntry(entryItem);
}
private void displayEntry(JPanel entry){
JPanel dummy = new JPanel();
add(entry, new GBC(0,row).setAnchor(GBC.LINE_START).setFill(GBC.HORIZONTAL));
add(dummy, new GBC(1,row).setWeight(100, 0));
row++;
}
}
}
and the containing frame and panel:
package example;
import java.awt.*;
import javax.swing.*;
public class Example extends JFrame{
private GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
private int width = (int)((gd.getDisplayMode().getWidth())*0.5);
private int height = (int)((gd.getDisplayMode().getHeight())*0.75);
private JPanel mainPanel = new JPanel();
private JSplitPane sp;
private JTable table = new JTable();
private BottomPanel bp = new BottomPanel();
public Example () {
setSize(width,height);
setLayout(new BorderLayout());
sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,new JScrollPane(table), bp);
add(sp);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
Example ex = new Example();
}
});
}
}
and the GridBagHelper:
package example;
import java.awt.GridBagConstraints;
public class GBC extends GridBagConstraints{
public GBC(int gridx, int gridy){
this.gridx = gridx;
this.gridy = gridy;
}
public GBC setAnchor(int anchor){
this.anchor = anchor;
return this;
}
public GBC setFill(int fill){
this.fill = fill;
return this;
}
public GBC setWeight(double weightx, double weighty){
this.weightx = weightx;
this.weighty = weighty;
return this;
}
}
Any help or advice would be really appreciated - I'm self taught and am struggling a fair bit!
Thanks

I'm too tired to write a proper answer, but I'm glancing through this and one of the first issues I see is setSize(width,height) inside the constructor for Example. It's better to call pack() on the JFrame and let it size everything itself automatically.
I think pack can be a bit unpredictable with a JScrollPane, though. From memory, I've had it do some weird things like collapse the scroll pane to its minimum size.
Using pack on your code example seems to attempt to size it around the sum of the panels in BottomPanel. (The sum would be taller than my screen resolution, though, so I can't tell what the exact behavior is. It caps the height of the window at the height of my screen.)
A more elegant way to do what you're trying to do is like this:
public Example () {
// setSize(width,height);
// setLayout(new BorderLayout());
sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,new JScrollPane(table), bp);
sp.setResizeWeight(2.0 / 3.0);
sp.setPreferredSize(width, height);
setContentPane(sp);
// add(sp);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
I used setResizeWeight to achieve the split pane proportions and set the preferred size of the pane instead of the window.
This is pretty similar to the result that you like but it's a little more proper. It could be that one of the more experienced Swing users here knows a better way to use JScrollPane which e.g. doesn't involve an explicit setPreferredSize.

I guess my question is why getting the preferredSize, and then setting the preferredSize makes a difference?
If we dig down through the code, you'll find...
public void setPreferredSize(Dimension preferredSize) {
Dimension old;
// If the preferred size was set, use it as the old value, otherwise
// use null to indicate we didn't previously have a set preferred
// size.
if (prefSizeSet) {
old = this.prefSize;
}
else {
old = null;
}
this.prefSize = preferredSize;
prefSizeSet = (preferredSize != null);
firePropertyChange("preferredSize", old, preferredSize);
}
You can the have a look at how getPreferredSize works...
public boolean isPreferredSizeSet() {
return prefSizeSet;
}
/**
* Gets the preferred size of this component.
* #return a dimension object indicating this component's preferred size
* #see #getMinimumSize
* #see LayoutManager
*/
public Dimension getPreferredSize() {
return preferredSize();
}
/**
* #deprecated As of JDK version 1.1,
* replaced by <code>getPreferredSize()</code>.
*/
#Deprecated
public Dimension preferredSize() {
/* Avoid grabbing the lock if a reasonable cached size value
* is available.
*/
Dimension dim = prefSize;
if (dim == null || !(isPreferredSizeSet() || isValid())) {
synchronized (getTreeLock()) {
prefSize = (peer != null) ?
peer.getPreferredSize() :
getMinimumSize();
dim = prefSize;
}
}
return new Dimension(dim);
}
This will return the user set preferredSize if it was set, over calculating its own.
surely i'm assigning a value that was already in place?
But you call setPreferredSize before any components are added to the container
If you change the code to something like...
Dimension dim = getPreferredSize();
System.out.println(dim);
setPreferredSize(dim);
it will print out something like java.awt.Dimension[width=10,height=10], so, yeah, small...
But, if you use something like...
public BottomPanel() {
setLayout(new GridBagLayout());
entryPanel.setBorder(BorderFactory.createLineBorder(Color.RED));
setComponents();
Dimension dim = getPreferredSize();
System.out.println(dim);
setPreferredSize(dim);
}
it prints out java.awt.Dimension[width=105,height=710]
The basic moral to the story is, don't call setPreferred/Minimum/MaximumSize unless you have a very, very good reason to and even then, consider overriding getPreferredSize instead. It's no one else's responsibility to calculate the size of the component, but the componet itself

Related

Java My images are being cut bottom and top

public class Main {
public static class GUI extends JFrame {
public GUI() {
//Title
super("Draw Card");
//panel
Panel buttonsPanel = new Panel();
Panel imagePanel = new Panel();
//App layout
setSize(600,480);
setLayout(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints();
//gc value
gc.gridy=0;gc.gridx=0;gc.gridwidth=1;gc.gridheight=1;gc.weightx=0.5;gc.weighty=0;gc.fill = GridBagConstraints.HORIZONTAL;
//layout in layout
GridLayout buttonsLayout = new GridLayout(1,2);
GridLayout imageLayout = new GridLayout(4,13);
buttonsPanel.setLayout(buttonsLayout);
imagePanel.setLayout(imageLayout);
//add
add(buttonsPanel,gc);
//change gc value
gc.gridy=1;gc.weighty=1;gc.gridheight=9;gc.fill = GridBagConstraints.BOTH;
//add
add(imagePanel,gc);
//button
JButton btn = new JButton("Draw card");
buttonsPanel.add(btn);
buttonsPanel.add(new JButton("Remove cards."));
//event
btn.addActionListener(e ->{
//just image from link
Image image = null;
try {
URL url = new URL("https://www.improvemagic.com/wp-content/uploads/2020/11/kj.png");
image = ImageIO.read(url);
} catch (IOException ee) {
ee.printStackTrace();
}
//add to label then add label to panel
JLabel label = new JLabel(new ImageIcon(image));
imagePanel.add(label);
revalidate();
});
//set visible
setVisible(true);
}
}
public static void main(String[] args){
GUI test = new GUI();
}
}
EDITED: I dont think i can make it shorter without destroying everything, only one image and it's online, got the same problem that i have.
I've tryed a couple a thing on the pannel and the layout to give the image size for each cell but didnt worked.
Hello ! I have some trouble to keep the full image, i didn't find a single way of getting them fully, did I miss something about those layout ?
I'm still not used to post here ask me if i need to add things ! Thank you !
I have a baglayout that contain 2 gridlayout (one for button and another one where I want to add random card by clicking on one button)
Introduction
I reworked your code to create the following GUI.
Here's the same GUI after drawing a few cards.
The JFrame worked out to be 822 x 420 pixels. In Swing, you work from the inside out. You create Swing components, put the Swing components in JPanels, put the JPanels in a JFrame, and see what the size of the JFrame turns out to be.
Explanation
Whenever I create a Swing GUI, I use the model/view/controller (MVC) pattern. This pattern implies that you create the model first, then the view, then the controller. For complex projects, the process is more iterative than waterfall.
The MVC pattern in Swing development means:
The view reads information from the model.
The view does not modify the model.
The controller modifies the model and updates the view.
There's usually not one controller class "to rule them all". Each Action or ActionListener class is responsible for its portion of the model and the view.
Model
I assumed you eventually wanted to read more than one card image, so I created a Card class. This class holds the card Image and whatever information you want about a playing card, including descriptive text, suit, int value, and int suit value.
I created a SomeCardGameModel class to hold a blank card, a card, and an int cardCount. The cardCount helps ensure that we don't draw more than 52 cards.
Reducing the size of the card image turned out to be a bit of a challenge. I used a search engine to find relevant bits of code and put them together in the SomeCardGameModel class. The main point is that you do all of the model creation before you create the view.
View
I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components will be created and executed on the Event Dispatch Thread.
I separated the creation of the JFrame and the JPanels into separate methods. This makes the code much easier for people to understand what you're doing.
The JFrame has a default BorderLayout. I used a BorderLayout for the main JPanel and GridLayouts for the button JPanel and the card JPanel. I added some spacing between the Swing components so you can see individual cards.
Controller
After creating a model and a view, your anonymous controller class is simplified. You should be able to create the controller for the "remove Cards" JButton.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I can post this code as one block.
import java.awt.BorderLayout;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SomeCardGame implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SomeCardGame());
}
private JLabel[] cardLabels;
private final SomeCardGameModel model;
public SomeCardGame() {
this.model = new SomeCardGameModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Draw Card");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println(frame.getSize());
}
public JPanel createMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.add(createButtonPanel(), BorderLayout.NORTH);
panel.add(createCardPanel(), BorderLayout.CENTER);
return panel;
}
public JPanel createButtonPanel() {
JPanel panel = new JPanel(new GridLayout(0, 2, 5, 5));
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JButton drawButton = new JButton("Draw card");
panel.add(drawButton);
drawButton.addActionListener(e -> {
int count = model.getCardCount();
if (count < 52) {
cardLabels[model.getCardCount()].setIcon(
new ImageIcon(model.getCard().getCardImage()));
model.incrementCardCount(1);
}
});
JButton removeButton = new JButton("Remove cards");
panel.add(removeButton);
return panel;
}
public JPanel createCardPanel() {
JPanel panel = new JPanel(new GridLayout(0, 13, 5, 5));
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.cardLabels = new JLabel[52];
for (int index = 0; index < cardLabels.length; index++) {
cardLabels[index] = new JLabel(new ImageIcon(
model.getBlankCard().getCardImage()));
panel.add(cardLabels[index]);
}
return panel;
}
public class SomeCardGameModel {
private int cardCount;
private final Card blankCard;
private final Card card;
public SomeCardGameModel() {
this.blankCard = new Card(createBlankCard());
BufferedImage image = readCard();
Image reducedImage = image.getScaledInstance(
56, 78, Image.SCALE_SMOOTH);
this.card = new Card(toBufferedImage(reducedImage));
this.cardCount = 0;
}
private BufferedImage createBlankCard() {
BufferedImage image = new BufferedImage(56, 78,
BufferedImage.TYPE_INT_RGB);
return image;
}
private BufferedImage readCard() {
try {
URL url = new URL("https://www.improvemagic.com/"
+ "wp-content/uploads/2020/11/kj.png");
return ImageIO.read(url);
} catch (IOException ee) {
ee.printStackTrace();
return null;
}
}
private BufferedImage toBufferedImage(Image img) {
if (img instanceof BufferedImage) {
return (BufferedImage) img;
}
BufferedImage bimage = new BufferedImage(
img.getWidth(null), img.getHeight(null),
BufferedImage.TYPE_INT_RGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
// Return the buffered image
return bimage;
}
public Card getBlankCard() {
return blankCard;
}
public Card getCard() {
return card;
}
public int getCardCount() {
return cardCount;
}
public void incrementCardCount(int increment) {
this.cardCount += increment;
}
}
public class Card {
private final BufferedImage cardImage;
public Card(BufferedImage cardImage) {
this.cardImage = cardImage;
}
public BufferedImage getCardImage() {
return cardImage;
}
}
}

Preferred height of JPanel is lower then combined height of its children in table renderer

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.

Are GridLayout cells really all the same size?

I decided to use a GridLayout LayoutManager for my Java Swing app because each cell within the grid is supposed to be exactly the same size.
From the Java Tutorials:
A GridLayout object places components in a grid of cells. Each component takes all the available space within its cell, and each cell is exactly the same size.
And even in the description of the GridLayout class:
The GridLayout class is a layout manager that lays out a container's components in a rectangular grid. The container is divided into equal-sized rectangles, and one component is placed in each rectangle.
However, my code seems to make a certain cell twice as large as the others. I added 3 JPanels to a Container with GridLayout, and gave each JPanel a different background color. This was the result:
Clearly, the first JPanel (red background) is twice as big as the others (green and yellow). The code that produced this is the following:
public void updateListFrameContentPane(Container mainPane) {
mainPane.setLayout(new GridLayout(1,0));
JPanel listPanel = new JPanel();
listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
listPanel.add(friendsLabel);
listPanel.add(listScrollPane);
listPanel.setBackground(Color.RED);
mainPane.add(listPanel);
for(JPanel chatPanel : chatPanels) {
chatPanel.setBackground((Math.random()>0.5 ? Color.YELLOW : Color.GREEN));
mainPane.add(chatPanel);
}
}
All I do is set the Container's layout to GridLayout with 1 row and any number of columns, and then add 3 JPanels to that. So why is the first JPanel so much larger? Strangely this only happens when two or more chatPanels are added. When there is only one, it formats correctly.
Kiheru is right. revalidate/repaint after changing the contents of a container. Here's a rough but working example:
public class GridLayoutExample {
private JFrame frame;
private Map<String,JPanel> chatBoxes = new HashMap<String,JPanel>();
private String lastKey = "0";
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
GridLayoutExample window = new GridLayoutExample();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public GridLayoutExample() {
initialize();
}
private void addChatBox() {
/*
* JPanel (border layout)
* - JPanel (Border South, Border layout)
* - - JTextField ( Border center )
* - - JButton ( Border east )
* - JLabel (Border North )
* - JTextArea (Border Center);
*/
int lk = Integer.valueOf(lastKey)+1;
lastKey = Integer.toString(lk);
JPanel np = new JPanel();
np.setLayout(new BorderLayout(0,0));
np.setBackground((lk%2 == 0) ? Color.GREEN : Color.YELLOW);
JPanel south = new JPanel();
south.setLayout(new BorderLayout(0,0));
np.add(south,BorderLayout.SOUTH);
JButton b = new JButton("New Button");
south.add(b,BorderLayout.EAST);
JTextField field = new JTextField();
south.add(field,BorderLayout.CENTER);
JLabel label = new JLabel(lastKey);
label.setHorizontalAlignment(SwingConstants.CENTER);
np.add(label,BorderLayout.NORTH);
JTextArea text = new JTextArea();
np.add(text,BorderLayout.CENTER);
chatBoxes.put(lastKey, np);
frame.getContentPane().add(np);
frame.revalidate(); // CRITICAL MISSING LINES
frame.repaint(); // CRITICAL MISSING LINES
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 923, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1, 0, 0, 0));
JPanel panel = new JPanel();
panel.setBackground(Color.RED);
frame.getContentPane().add(panel);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JLabel lblNewLabel = new JLabel("Online Users");
panel.add(lblNewLabel);
JList list = new JList();
list.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
addChatBox();
}
});
list.setModel(new AbstractListModel() {
String[] values = new String[] {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
public int getSize() {
return values.length;
}
public Object getElementAt(int index) {
return values[index];
}
});
panel.add(list);
}
}
I chose to revalidate/repaint the entire frame, but it may be possible to have it work while repainting a lesser container. Certainly without the critical lines marked above, it doesn't matter how often you click on the list elements, nothing new will show up. With those lines, every time you click, a new chatbox is added.
Huh... just noticed this. If the red area is considered two separate panels, then they're all the exactly correct size. Have you perhaps accidentally added an extra panel?

Drawing multiple images using paintComponent()

I would like to display more than one image on the screen in the same JPanel.
A for loop iterates over each element in the array and displays their corresponding image, but only seems to keep the last image.
The code:
import javax.swing.*;
import java.awt.*;
import java.io.*;
import javax.imageio.ImageIO;
public class GameGUI extends JFrame{
JPanel mainPanel = new JPanel();
JPanel buttonPanel = new JPanel();
int arrayLength;
public GameGUI() {
super("Gameplay");
//Set size of the frame.
setSize(650, 580);
//Location inside frame.
setLocation(10, 8);
SwingUtilities.isEventDispatchThread();
The methods that contain each individual panel:
createMainPanel();
createCentrePanel();
createNorthPanel();
createSouthPanel();
createWestPanel();
createEastPanel();
setVisible(true);
}
//creating panels
public void createMainPanel() {
//here is the main panel which the others will be nested in.
mainPanel.setLayout(new BorderLayout());
mainPanel.setBackground(Color.red);
add(mainPanel);
}
public boolean createCentrePanel() {
JPanel CENTRE = new JPanel(new BorderLayout());
CENTRE.setBackground(Color.WHITE);
CENTRE.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(CENTRE, BorderLayout.CENTER);
return true;
}
This is the panel which i am using to print multiple images to the panel.
As you can see I have a for loop which is going through each item in the array and passing the value to the draw component. However it only seems to keep the last image on the screen eventhough each item in the array is being passed to it.
I have tried using repaint but it doesn't seem to work:
public boolean createNorthPanel() {
int[] array = {1, 8, 9, 10};
arrayLength = array.length;
int size = 0;
JPanel NORTH = new JPanel(new BorderLayout());
NORTH.setPreferredSize(new Dimension(100, 100));
NORTH.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(NORTH, BorderLayout.NORTH);
for (int i = 0; i < arrayLength; i++) {
NORTH.add(new drawPanel(array[i], size, arrayLength));
size = size + 30;
//repaint();
}
return true;
}
public boolean createSouthPanel() {
JPanel SOUTH = new JPanel(new BorderLayout());
SOUTH.setPreferredSize(new Dimension(100, 100));
// SOUTH.add(new drawPanel(2, 0));
// SOUTH.add(new drawPanel(5, 30));
SOUTH.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(SOUTH, BorderLayout.SOUTH);
SOUTH.add(buttonPanel, BorderLayout.EAST);
return true;
}
public boolean createWestPanel() {
JPanel WEST = new JPanel(new BorderLayout());
WEST.setPreferredSize(new Dimension(150, 100));
//WEST.add(new drawPanel(8, 0));
WEST.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(WEST, BorderLayout.WEST);
return true;
}
public boolean createEastPanel() {
JPanel EAST = new JPanel(new BorderLayout());
EAST.setPreferredSize(new Dimension(150, 100));
EAST.setBorder(BorderFactory.createLineBorder(Color.black));
mainPanel.add(EAST, BorderLayout.EAST);
//EAST.add(new drawPanel(2, 0));
//EAST.add(new drawPanel(7, 60));
return true;
}
public static void main(String args[]){
new GameGUI();
}
}
Here is my class which draws the images on the screen
class drawPanel extends JPanel {
Image image = null;
int xPos;
public drawPanel(int x, int y, int length) {
xPos = y;
try {
File location = new File("src/Card_images/" + x + ".png");
image = ImageIO.read(location);
} catch (IOException e) {
System.out.println("Error: " + e);
}
}
/*public Dimension getPreferredSize() {
return new Dimension(71, 96);
}*/
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//draws image to screen at positions displayed
g.drawImage(image, xPos, 0, this);
}
}
You only have a single drawImage() statement that is executed in you paintCompent() method so you only ever see the last image drawn.
See Custom Painting Approaches for two different ways to draw multiple objects. You will obviously need to customize for your requirements, but the basic concepts will be the same.
Edit:
The above does not apply to your question, but is still good to know when you do need to do some custom painting.
Sorry, because of the custom painting I misread your question. You are making the code too complex.
The first problem is that you changed the layout manager of the "north" panel to a BorderLayout. You can only add a single component to any location of a BorderLayout. So that is why the last component added gets painted. Just use the default FlowLayout for the panel. Although your code still won't work because your components don't have a preferred size.
So the solution to your problem is:
a) create a panel using a FlowLayout
b) Use a JLabel to display your images. There is no need to do custom painting!. Add the labels to the panel, then add this panel to your frame.
Now the layout manager can do its job and you don't need to worry about the details.
Also, use standard Java naming conventions. Your code is too hard to read because you don't follow the standards.
NORTH is not a proper variable name. It should be "north". An upper cased name indicates a final static variable.
use proper class names. Classes should start with an upper case character. "drawPanel" should be "DrawPanel".

ScrollBar movement not smooth in JScrollpane in Swing

I have a situation like this.
I have scrollpane whose viewportView is a JPanel
And that JPanel has the layout as BoxLayout. In this panel I add one class which extends JPanel and that class contains JComponents.
So while running an application, the JComponents are shown in the JScrollPane.
This is how my ScrollPane is formed.
The problem here is, When the data exceeds more than around 750 rows, The scrollbar starts giving problems.
When scrolling up or down by mouse wheel, scroll doesnot move smoothly, It suddenly stops in the middle and again starts, say it has a jerky movement.
my Question is how can i get the smooth mouse movement in this scenario.
My scrollPane is like this
public JScrollPane getScrollPane() {
if (scrollPane == null) {
scrollPane = new JScrollPane();
scrollPane.setSize(new Dimension(1000, 433));
scrollPane.setLocation(new Point(10, 10));
scrollPane.setColumnHeaderView(getHeaderOfRowPanel());
scrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setViewportView(getScrollPanel());
scrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.getVerticalScrollBar().setUnitIncrement(
unitIncrement);
}
return scrollPane;
}
private JPanel getScrollPanel() {
if (scrollPanel == null) {
scrollPanel = new JPanel();
scrollPanel.setBorder(null);
scrollPanel.setLayout(new BoxLayout(getScrollPanel(),
BoxLayout.Y_AXIS));
}
return scrollPanel;
}
private class RowPanel extends JPanel {
//My components are here ..
//I add this Panel in scrollPanel
}
Have look at JScrollBar.setUnitIncrement, beacuse bunch of JPanels in the JScollPane has un_natural scrolling in compare with JList, JTable or JTextArea
example
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class JScrollBarUnitIncrement {
public static void main(String[] args) {
final JFrame f = new JFrame("");
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2000, 1));
for (int i = 0; i != 2000; i++) {
JButton btn = new JButton("Button 2");
panel.add(btn);
}
final JScrollPane sPane = new JScrollPane(panel);
final int increment = 50;
sPane.getVerticalScrollBar().setUnitIncrement(increment);
KeyStroke kUp = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
KeyStroke kDown = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
sPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(kUp, "actionWhenKeyUp");
sPane.getActionMap().put("actionWhenKeyUp", new AbstractAction("keyUpAction") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
final JScrollBar bar = sPane.getVerticalScrollBar();
int currentValue = bar.getValue();
bar.setValue(currentValue - increment);
}
});
sPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(kDown, "actionWhenKeyDown");
sPane.getActionMap().put("actionWhenKeyDown", new AbstractAction("keyDownAction") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
final JScrollBar bar = sPane.getVerticalScrollBar();
int currentValue = bar.getValue();
bar.setValue(currentValue + increment);
}
});
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(sPane);
f.pack();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.setVisible(true);
}
});
}
private JScrollBarUnitIncrement() {
}
}
It is never good to populate such huge no. of rows in JScrollPane. Because, the visible portion is only around let's say 20 to 30 rows in viewport depending on the height of the scrollpane and the height of your RowPanel. So, why to populate such huge rows at once ? The problem with the smoothness is because there might be exception (see the console ). So, resolve this, I see two options for you. One is to use pagination and another is to allow users to enter some search criteria to filter out the unwanted records.
As #mKorbel notes, both JTable and JList implement Scrollable for convenient scroll increments, and they both use the flyweight pattern for rendering speed. If you can't use either component directly, you can still use the patterns. The tutorial includes Scrollable examples, and there's a CellRendererPane example here.

Categories