Swing Overlapping components - java

I have two AWT components in a Frame, Panel A and Panel B. I would like panel A to be sized to the height width of the frame (and maintain that size on frame resize), but I would like panel B to overlap A. B will be at a fixed position (0,0 to make it easier) with a fixed height and width. I'm not sure what kind of layout manager I would need to make this work. If I use a null layout, I think I would have to manage the resizing of panel A myself, but it would make the sizing of panel B relatively easy. Any thoughts on how to accomplish this?
thanks,
Jeff

Take a look at JLayeredPanes. Here is a tutorial.
edit:
If panelA is an AWT component, you will be hard pressed to get panelB to overlap. From Sun's article entitled Mixing Heavy and Light Components:
Do not mix lightweight (Swing) and heavyweight (AWT) components within a container where the lightweight component is expected to overlap the heavyweight one.
However, if you are looking to have panelA fill the Frame completely, why not add panelB as a component of panelA?
Edit2:
If you can make panelB a heavyweight component, then you can use the JLayeredPane.
Here is a quick mockup that shows how:
public static void main(String[] args){
new GUITest();
}
public GUITest() {
frame = new JFrame("test");
frame.setSize(300,300);
addStuffToFrame();
SwingUtilities.invokeLater(new Runnable(){
public void run() {
frame.setVisible(true);
}
});
}
private void addStuffToFrame() {
Panel awtPanel = new Panel();
awtPanel.setBackground(Color.blue);
//here you can fool around with the pane:
//first, you can see how the layered pane works by switching the
//DEFUALT_LAYER and PALLETTE_LAYER back and forth between the two panels
//and re-compiling to see the results
awtPanel.setSize(200,300);
frame.getLayeredPane().add(awtPanel, JLayeredPane.DEFAULT_LAYER);
//next you comment out the above two lines and
//uncomment the following line. this will give you the desired effect of
//awtPanel filling in the entire frame, even on a resize.
//frame.add(awtPanel);
Panel awtPanel2 = new Panel();
awtPanel2.setBackground(Color.red);
awtPanel2.setSize(300,200);
frame.getLayeredPane().add(awtPanel2,JLayeredPane.PALETTE_LAYER);
}

Your best bet is to have your own LayoutManager. The easiest way is probably to extend or proxy BorderLayout, and have a specific case to layout panel B.

Maybe I'm missing something. If B is fixed size and is at (0,0) and A runs the full width what's the use of having B overlap A? You will never see anything that is placed under B.
I can't think of any of the default layout managers, but Orielly has this one you can use:
relative layout manager (see source code link) with documentation. I haven't used it in a long while, but it should beat managing the layout yourself.

Related

Centering Java JPanel Elements [duplicate]

