JPanel dropdown menu cut off - java

I'm practicing Java by creating a text analyzing program (word count, TF-IDF, etc).
I'm now constructing a small GUI to be displayed at the top of the frame. I want a dropdown menu, and I'm trying to create one using a JPanel and several JButton components. But to make the dropdown reusable, I've been trying to construct a class to manage it.
However, I encountered a problem: when I try to create the dropdown menu it's only visible in the upper left corner of the frame, and if I try to move it, it gets cut off or disappears completely.
I tried creating a simple method to do the same thing, but the problem remains the same. Here's the relevant code (ignore the processor and State stuff):
public class ExperimentState extends State {
private JFrame frame;
public ExperimentState(Processor processor) {
super(processor);
initialize();
}
private void initialize() {
int width, height;
//these variables are temporary, for testing
width = 800;
height = 640;
//Set up the frame
frame = new JFrame();
frame.setSize(width, height);
frame.getContentPane().setLayout(null);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Test code
String[] buttonNames = { "42", "Don't panic!", "Towel"};
// x, y, width, height, button names
JPanel panel = createPanel(0, 0, 100, 25, buttonNames);
frame.getContentPane().add(panel);
}
public JPanel createPanel(int x, int y, int width, int height, String[] names) {
//Create and setup the panel
JPanel panel = new JPanel();
panel.setLayout(null);
panel.setBounds(x, y, width, height);
JButton[] buttons = new JButton[names.length];
for(int i = 0; i < names.length; i++) {
JButton button = new JButton(names[i]);
button.setBounds(x, y + height*i, width, height); //each button has the size of the panel, and are placed on top of each other
panel.add(button);
buttons[i] = button;
}
buttons[0].addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
panel.setBounds(x, y, width, height*names.length); //the panel extends to reveal all the buttons
}
public void mouseExited(MouseEvent e) {
panel.setBounds(x, y, width, height); //the panel retracts
}
});
return panel;
}
#Override
public void display() {
frame.setVisible(true);
}
#Override
public void hide() {
frame.setVisible(false);
}
}
I thought there would be a problem with the mouse listener, since I don't really know how they work and thought they wouldn't be able to access the x, y, width and height variables anytime they were called, but there was no problem.
The problem appears when I call createPanel() with something like createPanel(50, 0, 100, 25, buttonNames).
Here, x is 50 instead of 0. When displayed, the panel is moved to the right, as it should be, but its buttons are now cut in half by some invisible line. If x = 100, it will go completely invisible.
Why is this happening? What am I missing?

Components inside a Container will only completely show (without a LayoutManager) if the bounds of these Components are completely inside it. By changing the x-coordinate from 0 to 50 you move the location of the Button. If you do not change its size or the size of your container your Button will be cut off.
The easiest way to fix this problem is to replace x and y in the location of your button with 0 and replace the 0‘s in the location of the Panel with x and y.
EDIT: Similar Problem here: Components not showing in second JPanel

Related

Making Multi-Coloured Buttons in Java

