I have strange problem with JComponent. I am trying to create my own JComponent and so I need to compose my JComponents together.
I wanted to paint JButton in my JComponent JDial:
public class JDial extends JComponent {
private static final long serialVersionUID = 3364481508702147328L;
public JDial() {
JButton b = new JButton("test");
this.add(b);
}
}
But that just paint nothing. Even more interesting is that this one works well:
public class JDial extends JPanel {
private static final long serialVersionUID = 3364481508702147328L;
public JDial() {
JButton b = new JButton("test");
this.add(b);
}
}
JPanel inherits from JComponent and paints JButton inside. How JPanel do this magic?
Thanks in advance
Generally you would extend JComponent when you want to do custom painting by overriding the paintComponent() method.
If you just want to add a bunch of components then you should use a JPanel.
The difference between the two is that by default JPanel uses a FlowLayout so it know how to layout any component added to it. To make JComponent like a JPanel you would need to set the layout manager and add custom painting to paint the background.
While JComponent also descends from Container and does have all code to repaint properly sized and positioned children, it does not have any capability to resize or layout them. And you do not set neither size nor location for your JButton so zero size is assumed by default.
Differently, JPanel is created with FlowLayout by default, this layout manager that will set component sizes mostly depending on they computed preferred sizes. In general, it is unusual to use JComponent as container directly, use JPanel.
Related
I am almost certain this question was asked before here: Java Swing: How to change GUI dynamically , but I seem to just have some fundamental misunderstanding in how it works.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class JTest extends JFrame
{
public static void main(String[] args)
{
JTest t = new JTest();
}
Container pane;
public JTest()
{
setSize(500,500);
setTitle("JTest");
setDefaultCloseOperation(EXIT_ON_CLOSE);
pane = getContentPane();
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("old");
old.addActionListener(new OldButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
setVisible(true);
}
private class OldButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("new");
old.addActionListener(new NewButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
pane.validate();
}
}
private class NewButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("old");
old.addActionListener(new OldButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
pane.validate();
}
}
}
This code should replace the preexisting layout with a new one anytime the button in the corner is pressed, but instead, it just adds the new layout to the frame. Can someone tell me what I'm doing wrong?
EDIT:
Adding some information. A picture for reference:
I'm making a set of components like this inside the scroll pane. whenever I press the "Make new field" button, I want it to add a "field" (the name of the field followed by a textarea or some such) to the set in that scrollpane. This means changing the layout of the area inside the scrollpane to include the new field.
OK -- so it looks like what you want to do (and please correct me if I'm wrong) is to add a new component to a JPanel that is displayed within a JScrollPane. If so, then you do not want to change or swap layouts, and you certainly don't want to keep adding new JScrollPanes. Instead consider doing:
Create one JScrollPane and add to your GUI. Don't re-add this as you'll only need one.
add a JPanel to the JScrollPane's viewport that uses a layout that allows multiple components to be easily added to it. Perhaps a GridLayout or a BoxLayout, depending on what you need.
Also consider not adding the above JPanel directly to the viewport but rather adding it to another JPanel, one that uses BorderLayout, adding the first JPanel to the BorderLayout-using JPanel's BorderLayout.PAGE_START position, and then add this to the JScrollPane's viewport. This way the first JPanel won't stretch to fill the viewport initially.
Then in your button's ActionListener, add your components to the first JPanel by calling .add(...) on it, and then call revalidate() and repaint() on that first JPanel to layout the newly added components and repaint the JPanel and its contents.
Ok, so it turns out this wasn't a layout problem at all. I had failed to realize that setting a new layout doesn't cause the previous layout's components to disappear, you have to remove them before adding the new components. That's why I was getting duplication.
Thanks for pointing me in the right direction, though.
I'm trying to understand what actually paints components in Swing. I read this article about painting in AWT and Swing and now tried to write the following simple program:
//A simple wrapper to understan how paint() works
public class MyButton extends JButton{
/**
* Default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private final JButton jButton;
public MyButton(JButton jButton) {
this.jButton = jButton;
}
#Override
public void paint(Graphics g){
jButton.paint(g);
}
}
But when I try to add MyButton to frame
JFrame frame = new JFrame("Hello swing");
JPanel panel = new JPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.add(new MyButton(button));
frame.add(panel);
it renders nothing
But after deleting
#Override
public void paint(Graphics g){
jButton.paint(g);
}
it renders the empty button:
QUESTION: Why does it behave that way? Why does the delegating cause rendering to fail?
First of all when you post a question you should post a proper SSCCE that demonstrates the problem. We can't copy/compile random lines of code. Until a problem is solved, you don't know what part of the code is causing the problem.
Why does the delegating cause rendering to fail?
My guess would be that the size of the button is (0, 0) so there is nothing to paint.
When you get rid of the custom paint method, then the real button can be painted because it does have a size because the layout manager has done its job.
public class Demo extends JFrame{
public static void main(String[] args)
{
JPanel panel = new JPanel();
getContentPane().setLayout(new BorderLayout());
panel.add(new JButton("Test"));
this.getContentPane().add(panel, BorderLayout.CENTER);
this.setSize(200,200);
this.setVisible(true);
}
}
If you want to add UI Components do it like that, don't use paint in any way.
If you want to paint for example a rectangle follow this tutorial: https://docs.oracle.com/javase/tutorial/uiswing/painting/
Your paint method does not draw the MyButton object, but instead draws the JButton which is member of your class. The problem now is, that this Button has not been added to the panel and so it's drawn on nothing. By removing your paint method, super.paint(g) is called because your class has no paint method and so your button, but not the member JButton is drawn.
I hope you understand what I am trying to explain to you.
I created a class that extends JComponent. An object of that class is added to a JPanel. A JPanel has a default flow layout manager. When I added the JPanel to a JFrame, nothing was visible except an empty JFrame. Yes, the frame is visible and sized to the maximum screen dimensions.
I tried several modifications to change this problem and deduced:
Component size - set it to no avail and still an empty JFrame
Intermediate panel - adding component to an intermediate panel and still an empty JFrame
Finally I decided to change the layout manager of the panel I was adding my component to and changed the panel layout manager to a border layout. I then added the component to the center and now it appears.
Follow-on questions I have are:
When making a custom JComponent, what are my considerations?
Why do I have to change the layout manager of a panel for a custom JComponent?
My naivety asks "If I can add a JButton to a panel and, using the default flow layout manager it shows, why not a custom JComponent?
My custom JComponent is an inner class:
public class OuterClass
{
private class Panel extends JPanel
{
public Panel()
{
add(new Custom());
}
}
private class Custom extends JComponent
{
public Custom()
{
// Initialization of members but not size of component
}
}
}
A JPanel has a default flow layout manager.
Yes, and the FlowLayout respects the preferred size of every component. Your component doesn't have a preferred size so it defaults to (0, 0).
Override the getPreferredSize() method to return the appropriate dimension.
"Why do I have to change the layout manager of a panel for a custom JComponent?"
The thing about JComponent is that it has no default LayoutManager to layout the components. If you run this test, you'll see
public static void main(String[] args) {
JComponent component = new JComponent() {};
System.out.println("JComponent = " + component.getLayout());
JPanel panel = new JPanel();
System.out.println("JPanel = " + panel.getLayout());
}
The result is
JComponent = null
JPanel = java.awt.FlowLayout[hgap=5,vgap=5,align=center]
So with JComponent, since it has a null layout, it doesn't know where to layout the the component. So you either have to
Explicity setBounds() on the component you want to add, or
Explicity set the layout, like you mentioned you had to do for the JComponent.
"Finally I decided to change the layout manager of the panel I was adding my component to and changed the panel layout manager to a border layout. I then added the component to the center and now it appears."
So by setting the LayoutManager to BorderLayout, you told the JComponent how to layout the components you add. It can be any LayoutManager though, not just BorderLayout
I'm creating an applet which consists of a class which extends JApplet, with a menubar and a class which extends a JPanel.(So there is a menubar and a JPanel shown in the applet).
In this class I add and remove some textfields to the JPanel. This all works fine. Here's where it gets tricky: it only works the first time. When I add some new textfields to the JPanel, they are added and visible in the JPanel, but the menubar in the JFrame stops working.
Since the code is too extensive I'll only post parts of it.
Here's the code where I add the JPanel to the JApplet:
public class Simulator extends JApplet implements ItemListener, ActionListener {
Container pane = getContentPane();
canvas = new DrawCanvas();
pane.add(canvas, BorderLayout.LINE_END);
}
Here's the code of the JPanel:
class DrawCanvas extends JPanel {
public void paintComponent(Graphics g) {
if(textfield != null)
remove(textfield);
textfield = new JTextField();
this.add(textfield);
}
}
This works the first time(when nothing is removed), but the second time the menubar stops working.
When I leave out the this.add(textfield); line, the menubar keeps working.
I once had similar problems with popup menus beeing painted behind other components.
Try calling static JPopupMenu.setDefaultLightWeightPopupEnabled(false); or the setLightWeightPopupEnabled on your specific submenu. This will make (all) popup menus (i.e. submenus) to heavy weight components that have a native peer.
I believe you are running into issues with threading. Adding and removing JComponents during painting might mess up the EDT (which is calling the paint method in the first place).
My program have 3 classes. 1) main, 2) frame, 3) drawingBoard. The logic of my program is that, a new drawing will be displayed every times user click on New pattern button (and this working fine).
1st class - main method
public class mainPage {
public static void main(String[]args){
JFrame appFrame = new Frame();
appFrame.setVisible(true);
appFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);*/
}
}
2nd class - describe the layout (I use Grid Bag Layout)
public class Frame extends JFrame implements ActionListener {
public Frame (){
GridBagLayout m = new GridBagLayout();
Container c = (Container)getContentPane();
c.setLayout (m);
GridBagConstraints con;
JButton bPattern = new JButton("New Pattern");
....
bPattern.addActionListener(this);
JPanel pDraw = new JPanel();
.....
pDraw.add(new drawingBoard()); //drawing will be placed in this panel
}
public void actionPerformed(ActionEvent e) {
repaint();
}
}
3rd class - run drawing functions e.g. paintComponent (), etc.
public class drawingBoard extends JPanel {
public drawingBoard(){}
public void paintComponent(Graphic g){}
....
}
The problem is that, when I look on the console, it seems that even though the user did not click on the button, the program call the class 'drawingBoard' and repaint. The paint component is in the 3rd class (drawingBoard). Although this seem not to give me a problem (e.g. no drawing displayed on the panel unless the user click the button), I am just curious how this happened. is that because I wrote this code at FRAME class (). My intention to write this code is to make sure the drawing should be place in this specific panel (I have 3 panels) but not to call the 3rd class unless the button has been clicked.
JPanel pDraw = new JPanel();
pDraw.add(new drawingBoard()); //place drawing here
The repaint method (and subsequently, the paintComponent method) is not only called by the JFrame but also by Swing itself as well, when there needs to be a repaint of the contents of the JPanel.
The Painting in AWT and Swing article is a good place to start to get information on how painting works.
In this case, the repaint method is being called by events which the article calls System-triggered Painting:
In a system-triggered painting
operation, the system requests a
component to render its contents,
usually for one of the following
reasons:
The component is first made visible on the screen.
The component is resized.
The component has damage that needs to be repaired. (For example,
something that previously obscured the
component has moved, and a previously
obscured portion of the component has
become exposed).