I have a panel derived from JPanel. I have a custom control derived from JLabel. I am attempting to center this custom JLabel on my panel.
The only way I know to do this that will work is to use the a null layout (setLayout(null)) and then calculate the custom JLabel's setLocation() point so that it's in the right spot.
The custom JLabel is physically moved from one panel to this panel in this app and I believe the location previously set in setLocation is affecting things. However when I set it to (0,0) the component goes up into the upper left corner.
BorderLayout doesn't work because when only 1 component is provided and placed into BorderLayout.CENTER, the central section expands to fill all of the space.
An example I cut and pasted from another site used BoxLayout and component.setAlignmentX(Component.CENTER_ALIGNMENT). This didn't work either.
Another answer mentioned overriding the panel's getInset() function (I think that's what it was called), but that proved to be a dead end.
So far I'm working with a panel with a GridBagLayout layout and I include a GridBagConstraints object when I insert the custom JLabel into my panel. This is inefficient, though. Is there a better way to center the JLabel in my JPanel?
Set GridBagLayout for JPanel, put JLabel without any GridBagConstraints to the JPanel, JLabel will be centered
example
import java.awt.*;
import javax.swing.*;
public class CenteredJLabel {
private JFrame frame = new JFrame("Test");
private JPanel panel = new JPanel();
private JLabel label = new JLabel("CenteredJLabel");
public CenteredJLabel() {
panel.setLayout(new GridBagLayout());
panel.add(label);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(panel);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
CenteredJLabel centeredJLabel = new CenteredJLabel();
}
});
}
}
Supose your JLabel is called label, then use:
label.setHorizontalAlignment(JLabel.CENTER);
Forget all the LayoutManagers in the Java Standard Library and use MigLayout. In my experience it's much easier to work with an usually does exactly what you expect it to do.
Here's how to accomplish what you're after using MigLayout.
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
public class Test
{
public static void main( String[] args )
{
JFrame frame = new JFrame( );
JPanel panel = new JPanel( );
// use MigLayout
panel.setLayout( new MigLayout( ) );
// add the panel to the frame
frame.add( panel );
// create the label
JLabel label = new JLabel( "Text" );
// give the label MigLayout constraints
panel.add( label, "push, align center" );
// show the frame
frame.setSize( 400, 400 );
frame.setVisible( true );
}
}
Most of that is just boilerplate. The key is the layout constraint: "push, align center":
align center tells MigLayout to place the JLabel in the center of its grid cell.
push tells MigLayout to expand the grid cell to fill available space.
BoxLayout is the way to go. If you set up a X_AXIS BoxLayout, try adding horizontal glues before and after the component:
panel.add(Box.createHorizontalGlue());
panel.add(label);
panel.add(Box.createHorizontalGlue());
I don't like the answers here.
I've never seen a valid use of a GridBagLayout ever in my career. Not saying there isn't one, just saying I haven't seen [a valid] one, and there might be correlation there. Moreover, adding a single JLabel to the middle of a Container might make it center for demonstrational purposes, but you're going to have a lot harder of a time later on if you try to continue to work with that over some other layouts.
I do like the suggestion about the BoxLayout, because that is actually a great way to do it. That said, that answer is only part of the puzzle, hence why I'm dredging up a 7 year old question.
My 'Answer'
Really there is no short answer to your question. There is an exact answer to your question based on what you asked, but StackOverflow is about a community learning from each other, and I suspect you're trying to learn how to use layouts in general (or you were 7 years ago) and telling you to type a combination of keys to do exactly your demo case is not going to teach you the answer.
I'm going to try not to explain any layouts that you can't web-search the answer for on your own (with a link to the Oracle tutorial at the end, because I think it explains the different layouts fairly well).
BoxLayout
BoxLayout is one way to do it, and there is already a code snippet to demo it above so I won't provide one. I'll expand on it to say that, just as mentioned, that only answers your question exactly, but doesn't really teach you anything. Glue, as the BoxLayout refers to it, basically gives you an equal amount of remaining real-estate between all the 'glue' currently in the Container. So, if you were to do something like
panel.add(Box.createHorizontalGlue());
panel.add(label);
panel.add(Box.createHorizontalGlue());
panel.add(otherLabel);
You would find that your JLabel is no longer centered, because the 'glue' is only the remaining real-estate left, after two JLabels were added to the Container which will be equally divided between the two 'slots' (two calls to Container#add(Component) with a glue parameter) in theContainer`.
BorderLayout
BorderLayout is another way to go about this. BorderLayout is broken down into 5 regions. BorderLayout#CENTER, as you might guess, is the center region. The important note about this layout and how it centers things is how it obeys sizes of the Component that is in the center. That I won't detail though; the Oracle tutorial at the end covers it well enough if you're interested, I think.
Worth Noting
I suppose you could use a GridLayout, but it's a more simple way to do it over a GridBagLayout, which I already said even that I think is not a good approach. So I won't detail this one either.
The Point of it All
Now all that said, I think all LayoutManagers are worth a look. Just like anything else with relation to programming - use the tool that fits the job. Don't just assume because you used X layout before, that you should always use X layout and no other layout is viable. Figure out what you want your display to look like, and how you think it should behave with respect to resizing components, and then pick what you think would work best.
Another dual meaning of picking the right tool, is that you don't have to just fit all of your components into one single Container and make one layout do everything. There is nothing stopping you (and I strongly encourage you to) use multiple Containers and group them all together. Control each Container with a layout that is appropriate for that section of the display, and a different layout for a different Container.
Important!!
The reason why this is very important is because each layout has rules and things that they obey, and other things that they respect, and then others that are effectively ignored (i.e. preferred, maximum, and minimum sizes, for instance). If you use different layouts [correctly], you will find your display accepts dynamically being resized while still obeying the shape that you wanted it to hold. This is a very important key difference between doing it the right way, and just settling with GridBagLayout.
JPanel outer = new JPanel(new BorderLayout());
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.PAGE_AXIS));
JPanel southPanel = new JPanel(new CardLayout());
outer.add(centerPanel, BorderLayout.CENTER);
outer.add(southPanel, BorderLayout.SOUTH);
Figure out what is appropriate to your scenario, and go with that. There is no one-size-fits-all unless you want something overly cumbersome and made redundant by other layouts (i.e. GridBagLayout).
Oracle's Tutorial
If you've made it this far, then I think you're looking for as much information as you can get. In which case, I strongly encourage you to read Oracle's tutorial on layout managers because it lays out general details of them all very well: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html
Use this.
labelName.setHorizontalAlignment(SwingConstants.CENTER);
or
labelName.setHorizontalAlignment(JPanel.CENTER);
Both of them must work.
Do like this instead of libraries and layouts :
JLabel jlabel = new JLabel("Label Text", SwingConstants.CENTER);
Make sure to import javax.swing.SwingConstants INTERFACE , BUT DO NOT IMPLEMENT IT. It contains only constants and no methods.
Put the JLabel in a JPanel or else it will come at the center of the JFrame or JWindow (your top level container).