I want to create a grid of squares in my Java Swing GUI. I need to toggle their state so I'm thinking a JToggleButton is appropriate for each of the squares.
The problem that I have is that I want to partially colour each toggle button according to given percentages. e.g. if 50% and 50% I want the left half of the button to be green and the right to be red. If 25%,25%,50% I'd need 3 colours. I also need to use the button Text field so hiding that isn't allowed in the solution.
Is it possible to do something like this with a JToggleButton? Is there a better element to use? Or how might I go about it?
I apologise for not posting my work so far but I can't find anything close to an example of this type of thing.
I want to end up with something like this where each square is a button.
You can construct a button with changeable 2-color background as required by overriding
paintComponent:
import java.awt.*;
import javax.swing.*;
public class TwoColorsButton extends JButton{
private final Color leftColor, rightColor;
private int percentOfLeftColor;
public TwoColorsButton(String text) {
this(text,Color.RED, Color.GREEN, 50);
}
public TwoColorsButton(String text, Color leftColor,Color rightColor, int percentOfLeftColor) {
super(text);
this.leftColor = leftColor;
this.rightColor = rightColor;
this.percentOfLeftColor = percentOfLeftColor;
//make button transparent
setOpaque(false);
setContentAreaFilled(false);
setBorderPainted(false);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
int leftWidth = getWidth() * percentOfLeftColor/100;
g2.setColor(leftColor);
g2.fillRect(0, 0, leftWidth , getHeight());
g2.setColor(rightColor);
g2.fillRect(leftWidth, 0, getWidth() -leftWidth, getHeight());
g2.setPaint(Color.BLACK);
super.paintComponent(g2); //button is transparent so super paints text only
g2.dispose();
}
public void setPercentOfLeftColor(int percentOfLeftColor) {
this.percentOfLeftColor = percentOfLeftColor;
repaint();
}
public int getPercentOfLeftColor() {
return percentOfLeftColor;
}
public static void main(String[] args) {
//run button test
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
JPanel topPanel = new JPanel();
TwoColorsButton twoColorBtn = new TwoColorsButton("Some Text");
topPanel.add(twoColorBtn);
frame.add(topPanel, BorderLayout.PAGE_START);
JPanel bottomPanel = new JPanel();
JButton runTestBtn = new JButton("Run test");
runTestBtn.addActionListener(e->{
runTestBtn.setEnabled(false);
new Timer(1000, e1 ->{
int percent = twoColorBtn.getPercentOfLeftColor() +25;
percent = percent > 100 ? 0 : percent;
twoColorBtn.setPercentOfLeftColor(percent);
}).start();
});
bottomPanel.add(runTestBtn);
frame.add(bottomPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setVisible(true);
}
}
The code can easily be modified to allow 3 colors, if needed.
(Test it online here)
(See a basic 3 colors toggle button here)

Problems rendering a round button with java.swing. Why isn't this code working?

I'm developing a Java application that demands a customized button. I'm using Swing for the GUI and found myself limited to some tricky solutions.
Here's one I found (from this website). It's supposed to use a custom image for the button and make it round.
public class RoundButton extends JButton
{
private static final long serialVersionUID = 1L;
protected Shape shape, base;
public RoundButton()
{
this(null, null);
}
public RoundButton(Icon icon)
{
this(null, icon);
}
public RoundButton(String text)
{
this(text, null);
}
public RoundButton(Action a)
{
this();
setAction(a);
}
public RoundButton(String text, Icon icon)
{
setModel(new DefaultButtonModel());
init(text, icon);
if(icon==null) return;
setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
setBackground(Color.BLACK);
setContentAreaFilled(false);
setFocusPainted(false);
//setVerticalAlignment(SwingConstants.TOP);
setAlignmentY(Component.TOP_ALIGNMENT);
init_shape();
}
protected void init_shape()
{
if(!getBounds().equals(base))
{
Dimension s = getPreferredSize();
base = getBounds();
shape = new Ellipse2D.Float(0, 0, s.width-1, s.height-1);
}
}
#Override
public Dimension getPreferredSize()
{
Icon icon = getIcon();
Insets i = getInsets();
int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
return new Dimension(iw+i.right+i.left, iw+i.top+i.bottom);
}
#Override
protected void paintBorder(Graphics g)
{
init_shape();
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(getBackground());
//g2.setStroke(new BasicStroke(1.0f));
g2.draw(shape);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
#Override
public boolean contains(int x, int y)
{
init_shape();
return shape.contains(x, y);
//or return super.contains(x, y) && ((image.getRGB(x, y) >> 24) & 0xff) > 0;
}
}
Here's some test code I wrote:
public class BtnTest extends JFrame
{
private static final long serialVersionUID = 1L;
private RoundButton btn;
public BtnTest()
{
init_components();
}
private void init_components()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
setBackground(Color.BLACK);
setResizable(false);
btn = new RoundButton(
new ImageIcon("/path/to/file.png"));
btn.setBounds(50, 50, 50, 50);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("click");
}
});
add(btn);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
BtnTest frame = new BtnTest();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
This is the result:
The problem is: the image's rendered away from the button location, so my ActionListener isn't triggered when I press the icon, but when the region inside the black circle (top left) is clicked. Can someone explain me why, and offer a solution?
PS: I'm in my first year of Java programming, so make it as simple as possible please.
PS2: JavaFX and other external solutions are out of question, this must be done pure Java.
The problem is: the image's rendered away from the button location,
What is happening is that you are adding the button to the frame without using a constraint. By default this means the component is added to BorderLayout.CENTER, which means the component will be sized to fill the entire frame.
Also, by default, when you paint an Icon in a JLabel, the Icon is centered in the spaced available to the label, so you see the Icon in the center of the frame. Try resizing the frame to see the Icon move.
However, you hard code the painting of the Border to be painted at the (0, 0) location of the label so it paints at the top/left.
The contains() method is also defined from the top/left of the component, so mouse detection only works from the top/left, not the center.
This means your button must always be painted at its "preferred size" in order for it to be painted properly and for the contains(...) method to work.
A simple way to demonstrate this is to use:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout( new FlowLayout() );
The FlowLayout does respect the preferred size of the button, so the Icon and Border will be painted properly.
Other options (instead of changing the layout of the frame) are to use a "wrapper" panel for the button. For example:
//add(btn);
JPanel wrapper = new JPanel(); // default to FlowLayout
wrapper.add( btn );
add( wrapper );
Now when you add the panel to the frame, the panel will grow in size, but the button will still be painted at its preferred size.
btn.setBounds(50, 50, 50, 50);
Also, not you should NOT be using this method. It is the job of the layout manager to set the size/location of the component. The above statement is effectively ignored.

