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.
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.
Disclaimer: I'm new to Java. I'm new to Swing. And I'm sure it shows.
I've viewed quite a number of examples/tutorials of how to draw on a jpanel "canvas". But they mostly have the same basic format and put all of their drawLine/drawRect/drawArc inside the paintComponent() method. It seems assumed that people want to draw static things to the jpanel one time. But what if I want to change the jpanel object over the course of the program's runtime, like a paint program, or a game?
I suppose I need to be able to access the jpanel object, and internal methods to paint. I'm betting what I'm doing isn't best practices, but here is what I have:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PaintPanel extends JPanel {
public static JFrame frame;
private Graphics g = getGraphics();
public static void main(String[] args) {
frame = new JFrame();
frame.getContentPane().setBackground(new Color(32, 32, 32));
frame.setResizable(false);
frame.setBounds(1, 1, 800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.setVisible(true);
frame.setLocationRelativeTo(null); // center frame on screen
PaintPanel paintPanel = new PaintPanel();
paintPanel.setBounds(10, 10, 100, 100);
paintPanel.setBackground(Color.red);
frame.add(paintPanel);
}
// constructor
public PaintPanel() {
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
public void DrawRect(Integer x, Integer y, Integer w, Integer h, Color color) {
g.setColor(color);
g.fillRect(x, y, w, h);
this.repaint(); // doesn't seem to do anything
}
}
This code results in a red panel box, but my user method DrawRect() doesn't draw anything.
I've read in some places that it's necessary to override the paintComponent() method. If there's nothing in it, what's the purpose?
How can I get my DrawRect() method to work?
The piece of the puzzle you're missing is the model object. There should be an external object that describes what should be drawn. In a game for example, it would be something that describes the current state of the game.
Your custom component looks at this model and takes the necessary steps to paint it. This is implemented in paintComponent and in helper methods you see fit to add.
To make an animation, you make a loop that modifies the model over time, and asks the custom component to redraw itself with repaint().
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.
I have a custom JPanel. The only thing that is in it, is a drawing of a rectangle, using the drawRect method of the Graphics object. The JPanel is always in a very specific square size, refuses to get any bigger or smaller. Tried overriding the getPreferredSize() method, didn't work.
Tried setting different layout managers for this custom JPanel, and also tried every layout manager for the JPanel that hosts this JPanel. Still, the size of the custom JPanel stays the same.
As I said, the custom JPanel has no components in it, only a drawing of a rectangle.
Any ideas?
Without knowing more about what you're trying to achieve:
As far as your containing panel, you need to know which layout managers respect preferred sizes and which ones don't
Grid Flow Border Box GridBag
Respect PreferredSize NO YES NO YES YES
That being said, if you wrap the painted JPanel in a JPanel with one of the "NOs", the painted JPanel shoud stretch with the resizing of the frame.
Also if you want the drawn rectangle to stretch along with its JPanel, then you need to remember to draw the rectangle with getWidth() and getHeight() of the JPanel and not use hard coded values.
Here is an example using BorderLayout as the containing panel's layout, and making use of getWidth() and getHeight() when performing the painting.
import java.awt.*;
import javax.swing.*;
public class StretchRect {
public StretchRect() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new RectanglePanel());
JFrame frame = new JFrame();
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public class RectanglePanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect( (int)(getWidth() * 0.1), (int)(getHeight() * 0.1),
(int)(getWidth() * 0.8), (int)(getHeight() * 0.8) );
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new StretchRect();
}
});
}
}
I'm designing an optimization system for public transport in a big city. So I have a map with some points on it, but don't care about it)
All I need is: my own JButton, which looks like a color-filled circle and a small text tag near it. I got some problems while overriding the paintComponent() method.. the round button is painted correctly, but not the text.
BUT, when i'm resizing the window manually, the text appears for a second, then it gets repainted again and dissapears.
Hope you guys understood my needs, thanks for help ;)
import java.awt.*;
import javax.swing.*;
public class JRoundButton extends JButton {
String label;
Color color;
int x,y;
public JRoundButton(Color color,int x,int y,String str)
{
label=str;
this.x=x;
this.y=y;
this.color=color;
}
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Dimension size = getPreferredSize();
setPreferredSize(size);
this.setBounds(0, 0, 10, 10);
setContentAreaFilled(false);
g.setFont(new Font("Arial",Font.BOLD,14));
g.drawChars(label.toCharArray(), 0, label.length(), 12,12);
g.fillOval(0,0,8,8);
}
public void paintBorder(Graphics g)
{
g.setColor(Color.white);
g.drawOval(0,0, 9, 9);
}
public static void main(String[] args)
{
JButton button = new JRoundButton(Color.GRAY,150,150,"Times Square");
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.black);
frame.setSize(300, 300);
frame.setVisible(true);
frame.add(button);
}
}
Seems that the call to 'setBounds( 0, 0, 10, 10 )' sets a component footprint that is too small to accomodate the text string. Extending the bounds to 100px wide and bringing down the point size to 6 looks to work okay.
1) NEVER set properties of the button in the paintComponent() method.
Dimension size = getPreferredSize();
setPreferredSize(size);
this.setBounds(0, 0, 10, 10);
setContentAreaFilled(false);
Get rid of the above code.
2) Dont set the Font of the Graphics object in the paintComponent() method. Thats what the setFont(...) method is used for.
3) There is no need to do any custom painting. If you want a circle, then add an Icon to the JLabel.
4) Don't override the paintBorder() method. If you want a Border then create a custom border and add it to the button using the setBorder() method.
In short there is no need to extend the button. Get rid of your JRoundButton class. Your code should simply look something like:
JButton = new JButton("Times Square");
button.setFont( new Font("Arial",Font.BOLD,14) );
button.setIcon( new OvalIcon(Color.WHITE, iconSize) );
Of course you will need to create an OvalIcon class but that is easy to implement since there are only three methods and you already know what the painting code should be.
I'd just cheat and use a unicode circle in the JButton's text. E.g.:
import javax.swing.*;
JFrame frame = new JFrame();
frame.getContentPane().add(new JButton("<html><font size='+10' color='red'>●</font> I'm next to a red circle!</html>"));
frame.pack();
frame.show();