This is my first project with AWT/Swing. I'm trying to design a simple cellular automaton. I had some problems choosing a layout manager, now I'm using GridLayout because is the closest I got to what I want. However, when a try to place a cell in the JPanel, the coordinates do not work as I expected. Maybe I should not be extending from JComponent and using fillRect()? Or maybe GridLayout is not the layout I need? The main problem is that the point (0,0) seems to be "moving". Is fillRect conflicting with GridLayout?
Note 1: I've tried GridBagLayout but did not work (because I have no idea how to configure it). I've also tried the add(component, x, y) method but it did not work.
Note 2: I did not post the code regarding the State of the Cell because it was not relevant.
Edit: Ok, I wrote an example in a single public class, I don't think I can be more concise and reproduce the same results.
Solution: https://docs.oracle.com/javase/tutorial/uiswing/painting/refining.html
This is my code:
public class Example{
class Cell extends JComponent{
private int x = 0; //Cell position ?
private int y = 0;
public Cell(int x, int y){
this.x = x;
this.y = y;
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
//draw cell
g.setColor(Color.white);
g.fillRect(x,y,15,15);
}
}
Example(){
JFrame frame = new JFrame("title");
frame.setBackground(Color.black);
frame.getContentPane().setPreferredSize(new Dimension(300,300));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
JPanel box = new JPanel(new GridLayout(20,20)){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
setBackground(Color.black);
//draw grid
for(int i = 0; i <= this.getHeight(); i += 15){
g.drawLine(0,0+i,getWidth(),0+i);
}
for(int i = 0; i <= this.getWidth(); i += 15){
g.drawLine(0+i,0,0+i,getHeight());
}
}
};
/*box.add(new Cell(0,0)); //TEST 1
box.add(new Cell(0,0));
box.add(new Cell(0,0));
box.add(new Cell(0,0));*/
box.add(new Cell(0,0)); //TEST 2
box.add(new Cell(15,0));
box.add(new Cell(30,0));
box.add(new Cell(45,0));
frame.add(box);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args){
new Example();
}
}
And this are the results corresponding to TEST 1 and TEST 2:
TEST 1
TEST 2
All painting is done relative to the component that contains the custom painting, not the panel you add the component to.
So in your case just do the painting from (0, 0).
The layout manager will position the Cell at the location determined by the layout manager.
Note:
A painting method is for painting only. It should NEVER create a component, as your current Box class is doing.
The basic logic is to:
create your panel with the desired layout.
add components to the panel.
the size/location of the components added to the panel will be determined by the layout manager. So in your Cell class you need to implement the getPreferredSize() method so the layout manager can use this information to position each component that is added to the panel.
If you want to manage the painting at different locations on the panel, then don't use real components. Instead you keep an ArrayList of shapes that you want to paint. Each shape will contain the location it should be painted. Then you iterate through the ArrayList in the paintComponent() method to paint each shape. For an example of this approach check out the Draw On Component example found in Custom Painting Approaches.
Related
So I have made a chess game where Inside the JFrame there is a boardPanel (chessboard) and a sidePanel (contains buttons like flipBoardButton).
The whole JFrame has a Dimension of 900x640 and the boardPanel therefore is 640x640.
public class BoardPanel extends JPanel{
private static final long serialVersionUID = 1L;
public BoardPanel() {
this.setSize(640, 640);
this.setLocation(0, 0);
}
#Override
public void paint(Graphics g){
boolean white = true;
for(int y = 0; y < 8; y++){
for(int x = 0; x < 8; x++){
if(white){
g.setColor(new Color(235, 235, 235));
}
else{
g.setColor(new Color(166, 123, 90));
}
g.fillRect(x*80, y*80, 80, 80);
white = !white;
}
white = !white;
}
for(Piece p : BoardHandler.piecesOnBoard)
{
Image image;
image = p.getImage();
g.drawImage(image, p.getX(), p.getY(), this);
}
}
}
My SidePanel should be on the right (at x=641 y=0) and have a width of 900-640=260 and a height of 640...
public class SidePanel extends JPanel{
public SidePanel()
{
this.setSize(260,640);
this.setLocation(641, 0);
}
#Override
public void paint(Graphics g) {
g.setColor(new Color(50,50,50));
g.fillRect(this.getX(), this.getY(), this.getWidth(), this.getHeight());
}
}
This is how I implemented both the panels (usual stuff):
JFrame frame = new JFrame("Chess");
BoardPanel boardPanel = new BoardPanel();
SidePanel sidePanel = new SidePanel();
frame.add(boardPanel);
frame.add(sidePanel);
frame.setVisible(true);
And I thought everything is working because this is what I got:
BUT: when I go into SidePanel calss and change the setSize to 100x100 or when I set the Location to 700,0 , I get the SAME result !
However, when I change Location / Size in the BoardPanel class, it works perfectly fine ?! (the chessboard then gets rearranged/resized)
How is this possible when I used the "same" code for both classes !?
The whole JFrame has a Dimension of 900x640 and the boardPanel therefore is 640x640.
The frame and boardPanel can't possibly have the same height because the frame has a border and a titlebar. Don't attempt to set the size/location of components. That is the job of layout managers.
Many issues:
Swing components are responsible for determining their own size. So when you do custom painting you need to implement the getPreferredSize() method so the layout manager can do its job.
For the boardPanel is would be something like:
#Override
public Dimension getPreferredSize()
{
return new Dimension(640, 640);
}
By default a JFrame uses a BorderLayout. You do NOT specify a constraint when you add your components to the frame, so be default each component is added to the CENTER. However, only the last component is managed by the BorderLayout so it will set the size/location of the sidePanel, which is why your attempt to do so is ignored.
Because the BorderLayout ignores the chessBoard your attempt to set the size/location appears to work.
However you should not attempt to set the size/location. Let the layout manager do its job.
Instead your code should be:
frame.add(boardPanel, BorderLayout.CENTER);
frame.add(sidePanel, BorderLayout.LINE_START);
Custom painting is done by overriding the paintComponent(...) method, not paint() and you always invoke super.paintComponent(...) first to make sure the background of the panel is cleared.
Custom painting is relative to the component, not relative to its location in the frame.
The following code in your sidePanel class is wrong:
g.fillRect(this.getX(), this.getY(), this.getWidth(), this.getHeight());
The getX()/getY() is wrong. If you really need to do custom painting then you should just use (0, 0).
However, there is no need to even use custom painting.
In the constructor of your class you just use:
setBackground( new Color(...) );
and the background will be painted automatically.
I'm currently trying to create a program that moves a rectangle over a background Image with keyboard keys. The problem I'm facing is that when I draw the components they are simply placed next to each other, instead of the square overlaying the background image. Here's the code to display both the components;
JLayeredPane panel = new JLayeredPane();
panel.setLayout(new FlowLayout());
add(panel);
paintBackground pb = new paintBackground(bimg);
panel.add(pb, 1, 0);
paintPlayer cc = new paintPlayer(startX, startY);
panel.add(cc, 2, 0);
pack();
setVisible(true);
I believe the problem is that the paintPlayer component is set to full size, and there seems to be a background. The paintPlayer component code looks like this:
public Dimension getMinimumSize() {
return new Dimension(800,600);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800,600);
}
#Override
public Dimension getMaximumSize() {
return new Dimension(800,600);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
System.out.println(startX + startY );
g.fillRect(startX, startY, 30, 30);
}
I've had a go at setting the component size to just the size of the rectangle, but that way I can't move the rectangle by using the first two values in fillRect. The background for the rest of the space filled by the component (800x600) seems to be opaque.
When added, the components just display next to each other, like this: https://gyazo.com/57245c518e02778c36ffc89ba75d5a81. How do I go about adding the paintPlayer ontop of the paintBackground, so that it only covers the rectangle on the background Image.
I've done a fair bit of searching but I can't seem to work it out. Perhaps something to do with the layout? One other thing I've noticed is that by doing this, neither the frame or the pane benefit from a setBackground, as it's not visible.
Cheers for any help.
This is the default Constructor of JLayerdPane.
public JLayeredPane() {
setLayout(null);
}
You see it uses normaly AbsolutLayout. And if you read here:
Note: that these layers are simply a logical construct and LayoutManagers will affect all child components of this container without regard for layer settings.
You should understand what is wrong. Check OverlapLayout.
I want to draw some different JComponents in one JPanel. I create some JComponents with different paint methods. Then create the objects in the main and put them to the JFrame.
My problem is, that only the last Object is painted in the Window.
How can I put different JComponents in the window, without remove or repaint the old ones?
(Model2 works like Model1, but the paintComponent is a little bit different)
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GuiModel{
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setSize(600, 600);
frame.setLocation(150, 150);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.getContentPane().add(new Model1(0,0));
frame.getContentPane().add(new Model2(25,37,true));
}
}
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
public class Model1 extends JComponent {
private int xPos, yPos;
Model1 (int x, int y){
this.xPos = x;
this.yPos = y;
}
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
g.drawOval(xPos, yPos, 5, 5);
}
}
When doing custom painting:
You need to override the getPreferredSize() method of your component to return the size of the component so a layout manager can display the component. Right now the size of your components are (0, 0) so there is nothing to paint.
The painting of a component is done from (0, 0), not (x, y). Then if you position the component at a specific point on the panel you use setLocation(x, y) to tell Swing where to painting the component.
If you want to paint the component at a random position then you also need to use a null layout on the panel and you must also set the size of the component. To set the size of the component you would use setSize( getPreferredSize()) in your constructor.
So your Model1 class would look something like:
public class Model1 extends JComponent {
//private int xPos, yPos;
Model1 (int x, int y){
//this.xPos = x;
//this.yPos = y;
setLocation(x, y);
setSize( getPreferredSize() );
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(5, 5);
}
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
//g.drawOval(xPos, yPos, 5, 5);
g.drawOval(0, 0, 5, 5);
}
}
JFrame uses BorderLayout by default. When adding components without specifying the constraints, each component will be placed at CENTER.
Either specify the constraints, or if BorderLayout isn't suffice for you, switch to another layout.
To add constraints:
frame.add(new Model1(0,0), BorderLayout.NORTH);
I suggest reading How to use BorderLayout, as well as guides on the other predefined layouts. Also, feel free to look online for 3rd party layouts, if none of the layouts available through the JDK fit your needs. Or you could create your own LayoutManager if no layouts exist that fit your needs
I also recommend using pack() to size your frame based on the components inside instead of setting it's size with frame.setSize. This ensures your frame tightly wraps around anything inside of it, leaving no empty spaces. If you want empty spaces, it should be handled by the layout manager
You can specify size for components or use Layouts to place objects not in one place. Or you can create copy of Graphics and write in it:
protected void paintComponent(Graphics g) {
g = g.create();
g.setColor(Color.BLACK);
g.drawOval(xPos, yPos, 5, 5);
g.dispose();
}
But the last variant is not efficient: it makes copy of Graphics on each repaint.
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));
}
I have a question. I want to make a swing form that, when clicking in a button he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect.
I Have tried to do a while how checks the size of the panel and then minimize it and shows the next one like this :
while (jpanelprincipal1.getWidth() < 439 || jpanelprincipal1.getHeight() > 250)
{
int panel1width = jpanelprincipal1.getWidth();
int panel2height = jpanelprincipal1.getHeight();
jpanelprincipal1.setSize(panel1width -- , panel2height --);
jpanelprincipal2.setSize(440,250);
}
I used this trick in C# but with the Application.DoEvent(); (how obviously it's not available on java).
Is there anyway i can make a slide effect of 2 or more panels?
BTW : Sorry for my very bad english !
Thanks In Advance,
Luis Da Costa
he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect
You question mentions you want the panel to "slide", but the code looks like you are trying to get the panel to "shrink", so it is replaced by another panel.
Assuming you have two panels each with the same size, then you can "slide" one out of view while the other slides into view.
To do this you an use a panel with a GridLayout. This way each component will be the same size. Then you add the panel to a scrollpane without any scrollbars. The size of the scrollpane will need to be set to the size of the first compnoent. Then you can "slide" the two panels by changing the position of the viewport. So in your Timer you would have code something like:
JViewport viewport = scrollPane.getViewport();
Point position = viewport.getViewPosition();
position.x += 5;
viewport.setViewPosition( position );
You would then stop the Timer when the position is greater than the size of the component.
As suggested by #HFOE, javax.swing.Timer is a good choice for animation. The setDividerLocation() method of JSplitPane can be called from the ActionListener. See How to Use Split Panes for additional options.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
/** #see http://stackoverflow.com/questions/5069152 */
public class SplitPaneTest {
double ratio = 0.5;
double delta = ratio / 10;
private void create() {
JFrame f = new JFrame("JSplitPane");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyPanel p1 = new MyPanel(Color.red);
MyPanel p2 = new MyPanel(Color.blue);
final JSplitPane jsp = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
Timer timer = new Timer(200, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ratio += delta;
if (ratio >= 1.0) {
ratio = 1.0;
delta = -delta;
} else if (ratio <= 0) {
delta = -delta;
ratio = 0;
}
jsp.setDividerLocation(ratio);
}
});
f.add(jsp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class MyPanel extends JPanel {
Color color;
public MyPanel(Color color) {
this.color = color;
this.setPreferredSize(new Dimension(300, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new SplitPaneTest().create();
}
});
}
}
I would probably do this with a Swing Timer. Change a class field representing the x, y position of the sliding JPanel in the timer's ActionListener and then call repaint on the container holding the JPanels. A JLayeredPane could work well as the container for the sliding JPanels.
Edit 1: regarding your request for code, I think the best thing is for you to try to create a very small compilable runnable program that attempts to do this, and then post your code with an explanation of your program's behavior as an edit to your original post. Also send us a comment to notify us of your changes. Then we can inspect your code, test it, modify it, and help you mold it into a working program. This is called creating a "Short, Self Contained, Correct (Compilable), Example" or SSCCE (please check the link).