Java can't focus on JTextField inside JPanel inside JPanel as viewport in JScrollPane

I have to admit, maybe the question seems strange how is written, but I'll explain here:
I have a JScrollPane, in which i correctly add a JPanel, let'sa say that for now JScrollPane variable is "JSP" and JPanel is "JP"
In my JP i have an ArrayList of JPanel (let's call it AJP) which i can control visibility from outside the JSP, i can control this visibility by clicking different buttons, every button is "linked" with a number so if i click button1, the first AJP element get visibility set to true, and all the others set to false.
Every of these AJP elements has a different elements inside, so for example, AJP at first position has two JTextFields and 1 JButton, AJP at position 2 has 1 JTextField only.
The fact is that seems that i can't click a JButton or edit a JTextField, like the mouse can't focus them, I'll post here some code
This is the class which contains the JP and it's an extension of JScrollPane
private int x, y, width, height;
private JPanel internalPanel = new JPanel();
private ArrayList<KPanel> kPanels = new ArrayList<KPanel>();
JViewport viewport = new JViewport();
public KScrollPanel(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.internalPanel.setBorder(new LineBorder(Color.black, 1));
this.internalPanel.setSize(new Dimension(this.width - 10, this.height - 10));
this.internalPanel.setPreferredSize(new Dimension(this.width - 10, this.height - 10));
this.setSize(new Dimension(this.internalPanel.getWidth() + 10, this.internalPanel.getHeight() + 10));
this.setPreferredSize(this.internalPanel.getSize());
this.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
this.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
viewport.add(this.internalPanel);
}
This other is the class which pratically is composed the ArrayList in the class KScrollPanel i just posted, as you can see, the method initialize adds dinamically the elements (KButton and KTextFields are two classes extendind JButton and JTextField)
This class of course is an extension of JPanel
private int x, y, width, height;
KTextField textfield_nodeName, textfield_relationshipName;
KButton button_saveNode, button_saveRelationship;
public KPanel(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
initGUI();
}
public KPanel() {
}
public void initGUI() {
this.setBounds(this.x, this.y, this.width, this.height);
this.setBorder(new LineBorder(Color.gray, 1));
}
public void initialize(String type) {
switch(type) {
case "Node":
textfield_nodeName = new KTextField(5, 5, 200, 30);
this.add(textfield_nodeName);
button_saveNode = new KButton(5, 35, 200, 30, "Save Node");
this.add(button_saveNode);
break;
case "Relationship":
textfield_relationshipName = new KTextField(5, 5, 250, 30);
this.add(textfield_relationshipName);
break;
}
this.revalidate();
this.repaint();
}
Why are you extending JScrollPane?
Why are you creating a JViewport?
Why are you createing an internal JPanel to add to the viewport?
You should not be attempting to play with the sizes of any of your components. The layout manager will determine the size of each component.
The component you want to add to the scrollpane should be created separately from the scrollpanel. You can then set the Border of this component when you create it.
JViewport viewport = new JViewport();
...
viewport.add(this.internalPanel);
So it looks to me like you create an internal panel and add it to the viewport but you never add the viewport to the scroll pane, so there are no components to display.
So my suggestion is to get rid of the custom JScrollPane class then is no need to extend it because you are not adding any functionality to the class. Just use a JScrollPane the way it was designed to be used:
JPanel internalPanel = new JPanel();
internalPanel.setBorder(...);
internalPanel.add(...);
JScrollPane scrollPane = new JScrollPane( internalPanel );
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

Why i need to click twice to let my JButton work?

I'm building a program that draws random (User input) rectangles on a JPanel.
Problem 1:
Whenever I type a number in my JTextfield,I need to click twice on
the JBUtton for Rectangles to show up.
Problem 2:
When i type a new number in the JTextField the number of that don't
show the rectangle but it shows the rectangles I typed in previous.
CODE:
private void init() {
final int FRAME_WIDHT = 800;
final int FRAME_HEIGHT = 1000;
int input = 3;
JFrame frame = new JFrame();
frame.setSize(FRAME_WIDHT, FRAME_HEIGHT);
frame.setTitle("Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
west = new JPanel();
west.setSize(500, 500);
west.setBorder(BorderFactory.createLineBorder(Color.black));
east = new JPanel();
east.setSize(300, 1000);
button = new JButton("Add squares");
field = new JTextField(10);
button.setSize(100, 50);
east.add(button);
east.add(field);
east.setBorder(BorderFactory.createLineBorder(Color.black));
button.addActionListener(new java.awt.event.ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
JButton1ActionPerformed(evt);
}
public void JButton1ActionPerformed(ActionEvent evt) {
int aantalRect = Integer.parseInt(field.getText());
MyDrawing draw = new MyDrawing(aantalRect);
west.add(draw);
draw.revalidate();
draw.repaint();
}
});
frame.add(west, BorderLayout.CENTER);
frame.add(east, BorderLayout.EAST);
frame.setResizable(true);
frame.setVisible(true);
}
public static void main(String[] a) {
P1027 form = new P1027();
}
}
class MyDrawing extends JPanel {
int input = 0;
public MyDrawing(int i) {
this.input = i;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Random r = new Random();
setPreferredSize(new Dimension(500, 1000));
for (int i = 0; i < input; i++) {
int x = r.nextInt(460);
int y = r.nextInt(960);
g.drawRect(x, y, 40, 40);
}
}
Can any one tell me how to fix that?
Problem 1: You're not be seeing the squares being drawn on your MyDrawing JPanel the first time because you are calling the setPreferredSize(...) method, when you really should be overriding the getPreferredSize() method as explained by this answer. It is also possible that they are being drawn off-screen. You set the preferred height of MyDrawing to 1000, which doesn't fit on my laptop's screen (The green line is the border of a MyDrawing).
To fix Problem 1, override the method and lower the preferred height if necessary:
class MyDrawing extends JPanel {
... //Constructor
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500); //Changed from 1000 to 500
}
... //paintComponent(...)
//If you change 1000 to 500, don't forget to change 960 to 460 too
}
Problem 2: You're seeing the amount of rectangles you typed into the JTextField previously because:
You are forgetting to remove the previously added MyDrawing from west before adding the new one.
You are calling revalidate() and repaint() on draw when you should be calling it on its parent component, west.
To fix problem 2, remove the old MyDrawing from west, add the new one, then call revalidate() and repaint():
...
public void JButton1ActionPerformed(ActionEvent evt) {
west.removeAll(); //If the old MyDrawing is the only thing
//that has been added to west. Otherwise use
//remove(int index) or remove(Component comp)
west.add(draw);
west.revalidate();
west.repaint();
}
...
Other things:
You switched the T and H around in FRAME_WIDTH.
You could put the code in JButton1ActionPerformed(...) into the actual actionPerformed method.
Your JFrame looks exactly the same with and without the calls to setSize(...) on west, east, and button and that answer I mentioned earlier suggests not using those methods, so consider removing them.

