arrange non rectangular JPanels in Java Swing - java

I've defined a new class LShapePanel which extends JPanel and which looks like an L.
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class LShapePanel extends JPanel{
public Color color;
public LShapePanel(Color color) {
this.color = color;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
/* coordinates for polygon */
int[] xPoints = {0,100,100,20,20,0};
int[] yPoints = {0,0,20,20,100,100};
/* draw polygon */
g2d.fillPolygon(xPoints, yPoints, 6);
}
}
I'd like to arrange two of these LShapePanels like this:
But I don't know how? Here is my code for arranging two LShapePanels in a row.
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class DifferentShapes extends JFrame {
public DifferentShapes() {
setTitle("different shapes");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocation(500, 300);
JPanel panel = new JPanel();
/* create and add first L in red */
LShapePanel lsp1 = new LShapePanel(new Color(255,0,0));
lsp1.setPreferredSize(new Dimension(100,100));
panel.add(lsp1);
/* create and add second L in green*/
LShapePanel lsp2 = new LShapePanel(new Color(0,255,0));
lsp2.setPreferredSize(new Dimension(100,100));
panel.add(lsp2);
add(panel);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
DifferentShapes df = new DifferentShapes();
df.setVisible(true);
}
});
}
}
And the result:

You need to use layout manager to arrange the components in the JFrame. According to this turorial, the content pane, which actually contains the components you put into JFrame, uses Borderlayout by default. In an "L" shape as the LShapePanel looks, it's actually a rectangle(every component in swing is a rectangle, as a matter of fact) with part of it transplant. So if you want to arrange the panels in the way you want, they will have to overlap with each other. Different kinds of layout managers use different layout strategies, and Borderlayout won't allow components to overlap, so you have to change to another layout manager. Sorry that I don't know any layout manager that allows components to overlap, but you can use JLayeredPane to achieve you goal. Add a JLayeredPane to the JFrame and then add the LShapePanels to the JLayeredPane.

Sorry, layout manager enthusiasts, but I can't think of any way other than using setLocation and a null layout manager. Here's a demonstration:
setLayout(null);
LShapePanel lsp1 = new LShapePanel(new Color(255,0,0));
lsp1.setPreferredSize(new Dimension(100,100));
lsp1.setLocation(0,0);
add(lsp1);
LShapePanel lsp2 = new LShapePanel(new Color(0,255,0));
lsp2.setPreferredSize(new Dimension(100,100));
lsp2.setLocation(30,30);
add(lsp2);

Related

Java Swing GUI BorderLayout: Location of components

