I've created a custom swing component. I can see it (the grid from the paint method is drawn), but the buttons that are added (verified by println) aren't shown. What am I doing wrong?
Background information: I'm trying to build a tree of visible objects like the Flash/AS3 display list.
public class MapPanel extends JComponent { // or extends JPanel, same effect
private static final long serialVersionUID = 4844990579260312742L;
public MapPanel(ShapeMap map) {
setBackground(Color.LIGHT_GRAY);
setPreferredSize(new Dimension(1000,1000));
setLayout(null);
for (Layer l : map.getLayers()) {
// LayerView layerView = new LayerView(l);
// add(layerView);
System.out.println(l);
JButton test = new JButton(l.getName());
add(test);
validate();
}
}
#Override
protected void paintComponent(Graphics g) {
// necessary?
super.paintComponent(g);
// background
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
// grid
g.setColor(Color.GRAY);
for (double x = 0; x < getWidth(); x += 10) {
g.drawLine((int)x, 0, (int)x, getHeight());
}
for (double y = 0; y < getHeight(); y += 10) {
g.drawLine(0, (int)y, getWidth(), (int)y);
}
}
}
Setting null as the layout manager and then adding buttons will not have any effect. A layout manager is responsible for computing the bounds of the children components, and setting layout manager to null effectively leaves all your buttons with bounds = (0,0,0,0).
Try calling test.setBounds(10, 10, 50, 20) as a quick test to see if the buttons appear. If they do, they will be shown at exactly the same spot. From there you can either install a custom layout manager that gives each button the required bounds, or use one of the core / third party layout managers.
It would be easier for us to diagnose your problem if you gave us a SSCCE. As it stands, we may not have enough information to fix your problem.
I can see it (the grid from the paint
method is drawn),
I don't know what that means, there is no paint() method in the posted code. (But I suppose it is easy enough to assume that you meant paintComponent(g))
However, it looks like the problem is that you are uisng a "null layout". The children will not paint unless you manually set the size and location of the children.
You should probably read a quick tutorial on LayoutManagers. It may make things easier for you when drawing components.
Related
I'm pretty new to Java and the GUI world. Right now I'm trying to create a really basic space shooter. To create it I started creating a JFrame, in which I've later on put a personal extension of a JPanel called GamePanel, on which I'm now trying to display all my components. Until here it's all pretty clear, the problem comes now: I have my GamePanel in which I display my player, and on the KeyEvent of pressing S the player should shoot the Bullets. I've managed the bullets as an Array, called Shooter[], of Bullet Objects, created by myself this way:
public class Bullet implements ActionListener{
Timer Time = new Timer(20, this);
private int BulletY = 430;
public int PlayerX;
public Rectangle Bound = new Rectangle();
public Bullet(int playerx) {
this.PlayerX = playerx;
Time.start();
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillRect(PlayerX + 2, BulletY, 3, 10);
g.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
if (Time.isRunning()) {
BulletY = BulletY - 5;
Bound = new Rectangle (PlayerX + 2, BulletY, 3, 10);
}
}
}
I thought that calling the draw method in the GamePanel's paint() method would have allowed me to display both all the bullets shot and the player. What actually happens is that at the start it seems allright, but when I press S the player disappears and just one bullet is shot. Can you explain me why? This is how my paint() method looks like:
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, 500, 500);
for(int i = 0; i < BulletsCounter; i++) {
Shooter[i].draw(g);
}
g.setColor(Color.RED);
g.fillRect(PlayerX, PlayerY, 20, 20);
//System.out.println("Here I should have painted the player...");
g.dispose();
}
BulletsCounter is a counter I've created to avoid any NullPointerExceptions in painting the whole array, it increases when S is pressed and so another bullet of the array is initialized and shot.
Thank you for your patience, I'm new to the site, so warn me for any mistake.
You've several significant problems, the biggest given first:
You're disposing a Graphics object given to you by the JVM. Never do this as this will break the painting chain. Instead, only dispose of a Graphics object that you yourself have created.
You're drawing within paint which is not good for several reasons, but especially bad for animation since you don't have automatic double buffering of the image
You don't call the super painting method within your override and thus don't allow the JPanel to do house-keeping painting.
Recommendations:
Don't dispose of the Graphics object, not unless you, yourself, create it, for example if you extract one from a BufferedImage.
Override the JPanel's paintComponent method, not its paint method to give you double buffering and smoother animation.
And call super.paintComponent(g) first thing in your override to allow for housekeeping painting
I'm doing a project for school in witch I have to simulate an ant colony and also show it in a graphics user interface.
The whole project is almost complete but I neet to implement a zoom feature for my jPanel.
I've found a thread on this site with basically what I need.Here's the link:
Zooming in and zooming out within a panel
What Thanasis made in that thread is what I basically need but I have no Idea how to implement it inside my code with the other classes.
I am a newbie in Graphic User Interface and we are basically learning and understanding it by doing this project so forgive me if the answer is Super easy and I'm asking for the answer.
I can provide code for the Pannel and Window classes.
I've allready tried launching it without anything thinking that it will work directly on my jpanel but it didn't of course.also tried to call it in my main but that didn't work either. Here's my paintComponent from my panel . I basically do this for everything that shows(ants, colony, food).
public void paintComponent(Graphics g){
int tailletab = this.gri.getTab().length;
//On récupère le tableau de la Grille
int[][] gril = this.gri.getTab();
int taillecarre;
int xcol = this.colo.getPos().getX();
int ycol = this.colo.getPos().getY();
int xsou = this.source.getPos().getX();
int ysou = this.source.getPos().getY();
if(tailletab<=50){
taillecarre = tailletab/4+2;
}else{
if(tailletab<60){
taillecarre = tailletab/5+1;
}else{
if(tailletab<70){
taillecarre = tailletab/7+1;
}else{
if(tailletab<80){
taillecarre = tailletab/8;
}else{
if(tailletab<90){
taillecarre = tailletab/10;
}else{
taillecarre = tailletab/13;
}
}
}
}
}
for(int i=0; i<tailletab; i++){
for(int j=0; j<tailletab; j++){
if(gril[j][i]==0){
if(j==xcol && i==ycol){
g.setColor(new Color(102, 51, 0));
g.fillRect(xcol*taillecarre, ycol*taillecarre,taillecarre,taillecarre);
g.setColor(Color.BLACK);
g.drawRect(xcol*taillecarre, ycol*taillecarre,taillecarre,taillecarre);
}else{
if(j==xsou && i==ysou){
g.setColor(Color.RED);
g.fillRect(xsou*taillecarre, ysou*taillecarre,taillecarre,taillecarre);
g.setColor(Color.BLACK);
g.drawRect(xsou*taillecarre, ysou*taillecarre,taillecarre,taillecarre);
}else{
g.setColor(Color.BLACK);
g.drawRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
}
}
}else{
g.setColor(Color.BLACK);
g.drawRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
g.fillRect(j*taillecarre, i*taillecarre, taillecarre, taillecarre);
}
}
}
}
The answer of Andreas Holstenson in the link you provided is better than Thanasis' because you shouldn't override paint but paintComponent as you correctly did, and Thanasis doesn't overwrite the transformation but tries to be dumb-clever about not progressively updating the transform. If you lost me, just forget that answer altogether.
Otherwise, the choice of whether to use AffineTransform or not does not matter as the result is the same. Arguably, AffineTransform is easier to use.
To answer your question, put the additional scale/translate code at the top of that method and all graphics drawn after that should be zoomed (even if if you use g instead of g2).
#Override
protected void paintComponent(Graphics g) { // No need to widen it to public
Graphics2D g2 = (Graphics2D)g;
AffineTransform oldTransform = g2.getTransform();
try {
float zoom = 0.5f; // shrink
// Note that transforms are in reverse order
// because of how matrix multiplications work.
AffineTransform newTransform = new AffineTransform();
// 2nd transform: re-center
newTransform.translate(getWidth() * (1 - zoom) / 2,
getHeight() * (1 - zoom) / 2);
// 1st transform: zoom (relative to upper-left corner)
newTransform.scale(zoom, zoom);
g2.transform(newTransform);
// Draw here
} finally {
// Try-finally is probably not required, but ensures that the transform
// gets restored even if an exception is thrown during drawing.
g2.setTransform(oldTransform);
}
I have been working on a basic Java Swing application, that paints some objects into a opaque JPanel. I have been coding this app on a University MAC-PC. Yesterday I tested the program on my macbook air(after exporting the project), the behaviour of the application totally change.
The logic of the application is as follows, a Jframe that contains a JLayeredPaneL with Jpanel, in each Jpanel I paint some objects.The application is working correctly on the University laptop.
1)The JPanel is not longer transparent
//Creating Layered Panel
JLayeredPane lpane = new JLayeredPane();
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
this.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
//creating Jpanel
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
this.setBackground(new Color(0, 255, 0, 0));//makes JFrame invisible
this.setVisible(true);
this.setResizable(false);
2) The JPanel does not remember what is previously drawn(it actually creates two Jpanel one what is being drawn at the moment and what was previously drawn)
I paint lines, whenever a new line is added to an array I call paintAgain(), here is the code of the paintComponent
public void paintLines(Point p)
{
arrayLines.add(p);
repaint();
//Only the point is displayed the other points are not visible,
//the other points are in another JPANEL?
}
public void delete()
{
delete = true;
arrayLines.clear();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(delete)
{
g.clearRect(0,0,screenSize.width,screenSize.height);
delete =false;
}
else
{
g2d = (Graphics2D) g.create();
g2d.setStroke(new BasicStroke(10));
g2d.setColor(pickedColor);
//It just paints the new lines, it does not iterate through all the points
g2d.draw(new Line2D.Float(arrayLines.get(arraySize-1).getx1(), arrayLines.get(arraySize-1).gety1(),arrayLines.get(arraySize-1).getx2(),arrayLines.get(arraySize-1).gety2()));
}
}
I do not know why the behaviour of the program changes. Maybe the JRE version that i'm using?I have literally no idea since, it has never happened before
Thank you in advance
In your paintComponent method, your code as written just draws one line, and so one line is all that you see. Instead you need to use a for loop to iterate through your Point collection, arrayLines, drawing a line between points.
// note that i *must* start at 1, not at 0
for (int i = 1; i < arrayLines.size(); i++) {
int x1 = arrayLines.get(i - 1).x;
int y1 = arrayLines.get(i - 1).y;
int x2 = arrayLines.get(i).x;
int y2 = arrayLines.get(i).y;
g2.drawLine(x1, y1, x2, y2);
}
Also, where do you set delete to true ever?
Edit
Regarding your comments:
...The paint method is an example, I call paintLines() several times.
This won't matter if paintComponent draws one and only one line.
Do I have to repaint everything , everytime i make a modification in the array?
At this point, probably, yes. Later consider doing your static background drawing to a BufferedImage and then displaying that in the paintComponent almost first thing, right after the super.paintComponent(g) call, and then drawing your non-static, your moving sprites directly in paintComponent. If you know for a fact that you've only altered a portion of a component, you can call one of the repaint(...) overload methods that suggest repainting a rectangular area of the component.
The problem is this code is working in another computer and in mine it misbehaves.
The issue for me is that I have no idea why the code would have a prayer of working on another system since it is broken code.
One of the other issues you are having is the fact that you are using an alpha based color as the background to an opaque component...
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
Swing only knows how to paint opaque or transparent components and makes these decisions based on the opaque state of the components.
When transparent, the API knows that it must first prepare the graphics context properly and secondly, paint all components that might be beneath this one.
When using an alpha based background color, the component is unable to clear the Graphics context for painting (as filling with a transparent color doesn't do anything), this tends to mean that the Graphics context still contains what ever was painted to it previously (as the Graphics context is a shared resource).
Instead, remove...
myGlassPane.setBackground(new Color(0, 255, 0, 0));
and use
myGlassPane.setOpaque(false);
which will give you the same effect.
It will also mean you won't need
g.clearRect(0,0,screenSize.width,screenSize.height);
and can simply remove the all the elements from the arrayLines instead, which will give you the same effect...just longer lasting...
Ok dear folks, i've got this question and i don't really know a certain way to solve it.
I'm doing like a "Paint application" in java, i know everything is ready, but I need to paint the shapes with Computer Graphics Algorithms.
So, the thing is, once the shape is painted in the container how could I convert it like sort of an "Object" to be able to select the shape and move it around (I have to move it with another algorithm) I just want to know how could I know that some random point clicked in the screen belongs to an object, knowing that, I would be able to fill it(with algorithm).
I was thinking that having a Point class, and a shape class, if i click on the screen, get the coordinates and look within all the shapes and their points, but this may not be very efficient.
Any ideas guys ?
Thanks for the help.
Here is some of my code:
public class Windows extends JFrame{
private JPanel panel;
private JLabel etiqueta,etiqueta2;
public Windows() {
initcomp();
}
public void initcomp()
{
panel = new JPanel();
panel.setBounds(50, 50, 300, 300);
etiqueta = new JLabel("Circulo Trigonometrico");
etiqueta.setBounds(20, 40, 200, 30);
etiqueta2 = new JLabel("Circulo Bresenham");
etiqueta2.setBounds(150, 110, 200, 30);
panel.setLayout(null);
panel.add(etiqueta);
panel.add(etiqueta2);
panel.setBackground(Color.gray);
this.add(panel);
this.setLayout(null);
this.setVisible(true);
this.setSize(400,400);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.red);
g2d.setStroke(new BasicStroke(2));
dibujarCirculo_bresenham(g2d, 50, 260, 260);
dibujarCirculo_trigonometrico(g2d, 50, 130, 200);
}
/*This functions paints a Circle*/
public void dibujarCirculo_trigonometrico(Graphics g,int R,int xc,int yc)
{
int x,y;
for (int i = 0; i < 180; i++) {
double angulo = Math.toRadians(i);
x = (int) (Math.cos(angulo)*R);
y = (int) (Math.sin(angulo)*R);
g.drawLine(x+xc, y+yc, x+xc, y+yc);
g.drawLine((-x+xc), (-y+yc), (-x+xc), (-y+yc));
}
}
I assume that any image is a valid (isn't constrained to a particular set of shapes). To get an contiguous area with similar properties, try using a flood fill.
To colour in or move a particular shape around, you can use flood fill to determine the set of pixels and manipulate the set accordingly. You can set a tolerance for similar hue, etc so that it's not as rigid as in Paint, and becomes more like the magic selection tool in Photoshop.
There are a couple of approaches to take here depending on what precisely you want.
1) is to have objects, one for each drawn thing on screen, with classes like Circle and Rectangle and Polygon so on. They would define methods like paint (how to draw them on screen), isCLickInsideOf (is a click at this point on screen contained by this shape, given size/position/etc?) and so on. Then, to redraw the screen draw each object, and to test if an object is being clicked on ask each object what it thinks.
2) is, if objects have the property of being uniform in colour, you can grab all pixels that make up a shape when the user clicks on one of the pixels by using a floodfill algorithm. Then you can load these into some kind of data structure, move them around as the user moves the mouse around, etc. Also, if every object is guaranteed to have a unique colour, you can test which object is being clicked on by just looking at colour. (Libraries like OpenGL use a trick like this sometimes to determine what object you have clicked on - drawing each object as a flat colour on a hidden frame and testing what pixel colour under the mouse pointer is)
I'm using the Java Tutorials example of how to use a JScrollPane (with row/column headers). The example is using a subclass of JLabel to display an image in the Viewport View. I used the sample code for displaying the row/column headers (Rule.java example code) and was perplexed at the bizarre results. I finally removed the call to getClipBounds() (apparently used to determine what region of the row/column header is visible to paint only that region) and painted the entire header, and the problem was resolved. That means that I'm now drawing the entire area (in both the row/column headers and the main Viewport). That strikes me as non-optimal.
Can anyone explain why the Java Tutorials example works properly (other than the source is not the same as that being executed in the example)?
Is it correct for me to be painting the entire pane even though it is only partially visible?
How can I determine what region of the overall object is visible in the Viewport (for row/column headers and the main Viewport) so I can just paint that region?
UPDATE:
I still don't know why the example works, but I've found that if I use JComponent.getVisibleRect() instead of Graphics.getClipBounds() things seem to work as expected. Not sure if this is the correct use of this method.
Look at this code below. I was just painting visible part.
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle view = new Rectangle();
if (getParent() instanceof JViewport) {
JViewport vp = (JViewport) getParent();
view = vp.getViewRect();
} else {
view = new Rectangle(0, 0, getWidth(), getHeight());
}
g2d.setColor(getBackground());
g2d.fillRect((int) view.getX(), (int) view.getY(), (int) view.getWidth(), (int) view.getHeight());
g2d.setColor(Color.YELLOW);
double x = view.getX();
double y = view.getY();
double w = view.getWidth();
double h = view.getHeight();
// draw Strings
for (StringShape ss : stringList) {
Rectangle sb = ss.getRectangle(g2d.getFontMetrics(ss.getFont()));
if (containShape(view, sb)) {
g2d.setFont(ss.getFont());
g2d.setColor(ss.getColor());
g2d.drawString(ss.getString(), (int) sb.getX(), (int) sb.getY());
}
}
}
JComponent.getVisibleRect() was the trick. Clearly I misunderstand the meaning/use of getClipBounds().