How does the opaque property work in Swing?

This a simple application I got from here this answer to How to set a Transparent Background of JPanel
that's supposed to explain how setOpaque() works.
public class TwoPanels {
public static void main(String[] args) {
JPanel p = new JPanel();
// setting layout to null so we can make panels overlap
p.setLayout(null);
CirclePanel topPanel = new CirclePanel();
// drawing should be in blue
topPanel.setForeground(Color.blue);
// background should be black, except it's not opaque, so
// background will not be drawn
topPanel.setBackground(Color.black);
// set opaque to false - background not drawn
topPanel.setOpaque(false);
topPanel.setBounds(50, 50, 100, 100);
// add topPanel - components paint in order added,
// so add topPanel first
p.add(topPanel);
CirclePanel bottomPanel = new CirclePanel();
// drawing in green
bottomPanel.setForeground(Color.green);
// background in cyan
bottomPanel.setBackground(Color.cyan);
// and it will show this time, because opaque is true
bottomPanel.setOpaque(true);
bottomPanel.setBounds(30, 30, 100, 100);
// add bottomPanel last...
p.add(bottomPanel);
// frame handling code...
JFrame f = new JFrame("Two Panels");
f.setContentPane(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
// Panel with a circle drawn on it.
private static class CirclePanel extends JPanel {
// This is Swing, so override paint*Component* - not paint
protected void paintComponent(Graphics g) {
// call super.paintComponent to get default Swing
// painting behavior (opaque honored, etc.)
super.paintComponent(g);
int x = 10;
int y = 10;
int width = getWidth() - 20;
int height = getHeight() - 20;
g.fillArc(x, y, width, height, 0, 360);
}
}
}
The thing the I don't get is how come he is adding the opaque layer on top of transparent layer? shouldn't be the other way around?
The way that I picture how it should work is by adding the transparent layer on top of the opaque one, kinda of like how you put a screen protector over a phone(sorry for the dumb example)
Can someone please explain how transparency works in java?
I apologize of my question is a bit naive but this has been bothering me for a while!
Yes, the example reliest on the fact that with a null layout, the child components are indeed drawn in reverse order. An implementation dependency. That at least deserves mention. Adding a visible border would make it more evident:
private static class CirclePanel extends JPanel {
CirclePanel() {
setBorder(BorderFactory.createLineBorder(Color.RED));
}

Categories