I'm new to Java and I'm playing around with a simple GUI example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class DrawTest {
class DrawingPanel extends JPanel {
private Rectangle2D shape;
public DrawingPanel(Rectangle2D shape) {
this.shape = shape;
}
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
g2D.setColor(new Color(31, 21, 1));
g2D.fill(shape);
}
}
public void draw() {
JFrame frame = new JFrame();
Rectangle2D shape = new Rectangle2D.Float();
final DrawingPanel drawing = new DrawingPanel(shape);
shape.setRect(0, 0, 400, 400);
frame.getContentPane().add(BorderLayout.NORTH, new JButton("TestN"));
frame.getContentPane().add(BorderLayout.SOUTH, new JButton("TestS"));
frame.getContentPane().add(BorderLayout.EAST, new JButton("TestE"));
frame.getContentPane().add(BorderLayout.WEST, new JButton("TestW"));
frame.getContentPane().add(BorderLayout.CENTER, drawing);
frame.pack();
frame.setSize(500,500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
public class DrawMain {
public static void main(String[] args) {
DrawTest test = new DrawTest();
test.draw();
}
}
As expected, this code produces a frame with the rectangle at the centre and buttons around it. However, if I change the code like this:
frame.getContentPane().add(BorderLayout.NORTH, drawing);
frame.getContentPane().add(BorderLayout.SOUTH, new JButton("TestS"));
frame.getContentPane().add(BorderLayout.EAST, new JButton("TestE"));
frame.getContentPane().add(BorderLayout.WEST, new JButton("TestW"));
frame.getContentPane().add(BorderLayout.CENTER, new JButton("TestC"));
the "TestC" button gets a huge area in the middle while the rectangle doesn't get enough space. This is even true if I remove the other buttons (TestS, TestE, TestW): I get a huge TestC button and a tiny part of the rectangle (even not the scaled rectangle) at the top.
Why doesn't the rectangle get enough space when it's drawn at the top (NORTH) but does get it when it's drawn at the CENTER?
The DrawingPanel should #Override getPreferredSize() to return an appropriate size.
The layout manager will then take that preferred size as a hint. Some layout managers will expand a component's height or width according to the logic of the layout and constraint. E.G. a BorderLayout will stretch components in the PAGE_START / PAGE_END to the width of the content pane, and LINE_START / LINE_END to the height of the tallest of either of those, or the CENTER. A GridBagLayout OTOH will completely hide / remove a component for which there is not enough space to display it at the preferred size, and that's where 'pack' comes in.
So change frame.setSize(500,500); (which is no better than a guess) to frame.pack();, which will make frame the minimum size it needs to be, in order to display the components it contains.

Using Layout Manager with JPanel and Graphics 2D

I want to draw lines and more on a JPanel, add that to a JFrame and using .pack() afterwards. My problem is that I dont get how to use a Layout Manager in that particular case. Usually I add a button or something to the panel by using a gridBagLayout and I totally understand that. But with graphics 2D I kind of just draw directly to the panel. Therfore I cant use .pack() properly. Does somebody know how to pack() that jPanel the right way? My code looks like that:
public class NetworkViewPanel extends JPanel implements KeyListener, ActionListener {
public NetworkViewPanel(NetworkAI network) {
this.network = network;
this.netList = network.getLayerList();
addKeyListener(this);
setFocusable(true);
this.setLayout(new GridLayout(2, 2, 2, 2)); // does that even make sense ?
}
public void paint(Graphics g) {
super.paint(g);
g2 = (Graphics2D) g;
if (showStandardView) {
drawRectangles();
drawLines();
} else {
drawRectangles();
drawLinesSpecial(listIndex, xIndex);
}
}
Greetings :)
You can layout a JPanel with a layout manager, and do custom painting on top of it.
This does not prevent you from using pack()1.
The following mre 2 demonstrates painting a line on a JPanel using a GridLayout:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class NetworkViewPanel extends JPanel{
private final List<JLabel> labels;
public NetworkViewPanel() {
this.setLayout(new GridLayout(2, 2, 2, 2));
this.setPreferredSize(new Dimension(400,300));//used by pack()
labels = new ArrayList<>();
addLabels(new String[]{ "A", "B" , "C" , "D"});
}
private void addLabels(String[] text){
for(String t: text){
JLabel label = new JLabel(t);
label.setBorder(BorderFactory.createLineBorder(Color.BLUE));
label.setHorizontalAlignment(JLabel.CENTER);
add(label);
labels.add(label);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //draw panel as layed out by layout manager
drawLines(g);
}
private void drawLines(Graphics g) {
//draw line between centers of first and last components
int x1 = labels.get(0).getBounds().x + labels.get(0).getBounds().width /2;
int y1 = labels.get(0).getBounds().y + labels.get(0).getBounds().height /2;
int x2 = labels.get(labels.size()-1).getBounds().x + labels.get(labels.size()-1).getBounds().width/2;
int y2 = labels.get(labels.size()-1).getBounds().y + labels.get(labels.size()-1).getBounds().height/2;
g.setColor(Color.RED);
g.drawLine(x1, y1, x2, y2);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new NetworkViewPanel());
f.pack();
f.setVisible(true);
}
}
1 See: What does .pack() do?
2 Consider posting mre when asking or answering

Java Swing Scroll through drawing

Im trying to add a JScrollpane to my JPanel. The problem is that the scrollpane doesn't recognize that my drawing is outside the frame. So how do I add the JScrollpane correctly?
Main class:
public MainFrame() extends JFrame{
public MainFrame() {
Container container = getContentPane();
container(new BorderLayout());
container.add(new JScrollPane(new Drawing()));
setSize(1280,720);
setVisible(true);
}
Drawing class:
public class Drawing() extends JPanel {
#Override
protected void paintComponent(Graphics g) {
g.drawLine(10, 100, 30000, 10);
}
}
There are a couple of errors in your code, let's step through each of them:
You're extending JFrame, and you should avoid it, see: Extends JFrame vs. creating it inside the program for more information about it. You're actually not changing its behavior so it's not needed to extend it.
For your JScrollPane to show the whole line, you need to change your window's size to be the same size of your line (as shown in this answer by #MadProgrammer).
Related to point 2, avoid the use of setSize(...) and instead override getPreferredSize(): See Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? for more information
You forgot to call super.paintComponent(...) method in your paintComponent() method.
Related to points 2, 3, you need to call pack() so Swing calculates the best preferred size for your component.
See this example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class LongDraw {
private JFrame frame;
private Drawing drawing;
public static void main(String[] args) {
SwingUtilities.invokeLater(new LongDraw()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
drawing = new Drawing();
JScrollPane scroll = new JScrollPane(drawing);
frame.add(scroll);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class Drawing extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawLine(10, 100, 3000, 10);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(3000, 500);
}
}
}
Which produces something similar to this:

Painting on a JPanel and adding it to a JScrollPane

As the title suggests, i want to paint on a JPanel and have it displayed within a JScrollpane so if the contents in the JPanel is large, scrollbars are displayed. I tried to accomplish this by:
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Main extends JFrame{
DrawPanel dp;
JScrollPane jsp;
public Main(){
setTitle("Test");
setSize(400,400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
dp = new DrawPanel();
jsp = new JScrollPane(dp);
getContentPane().add(jsp);
}
public class DrawPanel extends JPanel{
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.drawString("..long text....long text....long text....long text....long text....long text....long text....long text..", 10, 20);
}
}
public static void main(String[] args) {
new Main().setVisible(true);
}
}
Current Output
Expected Output: A window with the long text and horizontal scrollbar.
I have also tried overriding the getPreferredSize method (as some answers in other questions suggested) but it doesn't seem to work.
Also, can someone explain why scrollbars appear (using jscrollpane) when there are labels or buttons in a panel which is added to the jscrollpane, but the same doesn't work in the case above? Any help will be much appreciated.
You can resize a JPanel via setting the size(s) or overriding them.
Dimension d = new Dimension(800, 800);
dp.setMinimumSize(d);
dp.setMaximumSize(d);
dp.setPreferredSize(d);
Scroll bars appear when I do that.

Getting paintComponent elements of a JPanel to work with another JPanel inside of it

I have a JPanel with a simple animated snow particle effect inside of a JFrame, and it works from on its own. But when I try and add another panel to it, it makes the effect of the snow stop. Can anyone help me with this? Is it a problem with layering, or something? Or is it an issue with my Layout Managers?
My Code:
package christmasfinal;
import java.awt.*;
import javax.swing.*;
public class ChristmasFinal extends JApplet {
public static void main(String[] args) {
JFrame frame = new JFrame();
TreePanel treePanel = new TreePanel();
frame.setSize(550, 420);
SnowBackgroundPanel snowPanel = new SnowBackgroundPanel(frame.getWidth(), frame.getHeight());
treePanel.setSize(200, 320);
snowPanel.setSize(frame.getWidth(), frame.getHeight());
snowPanel.setLayout(new BorderLayout());
frame.setLayout(new BorderLayout());
frame.add(snowPanel, BorderLayout.CENTER);
snowPanel.add(treePanel, BorderLayout.CENTER);
System.out.println(treePanel.getWidth() + " " + treePanel.getHeight());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setTitle("Christmas Final");
}
}
package christmasfinal;
import javax.swing.*;
import java.awt.*;
public class TreePanel extends JPanel{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int[] treeX = {getWidth()/2, getWidth()/2-20, getWidth()/2-60, getWidth()/2-20,
getWidth()/2-40, getWidth()/2-80, getWidth()/2-20, getWidth()/2-60, getWidth()/2-100,
getWidth()/2+100,getWidth()/2+60, getWidth()/2+20, getWidth()/2+80,
getWidth()/2+40, getWidth()/2+20, getWidth()/2+60, getWidth()/2+20, getWidth()/2};
int[] treeY = {getHeight()/2-120, getHeight()/2-80, getHeight()/2-40, getHeight()/2-40,
getHeight()/2-20, getHeight()/2, getHeight()/2, getHeight()/2+40, getHeight()/2+60,
getHeight()/2+60, getHeight()/2+40, getHeight()/2, getHeight()/2, getHeight()/2-20,
getHeight()/2-40, getHeight()/2-40, getHeight()/2-80, getHeight()/2-120};
g.setColor(Color.GREEN);
g.fillPolygon(treeX, treeY, treeX.length);
g.setColor(new Color(102, 51, 0));
g.fillRect(getWidth()/2-20, getHeight()/2+60, 40, 40);
}
}
package christmasfinal;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import java.math.*;
public class SnowBackgroundPanel extends JPanel{
Random rand = new Random();
//Create ArrayList for SnowParticles
ArrayList<SnowParticle> snow = new ArrayList();
//Create animation timer
Timer timer = new Timer(7, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
public SnowBackgroundPanel(int width, int height){
setSize(width, height);
System.out.println(getHeight() + " " + getWidth());
for(int i=0; i<50; i++){
snow.add(new SnowParticle());
snow.get(i).x = rand.nextInt(getWidth()+1);
snow.get(i).y = 0-rand.nextInt(getHeight()+1);
}
timer.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.WHITE);
//Set, paint, and move snow particles
for(int i=0; i<50; i++){
g.fillOval(snow.get(i).x, snow.get(i).y, 10, 10);
snow.get(i).y += 1;
if(snow.get(i).y > getHeight()){
snow.get(i).x = rand.nextInt(getWidth()+1);
snow.get(i).y = 0-rand.nextInt(getHeight()+1);
}
}
}
}
You're adding your TreePanel to the BorderLayout.CENTER position of the SnowPanel which will effectively cover up the SnowPanel, so it should be expected to cover SnowPanel's graphics and animation. Likely setting TreePanel to be non-opaque by calling setOpaque(false) on it will allow you to see through it:
TreePanel treePanel = new TreePanel();
treePanel.setOpaque(false);
Other issues with your code:
Why have ChristmasFinal extend JApplet when it has no applet code nor behaviors?
Avoid calling setSize(...) on anything as that is a dangerous thing to do.
Better to let the components size themselves based on their layout managers and preferredSizes.
If you absolutely need to set a size, better to override getPreferredSize()
You should avoid having program logic within a paintComponent(...) method since you never have complete control over when or even if paintComponent(...) gets called.
I suggest an entirely different approach:
SnowBackgroundPanel & TreePanel which extend JPanel should instead implement Drawable
The Drawable interface will have a single method draw(Graphics2D g, Dimension sizeOfParent)
In the draw() method, put what was in paintComponent()
In the single area designated for custom painting, (e.g. RenderingSurface extends JPanel) keep a collection of the drawn elements in a list that respects their order. e.g. if the first element is the BG, it is rendered first.
In the paintComponent() of RenderingSurface, iterate the collection of drawable elements and draw each one.

Categories

Resources