How do I put JPanel on another JPanel?

I am new to the java (and programming in general) and I am trying to make my very first program. I'm stuck on the same problem for about 5 hours now, so I've decided to ask for help.
Basically I'm trying to make a program (2d game) that has about 20 positions on the board. Each position is either blue (owned by player1), red(owned by player2) or black(not owned by anyone).
The way I'm going about this is in main I've put a method that calls setup game, and then a method that plays game. I am working on the setup game, basically all it does is it makes an object of class Background (extends JPanel, and overrides paintComponent()) and 20 objects of class Position(extends JPanel, and overrides paintComponent()).
So far I'm stuck on putting those Position objects on top of Background object.
When I do:
Background background= new Background();
frame.getContentPane().add(background);
Position position1= new Position;
frame.getContentPane().add(position1);
frame.setVisible(true);
it shows only a circle and no background as I was hoping, if I first add position and then background, I only have background and no circle.
Anyway I'm new to the java and I am still having trouble founding my way around, however I've tried to search for solutions, and I've found many different solutions to this problem (such as adding position to background first, and then adding background to frame, etc.) but I couldn't make any of them to work.
I am aware that the way I am adding them both to frame is (very likely) completely wrong, but I wrote it that way so you would (hopefully) be sure that what I've wrote actually does show you that my code for each of those classes draws something on the screen.
PS: I didn't copy my code here as most of variable and method names aren't in English so it's fairly hard to read, but if you still think its needed, I will add it. Also I'm sorry for my probably stupid question, but I'm kinda hitting a wall here and I've no idea what else to try.
Basically I'm trying to make a program (2d game) that has about 20
positions on the board. Each position is either blue (owned by
player1), red(owned by player2) or black(not owned by anyone).
Painting in Swing by default never returns PreferredSize, is required to override getPreferedSize()
JPanel has implemented FlowLayout in API, this LayoutManager accepting only PreferredSize came from JComponents added to this container
after a.m. changes to post an SSCCE, short, runnable, compilable
Background background= new Background();
frame.getContentPane().add(background);
Position position1= new Position;
frame.getContentPane().add(position1);
A JFrame uses a BorderLayout by default. Also by default when you add a component to a Container that uses a BorderLayout the comopnent is added to the CENTER. Only one comonent can be added to the CENTER so your Position comonent replaces the Background component.
You want to add the Position to the Background and then add the Background to the frame. Something like:
Background background= new Background();
Position position1= new Position;
background.add(position1);
frame.add(background);
Note: there is no need to uses getContentPane() when adding a component to the frame.
The root panel should be a JFrame with a Container class underneath. When you call someRoot.window.container = yourJPanel, that loads the JPanel as the main component view of the JFrame. Note, a JFrame can only hold one JPanel but other JPanels can hold other JPanels. Just as you add the initial JPanel to the JFRam, a JPanel's own container can be another JPanel. Hope this helps.
Like this:
JPanel temp = new JPAnel();
frame.getContentPane().add(temp);
temp.getContentPane().add(new JPanel());
After these additions, there is a command that is illuding me but you call on JFrame to get it to refresh in real time. I think it is something like:
frame.validate(); //thanks #SMT
or something,
Try using something like
jPanelExampleName.validate();
jPanelExampleName.repaint();
after adding your JPanels.
It sounds like you want to use one JFrame and attach JPanels to it. This is how I personally would do it.
Declare your JFrame and JPanels
JFrame frame1 = new JFrame( "App Name");
JPanel panel1 = new JPanel();
JPanel panel2 = new JPanel();
JPanel panel3 = new JPanel();
JPanel panel4 = new JPanel();
Set the Background (I'm using colors but you get the idea)
panel1.setBackground(Color.orange);
panel2.setBackground(Color.orange);
panel3.setBackground(Color.orange);
panel4.setBackground(Color.orange);
Set your layout for the JFrame (I'm using BoxLayout not sure which would be best for you) You can find the best one for you and some sample code here. Also just set the default close operation.
frame1.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame1.setLayout( new BoxLayout( frame1.getContentPane(), BoxLayout.Y_AXIS ) );
Then Just attach your JPanels
frame1.add( panel1);
frame1.add( panel2);
frame1.add( panel3);
frame1.add( panel4);
frame1.pack();
frame1.setVisible( true );
This will allow you to use the JPanels you created and then change the colors via other methods.

JScrollPane does not update scrollbar

I am trying to create a JScrollPane that contains a JPanel that will be increasing and decreasing in height. When it becomes larger than the size of the JScrollPane, it should create a vertical scroll bar which will allow me to scroll through the entire JPanel. However, I am having difficulty achieving this. Yes, I know I am not using LayoutManagers. No, I will not be using them, and I need a solution that does not involve their usage.
Here are the two button's AbstractActions that add and subtract from the JPanel:
class AddACT extends AbstractAction
{
public void actionPerformed(ActionEvent e)
{
info.setSize(420,info.getHeight() + 40);
info.add(new SubPanel); // Adds another JPanel into the main JPanel (for content input)
gui.repaint();
infoS.validate();
}
}
class RemoveACT extends AbstractAction
{
public void actionPerformed(ActionEvent e)
{
info.remove(subPanel()); // This would remove the last JPanel added to the main JPanel
info.setSize(420,info.getHeight() - 40);
gui.repaint();
infoS.validate();
}
And here is the code for the main JPanel and the JScrollPane:
final JPanel info = new JPanel();
final JScrollPane infoS = new JScrollPane(info, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
info.setLayout(null);
info.setSize(420,600);
infoS.setLocation(10,80);
infoS.setSize(420,490);
gui.add(infoS); // gui is the frame's content pane (the overall JPanel)
This is the second project I've been trying to learn GUI by doing. I am a complete novice in Swing and am only intermediate in Java. Sorry if I am making a blindingly obvious mistake.
1) Use LayoutManagers (+1 to #kleopatra and #GagandeepBali comments)
The absence of LayoutManagers only guarantees your GUI's will look very trashy (especially when run on other OSes/builds) and being a Novice you should rather learn the correct way than learn the wrong way and get into bad habits like calling setSize() etc.
Have a read on these links to get you started:
A Visual Guide to Layout Managers
Concurrency in Swing
2) See this example for how to use a JScrollPane, it simply adds a JPanel with buttons to a JScrollPane which in-turn is added to the JFrame.
3) Also see this example for how to make the JScrollPane vertically scroll-able only.
4) For more on JScrollPanes have a look here: How to Use Scroll Panes.
5) As for how it interacts with LayoutManager, if you do not explicitly set its size via setPreferredSize(Dimension d) the scroll pane computes it based on the preferred size of its nine components (the viewport, and, if present, the two scroll bars, the row and column headers, and the four corners)
6) On your usage of validate():
validate() is used when new JComponents are added to a visible component
revalidate() is used when JComponent is removed/added from a visible component
revalidate() covers validate() too
Thus always use this:
//add or remove component(s)
revalidate();
repaint();
References:
http://www.daniweb.com/software-development/java/threads/405568/validate-vs-revalidate
LayoutManager is not required to solve the problem. The problem in Thrfoot's example is in these lines:
final JScrollPane infoS = new JScrollPane(info, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
info.setLayout(null);
info.setSize(420,600);
The program appears to recognize there is a need for scroll bars (it would show the scroll bar if your setting was VERTICAL_SCROLLBAR_AS_NEEDED), but the actual scrolling does not work (the scroll bar slider is not there).
To fix this, first set the preferred size of info, then construct the infoS.
Example:
info.setPreferredSize(420,600);
final JScrollPane infoS = new JScrollPane(info, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
The idea is to set the preferred size of the info panel before it is used for the scroll pane. This is the same reason to set the size and location of infoS before adding to the gui:
infoS.setLocation(10,80);
infoS.setSize(420,490);
gui.add(infoS); // gui is the frame's content pane (the overall JPanel)

Using JSplitPane with an AWT component

I have an AWT canvas which I cannot convert to a Swing component (it comes from VTK). I wish to display a few of these canvases inside of a JSplitPane. I've read about mixing heavy and light weight components in Java and know that it's a pain in the butt, but I don't have a choice. If I wrap the AWT canvas inside of a JPanel and then put that on the split pane the split pane doesn't function at all. However, if I put the AWT canvas inside of a JPanel and then that inside of a JScrollPane and then those scroll panes on the JSplitPane the split pane does function, but the AWT canvas components don't resize properly. I'm lost about how to get the AWT canvas components to resize properly when the JSplitPane's divider is moved. I can catch the divider moving operation and operate on the AWT canvases at that time, but I don't know what to do. I've tried calling invalidate() then validate() then repaint(), but that didn't work.
Any ideas?
Here's a example of the problem
import javax.swing.*;
import java.awt.*;
public class SwingAWTError {
public static void main(String[] args) {
Canvas leftCanvas = new Canvas();
Canvas rightCanvas = new Canvas();
leftCanvas.setBackground(Color.RED);
rightCanvas.setBackground(Color.BLUE);
JPanel leftPanel = new JPanel();
JPanel rightPanel = new JPanel();
leftPanel.setLayout(new BorderLayout());
rightPanel.setLayout(new BorderLayout());
leftPanel.add(leftCanvas, BorderLayout.CENTER);
rightPanel.add(rightCanvas, BorderLayout.CENTER);
JScrollPane leftScroll = new JScrollPane();
JScrollPane rightScroll = new JScrollPane();
leftScroll.getViewport().add(leftPanel);
rightScroll.getViewport().add(rightPanel);
JSplitPane split = new JSplitPane();
split.setLeftComponent(leftScroll);
split.setRightComponent(rightScroll);
split.setDividerLocation(400);
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(split, BorderLayout.CENTER);
frame.setSize(800, 800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
It is a dirty way but this will solve it:
When you call pack() without resizing the window, not much happens. So, when you first resize the window and then call pack(), your components are correcly drawn. This means you can put this dirty method in your divider moved listener method:
frame.setPreferredSize(frame.getSize()); // store the current size to restore it after packing.
frame.setSize(frame.getWidth() + 1, frame.getHeight()); // resize it!!
frame.pack();
I don't know what it is exactly but it is a strange behavour in Java...
Hope this helps until you've found a better solution...
You are kind of out of luck here. There's a pretty good article on this on the sun/oracle website:
http://java.sun.com/products/jfc/tsc/articles/mixing/
Essentially it boils down to this guideline (taken from that link, under the z-ordering heading):
Do not mix lightweight (Swing) and
heavyweight (AWT) components within a
container where the lightweight
component is expected to overlap the
heavyweight one.
Edit: I kept browsing that site and came across another link, and it would appear that the situation has improved slightly: http://java.sun.com/developer/technicalArticles/GUI/mixing_components/ But I think your case is one of those listed at the bottom in the limitations section:
Limitations
A few situations are not supported:
* Non-opaque lightweight components that have translucent
pixels (0 < alpha < 255) are not
supported. If a partially translucent
lightweight component overlaps a
heavyweight component, the heavyweight
component will not show through.
* Embedded heavyweight components must belong to the process that
created the frame or applet. The
heavyweight component must have a
valid peer within the main process of
the application (or applet).
* Advanced Swing key events, such as those events maintained in an
InputMap, might not work correctly
where lightweight and heavyweight
components are being mixed. There are
no known workarounds.

Why does BorderLayout overwrite components when new ones are added?

BorderLayout does something strange. If I add two panels to a Container with the same constraint (BorderLayout.CENTER for instance), then the first one goes away, even if the second one is deleted or made invisible
It seems as though it would make sense for it to "stack" each element on top of the previous ones.
Is this correct and by design? If so, is there some documentation on it?
Has anyone else been frustrated by it? Have you a solution, such as a custom LayoutManager?
Sample code:
JFrame frame = new JFrame();
frame.setSize(500, 500);
JPanel panel1 = new JPanel();
panel1.setBackground(Color.blue);
frame.getContentPane().add(panel1);
JPanel panel2 = new JPanel();
panel2.setBackground(Color.red);
frame.getContentPane().add(panel2);
panel2.setVisible(false); // Seems like it should allow us to see panel1.
frame.setVisible(true);
This creates and displays a 500x500 blank box.
BorderLayout was simply not designed to do what you want. Separation of responsibility. If you want that behavior you should compose: combine the BorderLayout with a CardLayout. Though for the actual stack behavior, you'll have to code something yourself (or find someone who already has.)
Is this correct and by design?
Yes.
You need to understand the basics of how layout managers work. One of the jobs of the layout manager is to set the "location" and "size" of the components added to the panel. In the case of a BorderLayout it only tracks 5 components so only the last component added to the CENTER is known by the layout manager.
Layout management is not done when components are added to the panel. It is done when the frame is packed, or made visible (or the revalidate() method is invoked) . In this case the blue panel is not part of the components managed by the BorderLayout so its size remains (0, 0), which means there is nothing to paint.
Try changing your code to:
JPanel panel1 = new JPanel();
panel1.setSize(200, 200);
and you will see the blue panel painted at the specified size.
Now try commenting out:
//panel2.setVisible(false);
and you will see both panels. This is because as components are added to the panel they are assigned a ZOrder. Basically the last component added is painted first, which is why the blue panel is painted on top of the red panel. Check out the setComponentZOrder() method of the Container class for more information.
The CardLayout is probably the layout manager you should be using, but you can check out the Overlap Layout as well.

Categories

Resources