I'm developing a GUI for a Java application and I want to have a background image. The problem is that I have a type of "drawer" filled with buttons, that has buttons highlighted in red when are selected.
Im using the method buttonName.setBackground(new Color(255, 102, 102, 200)); to set the highlited button and the transparency at the same time. The problem is that despite that the method works and transparents the button, the transparency shows random parts of the frame behind the button, beign these the header, another button, the scrollbar of the JScrollPane where the buttons are located, etc. The text of the button still shows up, and the button works, but the background shows the text from other buttons or parts of the frame.
Besides, I realized that if I click a button and pass the mouse over the selected button multiple times, the transparency starts to accumulate to the point that it gets to a solid color.
package buttonsbug;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
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.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
/**
*
* #author F&H
*/
public class ButtonsBug extends JFrame implements ActionListener {
private ArrayList<JButton> botones;
private JLabel panelPrin, panelNav, panelUser, panelImgUser, nombre, puesto;
private JButton logout, planDis, consuEmpleados, funConsultarPiezas, btnCalidad, compraMat, soySuper, histProy, crearProyecto, clientes, adminConsProye;
private JPanel buttonScroll;
private JScrollPane navScroll;
private BufferedImage img;
private Dimension screenSize;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
try {
new ButtonsBug().setVisible(true);
} catch (Exception e) {
System.out.println(e.getMessage());
System.exit(0);
}
}
});
}
public ButtonsBug() {
botones = new ArrayList<>();
screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(ajustarDimensiones(1400), ajustarDimensiones(800));
setContentPane(panelPrin = new JLabel());
panelPrin.setSize(ajustarDimensiones(1400), ajustarDimensiones(800));
try {
img = ImageIO.read(new File("src/tw3.png"));
Image dimg1 = img.getScaledInstance(panelPrin.getWidth(), panelPrin.getHeight(), Image.SCALE_SMOOTH);
ImageIcon imageIcon = new ImageIcon(dimg1);
panelPrin.setIcon(imageIcon);
} catch (IOException z) {
System.out.println(z.getMessage());
JOptionPane.showMessageDialog(this, "¡Error en la lectura de imagen!", "Error", JOptionPane.ERROR_MESSAGE);
}
panelPrin.setBackground(java.awt.Color.white);
panelPrin.add(panelNav = new JLabel());
// panelPrin.setOpaque(true);
// panelNav.setOpaque(true);
panelNav.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
// panelNav.setBackground(new Color(0, 0, 0, 150));
panelNav.setBounds(0, 0, ajustarDimensiones(305), ajustarDimensiones(771));
panelNav.add(panelUser = new JLabel());
panelNav.add(logout = new JButton());
logout.setContentAreaFilled(false);
// logout.setOpaque(true);
// panelUser.setOpaque(true);
panelUser.setBounds(ajustarDimensiones(1), ajustarDimensiones(1), ajustarDimensiones(303), ajustarDimensiones(88));
panelUser.add(panelImgUser = new JLabel());
panelUser.add(nombre = new JLabel());
panelUser.add(puesto = new JLabel());
nombre.setText("Wil Fonseca");
puesto.setText("Production manager");
nombre.setBounds(ajustarDimensiones(55), ajustarDimensiones(25), ajustarDimensiones(245), ajustarDimensiones(20));
puesto.setBounds(ajustarDimensiones(55), ajustarDimensiones(45), ajustarDimensiones(245), ajustarDimensiones(20));
nombre.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(14)));
puesto.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(12)));
nombre.setForeground(Color.white);
puesto.setForeground(Color.white);
logout.setText("Logout");
logout.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(34)));
logout.setBounds(ajustarDimensiones(1), ajustarDimensiones(691), ajustarDimensiones(303), ajustarDimensiones(88));
logout.setBackground(Color.white);
logout.setForeground(Color.red);
logout.addActionListener(this);
logout.setBorder(null);
logout.setBorderPainted(false);
logout.setFocusPainted(false);
panelImgUser.setBounds(ajustarDimensiones(3), ajustarDimensiones(24), ajustarDimensiones(40), ajustarDimensiones(40));
try {
img = ImageIO.read(new File("src/Usuario.png"));
Image dimg1 = img.getScaledInstance(panelImgUser.getWidth(), panelImgUser.getHeight(), Image.SCALE_SMOOTH);
ImageIcon imageIcon = new ImageIcon(dimg1);
panelImgUser.setIcon(imageIcon);
} catch (IOException z) {
System.out.println(z.getMessage());
}
setTitle("ButtonsBug");
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
buttonGenerator();
}
public int ajustarDimensiones(int coo) {
int newC = 0;
double res = (screenSize.getHeight());
float newRes;
if (res < 1080) {
if (coo == 1400) {
return 1208;
} else if (coo == 800) {
return 680;
}
}
if (coo == 0) {
return newC;
} else {
if (res < 1080) {
newRes = (918f / 1080f);
if (coo == 305) {
newC = (int) (newRes * coo) - 1;
} else if (coo == 90) {
newC = (int) (newRes * coo) - 1;
} else if (coo == 224) {
newC = (int) (newRes * coo) - 1;
} else if (coo == 601) {
newC = (int) (newRes * coo) + 3;
} else if (coo == 1066) {
newC = (int) (newRes * coo) - 1;
} else if (coo == 1474 || coo == 1576) {
newC = (int) (newRes * coo) + 1;
} else if (coo == 1059) {
newC = (int) (newRes * coo) - 10;
} else if (coo == 1095) {
newC = (int) (newRes * coo) + 14;
} else {
newC = (int) (newRes * coo);
}
} else {
newRes = (float) (res / 1080f);
newC = (int) (newRes * coo);
}
if (newC < 0) {
newC = 1;
}
}
return newC;
}
public void buttonGenerator() {
int y = 0;
panelNav.add(navScroll = new JScrollPane(buttonScroll = new JPanel(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
navScroll.setBorder(BorderFactory.createEmptyBorder());
navScroll.setBounds(ajustarDimensiones(1), ajustarDimensiones(90), ajustarDimensiones(303), ajustarDimensiones(600));
// navScroll.setBackground(Color.white);
navScroll.setOpaque(false);
navScroll.getVerticalScrollBar().setUnitIncrement(30);
navScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(601)));
// buttonScroll.setBackground(Color.white);
buttonScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(601)));
buttonScroll.setLayout(null);
navScroll.setViewportView(buttonScroll);
buttonScroll.setOpaque(false);
navScroll.getViewport().setOpaque(false);
buttonScroll.add(funConsultarPiezas = new JButton());
funConsultarPiezas.setContentAreaFilled(false);
// funConsultarPiezas.setOpaque(true);
funConsultarPiezas.setText("Consultar pieza");
funConsultarPiezas.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
funConsultarPiezas.setBounds(ajustarDimensiones(1), ajustarDimensiones(0), ajustarDimensiones(301), ajustarDimensiones(80));
// funConsultarPiezas.setBackground(java.awt.Color.white);
funConsultarPiezas.setForeground(Color.white);
funConsultarPiezas.addActionListener(this);
funConsultarPiezas.setBorder(null);
funConsultarPiezas.setBorderPainted(false);
funConsultarPiezas.setFocusPainted(false);
botones.add(funConsultarPiezas);
y += 81;
buttonScroll.add(btnCalidad = new JButton());
btnCalidad.setContentAreaFilled(false);
// btnCalidad.setOpaque(true);
btnCalidad.setText("Quality Check");
btnCalidad.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
btnCalidad.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
// btnCalidad.setBackground(Color.white);
btnCalidad.setForeground(Color.white);
btnCalidad.addActionListener(this);
btnCalidad.setBorder(null);
btnCalidad.setBorderPainted(false);
btnCalidad.setFocusPainted(false);
botones.add(btnCalidad);
y += 81;
buttonScroll.add(planDis = new JButton());
planDis.setContentAreaFilled(false);
// planDis.setOpaque(true);
planDis.setText("Diseño y planear");
planDis.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
planDis.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
// planDis.setBackground(Color.white);
planDis.setForeground(Color.white);
planDis.addActionListener(this);
planDis.setBorder(null);
planDis.setBorderPainted(false);
planDis.setFocusPainted(false);
botones.add(planDis);
y += 81;
buttonScroll.add(compraMat = new JButton());
compraMat.setContentAreaFilled(false);
// compraMat.setOpaque(true);
compraMat.setText("Compra Material");
compraMat.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
compraMat.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//compraMat.setBackground(Color.white);
compraMat.setForeground(Color.white);
compraMat.addActionListener(this);
compraMat.setBorder(null);
compraMat.setBorderPainted(false);
compraMat.setFocusPainted(false);
botones.add(compraMat);
y += 81;
buttonScroll.add(soySuper = new JButton());
soySuper.setContentAreaFilled(false);
// soySuper.setOpaque(true);
soySuper.setText("Liberar piezas");
soySuper.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
soySuper.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//soySuper.setBackground(Color.white);
soySuper.setForeground(Color.white);
soySuper.addActionListener(this);
soySuper.setBorder(null);
soySuper.setBorderPainted(false);
soySuper.setFocusPainted(false);
botones.add(soySuper);
y += 81;
buttonScroll.add(crearProyecto = new JButton());
crearProyecto.setContentAreaFilled(false);
// crearProyecto.setOpaque(true);
crearProyecto.setText("Crear proyecto");
crearProyecto.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
crearProyecto.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//crearProyecto.setBackground(Color.white);
crearProyecto.setForeground(Color.white);
crearProyecto.addActionListener(this);
crearProyecto.setBorder(null);
crearProyecto.setBorderPainted(false);
crearProyecto.setFocusPainted(false);
botones.add(crearProyecto);
y += 81;
buttonScroll.add(clientes = new JButton());
clientes.setContentAreaFilled(false);
// clientes.setOpaque(true);
clientes.setText("Clientes");
clientes.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
clientes.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//clientes.setBackground(Color.white);
clientes.setForeground(Color.white);
clientes.addActionListener(this);
clientes.setBorder(null);
clientes.setBorderPainted(false);
clientes.setFocusPainted(false);
botones.add(clientes);
y += 81;
buttonScroll.add(adminConsProye = new JButton());
adminConsProye.setContentAreaFilled(false);
// adminConsProye.setOpaque(true);
adminConsProye.setText("Consultar proyectos");
adminConsProye.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
adminConsProye.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(26)));
//adminConsProye.setBackground(Color.white);
adminConsProye.setForeground(Color.white);
adminConsProye.addActionListener(this);
adminConsProye.setBorder(null);
adminConsProye.setBorderPainted(false);
adminConsProye.setFocusPainted(false);
botones.add(adminConsProye);
y += 81;
buttonScroll.add(histProy = new JButton());
histProy.setText("Historial");
histProy.setContentAreaFilled(false);
// histProy.setOpaque(true);
histProy.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
histProy.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//histProy.setBackground(Color.white);
histProy.setForeground(Color.white);
histProy.addActionListener(this);
histProy.setBorder(null);
histProy.setBorderPainted(false);
histProy.setFocusPainted(false);
botones.add(histProy);
y += 81;
buttonScroll.add(consuEmpleados = new JButton());
consuEmpleados.setText("Trabajadores");
consuEmpleados.setContentAreaFilled(false);
// consuEmpleados.setOpaque(true);
consuEmpleados.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
consuEmpleados.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
//consuEmpleados.setBackground(Color.white);
consuEmpleados.setForeground(Color.white);
consuEmpleados.addActionListener(this);
consuEmpleados.setBorder(null);
consuEmpleados.setBorderPainted(false);
consuEmpleados.setFocusPainted(false);
botones.add(consuEmpleados);
y += 81;
buttonScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(y)));
}
public void botonSeleccionado(JButton but) {
for (JButton b : botones) {
if (b.getText().equalsIgnoreCase(but.getText())) {
b.setOpaque(true);
b.setBackground(new Color(255, 102, 102, 200));
b.setForeground(Color.white);
} else {
b.setOpaque(false);
//b.setBackground(Color.white);
b.setForeground(Color.white);
}
b.revalidate();
b.repaint();
}
}
#Override
public void actionPerformed(ActionEvent ae) {
JButton obj = (JButton) ae.getSource();
if (obj != logout) {
botonSeleccionado(obj);
}
}
}
If someone knows how to make a clean transparent button, I would really appreciate the help.
I leave here the source code with the test images that I'm using.
https://drive.google.com/file/d/1l8R52WTDyP93L0UhTNd3oorD7Qhv-TcP/view?usp=sharing
In this image, you can see the 3 kind of bugs that I have, in the first one you can see how there's another button in the background and the scroll bar is shown in the left of the button. In the second one, it's the header of the navigation panel. In the third one, I passed the mouse multiple times over the selected button and it turned into a solid color instead of a transparent one.
EDIT:
I decided to check if the bug persisted if I applied it to the navigation panel, because it will have to be transparent as well when the application is finished. So I added the following lines of code in the lines 82 and 83 of the code above:
panelNav.setBackground(new Color(0, 0, 0, 200));
panelNav.setOpaque(true);
In this other image I applied the transparency to the whole navigation panel, which is a JLabel. In the first image is shown what appears when the frame is displayed, there's even parts of the frame that are displayed beneath the navigation panel. In the second one is shown what happens when I use the scroll bar once.
EDIT 2:
I replaced all the JLabel that were used as JPanel for actual JPanel. Sadly, the bug persisted. I add an extra button in the main JPanel, I did this because I thought the bug origin from adding buttons to a JScrollPane. But it seems that the problem is directly in how I implement the method buttonName.setBackground().
Here's the new version of the code:
https://drive.google.com/file/d/1PuHMkEYNbBoafqs5XiyUaeCkIyXfnHFJ/view?usp=sharing
Any issues like the one you are seeing in the example app you have posted in 99% of cases happen due to incorrect mixing of opaque and/or non-opaque components.
As I can see from the code - you are using setOpaque(...) to change opacity of various components, but it's quite chaotic. Shortly what opaque property does - it affects the way Swing repaints the specific UI element (panel/label/button/etc) whenever a visual update for that element is required.
For example when you hover a button with the mouse - it might need to repaint if it has a different hover state, whether it's just an icon or a slightly/completely different style. This is where opacity comes into play - opaque=true components will never pass repaint calls "under" themselves (in other/proper terms - to their parent components). That means that if you have an opaque button on a panel and it have to be repainted when it changes to "hover" state - that button will be the only component to get repainted as there is no reason to repaint anything below it because it is opaque, you literally should not be able to see through it, so the graphics are expected to fill all pixels within bounds of that button with opaque colors.
Theoretically. On practice if you set a button to an opaque state, but keep it's graphics content transparent or semi-transparent (which is obviously a mistake, but Swing will never tell you about it) - you will end up seeing various visual artifacts like the ones you see in your application. That happens due to Graphics2D implementation often performing different paint operations at (0,0) coordinate to optimize their speed - this is not really important to know, but that is partially why you might see other component "parts" mixed in your component bounds when it is transparent. It's a bit more complicated than that, but it shouldn't ever really matter as it is simply an internal Swing optimization.
Similar visual problems might also be caused by mixing opaque=true and opaque=false components on the same layout. Most probably that is the case for your issue as well. I did quickly try to set everything in your demo to opaque=false and it did fix the issue, but it's not really the correct way to fix it, especially if you want to keep some components opaque. That just means the problem lies somewhere in the way of mixing components with different opacity types on top of each other within a single container.
My personal recommendation - never mix opaque and non-opaque components in one layout if there is even a slight chance they will overlap (meaning their bounds will intersect within the layout). Or even better - never overlap components on top of each other within a single container. Use multiple nested containers with appropriate non-null layouts, this will also help you a lot in the future to easily modify your UI.
I can give you a simple example that can demonstrate why it is bad:
/**
* #author Mikle Garin
*/
public class OpacityGlitch
{
public static void main ( String[] args )
{
SwingUtilities.invokeLater ( () -> {
final JFrame frame = new JFrame ( "Opacity glitch sample" );
// Opaque by default
final JPanel panel = new JPanel ( null );
// Opaque by default, but might vary with L&F
final JButton button1 = new JButton ( "1111111" );
panel.add ( button1 );
// Non-opaque to demonstrate the problem
final JButton button2 = new JButton ( "2222222" );
panel.add ( button2 );
// Intersecting buttons
button1.setBounds ( 100, 100, 150, 30 );
button2.setBounds ( 130, 115, 150, 30 );
frame.getContentPane ().add ( panel );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setSize ( 500, 500 );
frame.setLocationRelativeTo ( null );
frame.setVisible ( true );
} );
}
}
Theoretically what you should get is button1 always being on top (due to it being added earlier and being painted last on the container), but on practice - which button is fully visible and on top of the other one will change if you try hovering either of the buttons. This happens due to both buttons being opaque and repaint calls not going past the button components to their container and whatever intersects with them further on. To fix this particular case it is enough to make button2 non-opaque because it should always stay below the button1 and if it is non-opaque it will safely pass the repaint calls at least to it's container:
button2.setOpaque ( false );
Although I personally would recommend making all involved components opaque in such cases to avoid other possible issues if component order may be changed in the future or due to any user interactions - for instance the scroll bar in your application is the prime example. Container does not have to be non-opaque though as it will correctly pass repaint calls between components placed in it and will correctly repaint itself as well.
Once you change buttons from my example above to non-opaque the issue will disappear due to repaint being correctly handled for them.
This might be a complicated topic for a beginner in Swing, but I highly recommend to fully understand why and how things like that happen, otherwise it will become a huge problem for you in the future, once your application grows bigger.
I have seen this thread which asked the exact same question I have now, but find the answers a bit unsatisfactory:
Android's LinearLayout for Swing
I created a class WeightedPanel like so:
public class WeightedPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 6844740568601141924L;
private boolean mVertical;
private double mLastWeight = 1;
private GridBagConstraints mConstraints;
private int mLastGrid = 0;
public WeightedPanel(boolean vertical) {
mVertical = vertical;
mConstraints = new GridBagConstraints();
setLayout(new GridBagLayout());
}
#Override
public Component add(Component comp) {
return add(comp, mLastWeight);
}
public Component add(Component comp, double weight) {
if (mVertical) {
mConstraints.weighty = weight;
mConstraints.weightx = 1;
mConstraints.gridy = mLastGrid;
mConstraints.gridx = 0;
} else {
mConstraints.weightx = weight;
mConstraints.weighty = 1;
mConstraints.gridx = mLastGrid;
mConstraints.gridy = 0;
}
mConstraints.fill = GridBagConstraints.BOTH;
add(comp, mConstraints);
mLastWeight = weight;
mLastGrid += weight;
return comp;
}
public Component add(Component comp, int weight) {
return add(comp, (double) weight);
}
}
This kind of works, but I have two problems with it:
1) In my application, I have a login screen:
#Override
protected void addComponents(WeightedPanel jPanel) {
mUpdateListener = new UpdateListener() {
#Override
public void onUpdate() {
LoginFrame.this.onUpdate();
}
};
WeightedPanel panel = getUserPanel();
jPanel.add(panel);
panel = getPasswordPanel();
jPanel.add(panel);
mLoginButton = getLoginButton();
jPanel.add(mLoginButton);
}
private WeightedPanel getPasswordPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("Password");
result.add(label);
mPasswordField = new PasswordField(mUpdateListener);
result.add(mPasswordField);
return result;
}
private WeightedPanel getUserPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("User");
result.add(label);
mUserTextField = new TextField(mUpdateListener);
result.add(mUserTextField);
return result;
}
which in practice looks like this:
Click to view
Why aren't the labels and text fields all the same size here? I figure it's got something to do with the fact that "Password" is a longer string than "User", but that's obviously not what I want!
2) My second problem is this. I have another screen like so:
#Override
protected void addComponents(WeightedPanel jPanel) {
WeightedPanel scrollPanePanel = getOrdersScrollPane();
jPanel.add(scrollPanePanel);
WeightedPanel buttonPanel = getButtonPanel();
jPanel.add(buttonPanel);
}
private WeightedPanel getOrdersScrollPane() {
WeightedPanel result = new WeightedPanel(true);
JPanel filterPanel = getFilterPanel();
result.add(filterPanel, 1);
mTableModel = new OrdersTableModel();
mTable = new JTable(mTableModel);
mTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent arg0) {
checkEnabled();
}
});
JScrollPane scrollPane = new JScrollPane(mTable);
result.add(scrollPane, 40);
return result;
}
It really doesn't look bad in practice:
Click to view
But have a look at the getOrdersScrollPane() function. The call to functions result.add(filterPanel, 1); and result.add(scrollPane, 50); say that the proportion between the filter panel and the scroll pane should be 1:50, but looking at the scroll pane, it's definitely not 50 times the size of the filter panel. Obviously, I am exaggerating to make my point, I don't really want a proportion of 1:50; it just strikes me that it makes no difference whether I do result.add(scrollPane, 10); or result.add(scrollPane, 50);
Both questions stem from an incorrect understanding of GridBagLayout. A bit more reading and experimenting should help) To answer the question at hand:
1) The problem here is that you want a single GridBagLayout, but instead are adding 2 independent panels.
The result: The columns in the top grid bag are independent of the columns in the bottom grid bag.
To rectify this, there are 2 things you can try:
Add both labels and both text fields to a single GridBag panel. That way the columns will align.
Make a minimum and preferred size for the labels so that their width matches and set their weightx to 0 (and weightx of text fields non-zero). That way you are making the GridBags allocate the same amount of space for the labels and text fields.
The first method is preferred, but not always possible. The second method is hacky and will likely break as soon as you change the label string, a user set a different default font etc, etc.
2) Here you are misunderstanding what weighty does.
It does not make your components of the specified proportion. That should be clear enough since you can mix 0 and non-0 weight components in a single layout.
What it does, is it allocates the preferred (or minimum) sizes for components, and distributes the remaining space in that proportion. Which means if you make your panel 100 pixels higher by resizing the window, 2 will go to the top panel adding spacing, and 98 will go to the table.
What you likely wanted is to make the weighty of the top filter 0 (so that there is no awkward spacings in large windows) and control its actual height with setPreferred and setMinimum size (or by setting those on the embedded components).
EDIT
As docs for Linear Layout state, to achieve a fixed proportion of sizes of components (the initial problem), one has to set their preferred sizes to 0, and then set weights (then all space is remaining space, and is distributed according to weights only). This also works for the GridBag variant.
I am adding a JPanel in a JScrollPane in my project.
All is working fine, but there is one problem about mouse scroll using the mouse-Wheel in JPanel. It's speed is very slow on scrolling. How to make it faster?
My code is :
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
objCheckBoxList = new CheckBoxList();
BaseTreeExplorer node = (BaseTreeExplorer)projectMain.objCommon.tree.getLastSelectedPathComponent();
if (node.getObject() != null) {
cmbList.setSelectedItem(node.getParent().toString());
} else {
if (node.toString().equalsIgnoreCase("List of attributes")) {
cmbList.setSelectedIndex(0);
} else {
cmbList.setSelectedItem(node.toString());
}
}
panel.add(objCheckBoxList);
JScrollPane myScrollPanel = new JScrollPane(panel);
myScrollPanel.setPreferredSize(new Dimension(200, 200));
myScrollPanel.setBorder(BorderFactory.createTitledBorder("Attribute List"));
You can set your scrolling speed with this line of code myJScrollPane.getVerticalScrollBar().setUnitIncrement(16);
Here is details.
This bug seems to occur because swing interprets the scroll speed in pixels instead of lines of text. If you are looking for a more accessible alternative to the accepted solution, you can use the following function to calculate and set the actual desired scroll speed in pixels:
public static void fixScrolling(JScrollPane scrollpane) {
JLabel systemLabel = new JLabel();
FontMetrics metrics = systemLabel.getFontMetrics(systemLabel.getFont());
int lineHeight = metrics.getHeight();
int charWidth = metrics.getMaxAdvance();
JScrollBar systemVBar = new JScrollBar(JScrollBar.VERTICAL);
JScrollBar systemHBar = new JScrollBar(JScrollBar.HORIZONTAL);
int verticalIncrement = systemVBar.getUnitIncrement();
int horizontalIncrement = systemHBar.getUnitIncrement();
scrollpane.getVerticalScrollBar().setUnitIncrement(lineHeight * verticalIncrement);
scrollpane.getHorizontalScrollBar().setUnitIncrement(charWidth * horizontalIncrement);
}
Note that swing does calculate the scroll speed correctly when it contains a single component like a JTable or JTextArea. This fix is specifically for when your scroll pane contains a JPanel.
Hy.. I have a JPanel, and in this contentPanel I added some other custom panels and give them locations etc. So now I added a JScrollPane to the contentPanel and always when I scroll down it clears my contentPanel, but the panels are still there but not visible...
How can I make them visible again?
That's my code to add the Panel into the contentPanel. The x,y,j are some settingsstuff for the location because I have an fixed window.
private void reloadContentPanel() {
int x = -200, y = 0, j = 1, row = 4;
EventPanel panel = null;
int i;
for(i=0; i < this.images.size();i++)
{
panel = new EventPanel(this.images.get(i).getAbsolutePath(),
this.images.get(i).getName());
panel.setLocation(x+(j*200), y);
j++;
if(i == row) {
x = -200;
y += 205;
j = 1;
row += 5;
}
this.contentPanel.add(panel);
}
this.repaint();
}
Thanks
it sounds like you are not using a LayoutManager correctly.
after creating your JFrame (i'm guessing within your constructor) add the following (for example):
this.setLayout(new FlowLayout());
this will certainly not be the best layout manager for what you are trying to do but will stop the add calls from overriding the displayed component.
you will need to read further about LayoutManagers
besides this, it's not really advisable to extend JFrame. It's better practice to treat JFrame as a member of your class just like all the other components.
I have the answer! :)
I use a GridLayout not a FlowLayout, so it's fine and it automatically refreshes the panels =)
Problem: I have a method that creates a list from the parsed ArrayList. I manage to show the list in the GUI, without scrollbar. However, I am having problem setting it to show only the size of ArrayList. Meaning, say if the size is 6, there should only be 6 rows in the shown List. Below is the code that I am using. I tried setting the visibleRowCount as below but it does not work. I tried printing out the result and it shows that the change is made.
private void createSuggestionList(ArrayList<String> str) {
int visibleRowCount = str.size();
System.out.println("visibleRowCount " + visibleRowCount);
listForSuggestion = new JList(str.toArray());
listForSuggestion.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
listForSuggestion.setSelectedIndex(0);
listForSuggestion.setVisibleRowCount(visibleRowCount);
System.out.println(listForSuggestion.getVisibleRowCount());
listScrollPane = new JScrollPane(listForSuggestion);
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseEvent) {
JList theList = (JList) mouseEvent.getSource();
if (mouseEvent.getClickCount() == 2) {
int index = theList.locationToIndex(mouseEvent.getPoint());
if (index >= 0) {
Object o = theList.getModel().getElementAt(index);
System.out.println("Double-clicked on: " + o.toString());
}
}
}
};
listForSuggestion.addMouseListener(mouseListener);
textPane.add(listScrollPane);
repaint();
}
To summarize: I want the JList to show as many rows as the size of the parsed ArrayList, without a scrollbar.
Here is the picture of the problem:
Here's the link to the other 2 as the picture resolution is quite big I'm scared it will distort the view:
JList 1 & JList 2
The JList 1 and 2 pictures shows it clearly. The JList displays empty rows, which I do not want it to happen.
Any ideas? Please help. Thanks. Please let me know if a picture of the problem is needed in case I did not phrase my question correctly.
--
Edit:
JScrollPane scrollPane = new JScrollPane(textPane);
scrollPane.setPreferredSize(new Dimension(200, 200));
//Create the text area for the status log and configure it.
changeLog = new JTextArea(5, 30);
changeLog.setEditable(false);
JScrollPane scrollPaneForLog = new JScrollPane(changeLog);
//Create a split pane for the change log and the text area.
JSplitPane splitPane = new JSplitPane(
JSplitPane.VERTICAL_SPLIT,
scrollPane, scrollPaneForLog);
splitPane.setOneTouchExpandable(true);
//Create the status area.
JPanel statusPane = new JPanel(new GridLayout(1, 1));
CaretListenerLabel caretListenerLabel =
new CaretListenerLabel("Caret Status");
statusPane.add(caretListenerLabel);
//Add the components.
getContentPane().add(splitPane, BorderLayout.CENTER);
getContentPane().add(statusPane, BorderLayout.PAGE_END);
How the textPane is included into the container, if that helps
Another edit:
public void showSuggestionList(JScrollPane pane, Rectangle caretCoords) {
pane.setVisible(false);
pane.setBounds(caretCoords.x - 5, caretCoords.y + 25, 400, 250);
pane.setVisible(true);
repaint();
}
showSuggestionList() is being called my CaretListener, to show the JScrollPane when the caret moves.
I suspect it's the layout-management of textPane that is the issue. From what I can see, the listForSuggestions should not occupy more space than it needs to display those items, if it's preferred size is respected.
So the JTextPane is a Container, that is, you can add subcomponents to it. But how are those subcomponents layed out? That is up to the layout manager currently in use. If the layout manager respects the preferred dimension of the listForSuggestios I think you should be ok. Not sure though.
From what I can see, you get the "null-layout" by just instantiating a JTextPane, which means that unless you set another layout manager explicitly, you would need to take care of placement / resizing of the subcomponents yourself.
You could try to do something like
Dimension dim = listForSuggestions.getPreferredSize();
listForSuggestions.setBounds(xPos, yPos, dim.getWidth(), dim.getHeight());
Here is a complete example
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
public class FrameTest {
public static void main(String[] args) {
JFrame f = new JFrame("Frame Test");
ArrayList<String> str = new ArrayList<String>();
for (int i = 0; i < 20; i++)
str.add("number " + i);
JTextPane tp = new JTextPane();
int visibleRowCount = str.size();
System.out.println("visibleRowCount " + visibleRowCount);
JList listForSuggestion = new JList(str.toArray());
listForSuggestion.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
listForSuggestion.setSelectedIndex(0);
listForSuggestion.setVisibleRowCount(5);
System.out.println(listForSuggestion.getVisibleRowCount());
JScrollPane listScrollPane = new JScrollPane(listForSuggestion);
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseEvent) {
JList theList = (JList) mouseEvent.getSource();
if (mouseEvent.getClickCount() == 2) {
int index = theList.locationToIndex(mouseEvent.getPoint());
if (index >= 0) {
Object o = theList.getModel().getElementAt(index);
System.out.println("Double-clicked on: " + o.toString());
}
}
}
};
listForSuggestion.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK));
listForSuggestion.addMouseListener(mouseListener);
Dimension dim = listForSuggestion.getPreferredSize();
listForSuggestion.setBounds(20, 20, (int) dim.getWidth(), (int) dim.getHeight());
tp.add(listForSuggestion);
f.add(tp);
f.setSize(400, 400);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I think the most elegant way of doing this is to roll your own layout-manager. (It's actually quite simple.) And then, instead of doing textPane.add(list), you do textPane.add(list, YourLayoutManager.POPUP_LIST). The layout-manager then remembers the fact that list was supposed to be layed out according to it's preferred size, and layes it out accordingly in its layoutContainer-method. (If you give the YourLayoutManager a reference to the JTextPane that it is attached to, you could probably even make it layout the list right beside the current caret location.)
If you are dynamically (via code) filling your list, it is bad idea not to use scrollbar. It might work as you want f.e. for 20 list items, but imagine what happens once you need use more data - like 2000. Your GUI will be ruined.