Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last month.
Improve this question
I have set up a game in Java using Swing UI.
Expected
When firing a projectile across the screen and I want the background to be constantly there
Issue
When I run the code the background appears to be fine.
But when the button to fire the projectile is pressed then the background disappears.
Code
public class ProjectileShooterTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame f = new JFrame() {
};
ImageIcon background=new ImageIcon("Background.png");
Image img=background.getImage();
Image temp=img.getScaledInstance(800,440,Image.SCALE_SMOOTH);
background=new ImageIcon(temp);
JLabel back=new JLabel(background);
back.setBounds(0,0,800,500);
f.getContentPane().setLayout(new BorderLayout());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800,500);
final ProjectileShooter projectileShooter = new ProjectileShooter();
ProjectileShooterPanel projectileShooterPanel = new ProjectileShooterPanel(projectileShooter);
projectileShooter.setPaintingComponent(projectileShooterPanel);
JPanel controlPanel = new JPanel(new GridLayout(1,0));
controlPanel.add(new JLabel(" Post-Launch Angle"));
final JSlider angleSlider = new JSlider(70, 89, 85);
angleSlider.setLayout( new FlowLayout() );
controlPanel.add(angleSlider);
f.add(back);
controlPanel.add(new JLabel(" Thrust"));
final JSlider powerSlider = new JSlider(50, 80, 60);
powerSlider.setLayout( new FlowLayout() );
controlPanel.add(powerSlider);
JButton shootButton = new JButton("Launch");
shootButton.setLayout( new FlowLayout() );
shootButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int angleDeg = angleSlider.getValue();
int power = powerSlider.getValue();
projectileShooter.setAngle(Math.toRadians(angleDeg));
projectileShooter.setPower(power);
projectileShooter.shoot();
}
});
f.add(back);
controlPanel.add(shootButton);
f.getContentPane().add(controlPanel, BorderLayout.NORTH);
f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
f.setVisible(true);
f.setLayout(new BorderLayout());
}
}
class ProjectileShooter
{
private double angleRad = Math.toRadians(45);
private double power = 50;
private Projectile projectile;
private JComponent paintingComponent;
void setPaintingComponent(JComponent paintingComponent)
{
this.paintingComponent = paintingComponent;
}
void setAngle(double angleRad)
{
this.angleRad = angleRad;
}
void setPower(double power)
{
this.power = power;
}
void shoot()
{
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
executeShot();
}
});
t.setDaemon(true);
t.start();
}
private void executeShot()
{
if (projectile != null)
{
return;
}
projectile = new Projectile();
Point2D velocity =
AffineTransform.getRotateInstance(angleRad).
transform(new Point2D.Double(1,0), null);
velocity.setLocation(
velocity.getX() * power * 0.5,
velocity.getY() * power * 0.5);
projectile.setVelocity(velocity);
//System.out.println("Initial "+velocity);
long prevTime = System.nanoTime();
while (projectile.getPosition().getY() >= 0)
{
long currentTime = System.nanoTime();
double dt = 3 * (currentTime - prevTime) / 1e8;
projectile.performTimeStep(dt);
prevTime = currentTime;
paintingComponent.repaint();
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
projectile = null;
paintingComponent.repaint();
}
Projectile getProjectile()
{
return projectile;
}
}
Question
How can I solve this?
f.add(back);
is equivalent to:
f.getContentPane().add(back, BorderLayout.CENTER);
Later in your code you do:
f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
Which will cause problems because you can't add two components to the CENTER.
A Swing GUI is parent/child design so you need something like:
f.getContentPane().add(back, BorderLayout.CENTER);
back.setLayout(new BorderLayout());
back.add(projectileShooterPanel, BorderLayout.CENTER);
I'll let you figure out the logic order and placement of the above statements.
Also why do you have:
f.setLayout(new BorderLayout());
at the end of the constructor? This replaces all the constraint information of your original layout.
Finally, your projectile shooter panel needs to be transparent, otherwise it will paint over top of the background. So you also need:
projectileShooterPanel.setOpaque( false );
Note: instead of using the JLabel as the background it would be easier to paint the background in your projectile shooter panel. Then you won't have issues with trying to add multiple panels to one another and the shooter panel won't need to be transparent.
Edit:
You need a parent/child relationship between your components like:
- frame
- content pane
- background image
- projectile panel
- projectile
My first suggestion was to make the background a component and add the projectile panel to it:
set the layout of the background component
add the projectile panel to the background
make the projectile panel transparent so you can see the image
This is not the best solution
The second solution was to paint the background as part of the projectile panel:
paint the background image in the projectile panel
paint the projectile
This is the preferred solution.
Related
I am trying to write an application that get video frames, process them and then display them in JPanel as images. I use the OpenCV library to get video frames (one by one), then they are processed and after that displayed on the screen (to get the effect of playing video).
I created the GUI using Java Swing. A window application is created with the necessary buttons and a panel to display the video. After clicking "START", a method playVideo is called, which takes video frames from the selected video, modifies them and displays them in the panel. My code looks like this:
public class HelloApp {
private JFrame frame;
private JPanel panel;
final JLabel vidpanel1;
ImageIcon image;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
HelloApp window = new HelloApp();
window.frame.setVisible(true);
}
});
}
public void playVideo() throws InterruptedException{
Mat inFrame = new Mat();
VideoCapture camera = new VideoCapture();
camera.open(Config.filename);
while (true) {
if (!camera.read(inFrame))
break;
Imgproc.resize(inFrame, inFrame, new Size(Config.FRAME_WIDTH, Config.FRAME_HEIGHT), 0., 0., Imgproc.INTER_LINEAR);
... processing frame
ImageIcon image = new ImageIcon(Functions.Mat2bufferedImage(inFrame)); // option 0
vidpanel1.setIcon(image);
vidpanel1.repaint();
}
}
public HelloApp() {
frame = new JFrame("MULTIPLE-TARGET TRACKING");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);//new FlowLayout()
frame.setResizable(false);
frame.setBounds(50, 50, 800, 500);
frame.setLocation(
(3 / 4) * Toolkit.getDefaultToolkit().getScreenSize().width,
(3 / 4) * Toolkit.getDefaultToolkit().getScreenSize().height
);
frame.setVisible(true);
vidpanel1 = new JLabel();
panel = new JPanel();
panel.setBounds(11, 39, 593, 371);
panel.add(vidpanel1);
frame.getContentPane().add(panel);
JButton btnStart = new JButton("START / REPLAY");
btnStart.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
playVideo();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
});
}
}
I tried to delete the old panel and create a new one every time when button "START" is clicked, but it didn't work. Also I tried before running method playVideo to clean all the panel with methods:
panel.removeAll();
panel.repaint();
playVideo();
And to be honest I don't know what's wrong. The GUI is created, frames are taken and processed, but the panel displays only the last frame. I would be grateful for any advice :)
First of all, a proof it can actually work, somehow, with your code.
Here I read JPG images located in the resources folder, but it actually doesn't really matter.
Your code is a bit messy too. Where are you attaching the btnStart JButton to the outer panel? You need to understand how to layout components too.
You have a main JFrame, and a root JPanel which needs a layout. In this case we can opt for a BorderLayout.
panel = new JPanel(new BorderLayout());
Then we add our components.
panel.add(btnStart, BorderLayout.PAGE_START);
panel.add(vidpanel1, BorderLayout.CENTER);
Now coming to your issue, you say
The gui is created, frames are taken and processed, but panel display only the last frame
I don't know how much the "last frame" part is true, mostly because you're running an infinite - blocking - loop inside the Event Dispatch Thread, which will cause the GUI to freeze and become unresponsive.
In actionPerformed you should actually spawn a new Thread, and inside playVideo you should wrap
ImageIcon image = new ImageIcon(Functions.Mat2bufferedImage(inFrame));
vidpanel1.setIcon(image);
vidpanel1.repaint(); // Remove this
in EventQueue.invokeAndWait, such as
// Process frame
...
// Update GUI
EventQueue.invokeAndWait(() -> {
ImageIcon image = new ImageIcon(Functions.Mat2bufferedImage(inFrame));
vidpanel1.setIcon(image);
});
I am looking for a way to dinamiclly switch between panels / between a panel or a canvas.
More specific: I am developing a game. In my code there is a class that extends canvas and implements
Runnable, and in the constructor of Game, it creates a new instance of a class called window. That is window class:
public class Window extends Canvas {
private static final long serialVersionUID = -299686449326748512L;
public static JFrame frame = new JFrame();
public Window(int width, int height, String title, Game game) {
// JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(width, height));
frame.setMaximumSize(new Dimension(width, height));
frame.setMinimumSize(new Dimension(width, height));
frame.setTitle(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
I want to be able to remove game from the frame, activate another panel, and stop the execution of Game.
I have already tried:
game.stop();
Window.frame.remove(game);
but it makes the program to crash. Those are start() & stop() methods:
/**
* starts the game.
*/
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
/**
* tries to stop the game.
*/
public synchronized void stop() {
try {
thread.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
My main goal is to be able to play a cutscene if some event happend and I am trying to use vlcj for that purpose. If anyone has an idea that will allow me to execute this goal that would be great too.
I've made an example, doing what I think you want without a card layout, and using a thread. I think this is a proof of concept, that what your asking is possible. Below, I will include a few things I would do to improve it.
import javax.swing.*;
import java.awt.*;
public class SwapCards{
Thread gameLoop;
volatile boolean running = false;
double x = 0;
double y = 0;
double theta = 0;
JFrame frame = new JFrame("swapped");
Canvas gamePanel = new Canvas(){
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.BLACK);
g.drawOval((int)x, (int)y, 25, 25);
}
};
Canvas nonGame = new Canvas(){
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.BLUE);
g.fillRect(0,0,200, 200);
}
};
public void step(){
x = 100 + 50*Math.sin( theta );
y = 100 + 50*Math.cos( theta );
theta += 0.02;
if(theta > 6.28) theta = 0;
}
public void startGameLoop(){
frame.remove(nonGame);
frame.add(gamePanel, BorderLayout.CENTER);
frame.validate();
running = true;
gameLoop = new Thread(()->{
while(running){
step();
gamePanel.repaint();
try{
Thread.sleep(30);
}catch (Exception e){
running = false;
throw new RuntimeException(e);
}
}
});
gameLoop.start();
}
public void stopGameLoop(){
frame.remove(gamePanel);
frame.add(nonGame, BorderLayout.CENTER);
running = false;
try{
gameLoop.join();
} catch(Exception e){
throw new RuntimeException(e);
}
}
public void buildGui(){
JButton button = new JButton("action");
button.addActionListener( evt->{
if(!running){
startGameLoop();
} else{
stopGameLoop();
}
});
frame.add(nonGame, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args){
EventQueue.invokeLater( new SwapCards()::buildGui );
}
}
First off, Canvas is somewhat outdated, use a JPane and override paintComponent. That gives you more access to the power of swing.
In this example I am doing trivial work so the thread is absolutely overkill I could replace it with a javax.swing.Timer.
Timer timer = new Timer(30, evt->{
step();
gamePanel.repaint();
});
Then in the start and stop methods, I just call timer.start() or timer.stop() respectively.
Using a CardLayout makes it a bit clearer what you want to do, plus it has methods for navigating the cards. Eg. If you have a cut scene with a series of components you want to show, you can use cardLayout.next(parent).
When we create the layout:
cards = new CardLayout();
swap = new JPanel(cards);
swap.add(gamePanel, "game");
swap.add(nonGame, "nogame");
cards.last(swap);
frame.add(swap, BorderLayout.CENTER);
This will add the cards to swap and make it show "nogame". Then in the start/stop methods we just switch to the respective card.
cards.show(swap, "game");
I created a website that acts as a software.
Now i'm trying to do the exact same thing with Java.
is there a way to draw some objects like "div" in html, that i can change x and y position (absolute), background-image, background-color, and put other object into it, [...] with Java ?
I tried this code :
import javax.swing.*;
import javax.swing.border.*;
import javax.accessibility.*;
import java.awt.*;
import java.awt.event.*;
/*
* LayeredPaneDemo.java requires
* images/dukeWaveRed.gif.
*/
public class Demo extends JPanel implements ActionListener, MouseMotionListener{
private String[] layerStrings = { "Yellow (0)", "Magenta (1)",
"Cyan (2)", "Red (3)",
"Green (4)" };
private Color[] layerColors = { Color.yellow, Color.magenta,
Color.cyan, Color.red,
Color.green };
private JLayeredPane layeredPane;
private JLabel dukeLabel;
private JCheckBox onTop;
private JComboBox layerList;
//Action commands
private static String ON_TOP_COMMAND = "ontop";
private static String LAYER_COMMAND = "layer";
//Adjustments to put Duke's toe at the cursor's tip.
private static final int XFUDGE = 40;
private static final int YFUDGE = 57;
public Demo() {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
//Create and load the duke icon.
final ImageIcon icon = createImageIcon("images/dukeWaveRed.gif");
//Create and set up the layered pane.
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(300, 310));
layeredPane.setBorder(BorderFactory.createTitledBorder(
"Move the Mouse to Move Duke"));
layeredPane.addMouseMotionListener(this);
//This is the origin of the first label added.
Point origin = new Point(10, 20);
//This is the offset for computing the origin for the next label.
int offset = 35;
//Add several overlapping, colored labels to the layered pane
//using absolute positioning/sizing.
for (int i = 0; i < layerStrings.length; i++) {
JLabel label = createColoredLabel(layerStrings[i], layerColors[i], origin);
layeredPane.add(label, new Integer(i));
origin.x += offset;
origin.y += offset;
}
//Create and add the Duke label to the layered pane.
dukeLabel = new JLabel(icon);
if (icon != null) {
dukeLabel.setBounds(15, 225, icon.getIconWidth(), icon.getIconHeight());
} else {
System.err.println("Duke icon not found; using black square instead.");
dukeLabel.setBounds(15, 225, 30, 30);
dukeLabel.setOpaque(true);
dukeLabel.setBackground(Color.BLACK);
}
layeredPane.add(dukeLabel, new Integer(2), 0);
//Add control pane and layered pane to this JPanel.
add(Box.createRigidArea(new Dimension(0, 10)));
add(createControlPanel());
add(Box.createRigidArea(new Dimension(0, 10)));
add(layeredPane);
}
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path) {
java.net.URL imgURL = Demo.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
//Create and set up a colored label.
private JLabel createColoredLabel(String text, Color color, Point origin) {
JLabel label = new JLabel(text);
label.setVerticalAlignment(JLabel.TOP);
label.setHorizontalAlignment(JLabel.CENTER);
label.setOpaque(true);
label.setBackground(color);
label.setForeground(Color.black);
label.setBorder(BorderFactory.createLineBorder(Color.black));
label.setBounds(origin.x, origin.y, 140, 140);
return label;
}
//Create the control pane for the top of the frame.
private JPanel createControlPanel() {
onTop = new JCheckBox("Top Position in Layer");
onTop.setSelected(true);
onTop.setActionCommand(ON_TOP_COMMAND);
onTop.addActionListener(this);
layerList = new JComboBox(layerStrings);
layerList.setSelectedIndex(2); //cyan layer
layerList.setActionCommand(LAYER_COMMAND);
layerList.addActionListener(this);
JPanel controls = new JPanel();
controls.add(layerList);
controls.add(onTop);
controls.setBorder(BorderFactory.createTitledBorder("Choose Duke's Layer and Position"));
return controls;
}
//Make Duke follow the cursor.
public void mouseMoved(MouseEvent e) {
dukeLabel.setLocation(e.getX()-XFUDGE, e.getY()-YFUDGE);
}
public void mouseDragged(MouseEvent e) {} //do nothing
//Handle user interaction with the check box and combo box.
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (ON_TOP_COMMAND.equals(cmd)) {
if (onTop.isSelected())
layeredPane.moveToFront(dukeLabel);
else
layeredPane.moveToBack(dukeLabel);
}
else if (LAYER_COMMAND.equals(cmd)) {
int position = onTop.isSelected() ? 0 : 1;
layeredPane.setLayer(dukeLabel,layerList.getSelectedIndex(),position);
}
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new Demo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
This is the result :
It looks to run, but I can not put JLabel into JLabel (will have i to create my own object ? with a JLabel and with sons ?
And finally when i put an addMouseListener to a JLabel , if an other JLabel is above it, i can click it through the other :-/
Use JavaFX with Java 8.
It is a bit slower in development until learned. Due to lambdas and component complexity.
The styling can be done with CSS.
There are animated effects.
Since the question targets Swing, I'll add a Swing-oriented answer anyway for the people who find this question and really must use Swing for any reason.
The trick is to use a fitting look & feel to do it for you in Swing.
Swing tutorial documentation on the subject.
That alone doesn't answer the question as this is about make it pretty and the default look & feel selections are not exactly all that great to look at / didn't stand the test of time. But there are also third party look & feels available for download, you're not necessarily limited to what is bundled with the runtime by default.
As an example, one can use Insubstantial (formerly known as Substance) to pretty up Swing applications. It is too naive to think that you can just plop this look & feel into an existing program and make it look good in an instant, but when you design your application with one of these look & feels from the beginning, the application can look very slick indeed.
But one should really use Swing only when it is a must. Swing is an aging technology and whatever extensions were available for download for it in its glory days are slowly disappearing from the internet / no longer maintained. On top of that it is basically the difference between choosing a pre-designed look & feel or having flexibility to do styling yourself, which JavaFX allows you to do with quite some flexibility.
i am developing an application in which Brightness of image change as per user change the value of JSlider.
Jslider display on window but image is not loaded and i also don't know how to pass value of JSlider to paintComponent() method.
my code :
public class Neo_2010_Slider1 extends JFrame
{
private static final long serialVersionUID = 1L;
private Container container ;
private JSlider slider1 ;
private JLabel lbl1 ;
private JPanel panel1 ;
private JTextField txt1 ;
public Neo_2010_Slider1() {
super("Slider");
setAlwaysOnTop(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(new Color(14555));
setSize(new Dimension(400,400));
setResizable(true);
container = getContentPane();
BorderLayout containerLayout = new BorderLayout();
container.setLayout(containerLayout);
lbl1 = new JLabel("Slider 1");
/****************** TextField Properties ********************************/
txt1 = new JTextField(4);
slider1 = new JSlider(JSlider.HORIZONTAL,0,1000,0);//direction , min , max , current
slider1.setFont(new Font("Tahoma",Font.BOLD,12));
slider1.setMajorTickSpacing(100);
slider1.setMinorTickSpacing(25);
slider1.setPaintLabels(true);
slider1.setPaintTicks(true);
slider1.setPaintTrack(true);
slider1.setAutoscrolls(true);
slider1.setPreferredSize(new Dimension(500,500));
slider1.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
txt1.setText(String.valueOf(slider1.getValue()));
repaint();
}
});
txt1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try
{
slider1.setValue(Integer.parseInt(txt1.getText()));
}
catch(Exception ex)
{
txt1.setText("ERROR");
txt1.setToolTipText("Set Value in Range between 0 - 1000 ") ;
}
}
});
this.addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e){
}
#Override
public void focusGained(FocusEvent e) {
txt1.setText(String.valueOf(slider1.getValue()));
}
});
panel1 = new JPanel();
container.add(panel1, BorderLayout.WEST);
panel1.add(lbl1);
panel1.add(txt1);
panel1.add(slider1);
setVisible(true);
}
public void paintComponent(Graphics g){
Graphics2D g2d=(Graphics2D)g;
try{
BufferedImage src=ImageIO.read(new File("src.jpg"));
BufferedImage dest=changeBrightness(src,0.5f);
g2d.drawImage(dest,0,0,this);
ImageIO.write(dest,"jpeg",new File("dest.jpg"));
}catch(Exception e){
e.printStackTrace();
}
}
public BufferedImage changeBrightness(BufferedImage src,float val){
RescaleOp brighterOp = new RescaleOp(val, 0, null);
return brighterOp.filter(src,null); //filtering
}
public static void main(String args[])
{
new Neo_2010_Slider1();
}
}
i also don't know that where to pass paintComponent method..
if anyone knows then please guide me.
I'm not sure what it is you how to gain, but JFrame doesn't have a paintComponent method, so it will never be called.
What I would do, instead is
Load the source image as soon as you can and store it in variable (masterImage for example)
Use a JLabel to show the image
Use a javax.swing.Timer with a short delay (so 125 milliseconds) set so it won't repeat. Each time the JSlider's stateChanged event is raised, I would restart this timer.
When the timer finally triggers, I would update the "master" image's brightness and apply teh result to "image" JLabel's as it's icon.
The reason for the timer is the fact that the operation of applying the brightness can not only take time, but can increase the amount of memory the application consumes. You will want to reduce this to the absolute minimum if you can
to get the value of a JSlider, you should add a changelistener.
slider.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
// handle change
JSlider source = (JSlider)e.getSource();
methodeToHandleChange( (int) source.getValue() );
}
});
see also how to use a sliders
paintComponent
the paintComponent method is a protected method, so it can not be called from the outside. However it is part of the painting mechanisme that can be invoked by calling the repaint method.
slider.repaint();
I'm basically trying to draw a JComponent inside another by calling the second component's paint passing it the first component's Graphics.
I'm trying to create a GUI editor, (reinventing the wheel, I know, it's just a proof of concept)
So I have a class that extends JPanel where I want to draw components from a VectorControls.
So far I got this method in my extended JPanel:
#SuppressWarnings("serial")
public class Sketch extends JPanel {
private Vector<JComponent> controls = new Vector<JComponent>();
public Sketch() {
super();
this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
}
public void addControl(JComponent c) {
Dimension d = new Dimension(100,50);
c.setPreferredSize(d);
c.setMinimumSize(d);
c.setMaximumSize(d);
controls.add(c);
this.repaint();
this.revalidate();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for(int i=controls.size()-1; i>=0; i--) {
JComponent c = controls.get(i);
c.paint(g);
}
}
}
I'm building/attaching the Sketch panel like this:
public GUIEditor() {
mainFrame = new JFrame("GUI EDITOR");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Sketch mainPanel = new Sketch();
mainPanel.setPreferredSize(new Dimension(640,480));
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
mainFrame.setLayout(gbl);
JPanel toolsPanel = new JPanel();
toolsPanel.setPreferredSize(new Dimension(160,480));
toolsPanel.setLayout(new GridLayout(0,1));
for(Control c : toolBoxItems ) {
AbstractAction action = new ToolBoxButtonAction(mainPanel, c.type);
JButton b = new JButton(action);
b.setText(c.title);
toolsPanel.add(b);
}
gbc.gridx = 0;
gbc.gridy = 0;
gbl.setConstraints(mainPanel, gbc);
mainFrame.add(mainPanel);
gbc.gridx = 1;
gbc.gridy = 0;
gbl.setConstraints(toolsPanel, gbc);
mainFrame.add(toolsPanel);
mainFrame.pack();
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
Inside ToolBoxButtonAction, basically I'm doing this:
public void actionPerformed(ActionEvent e) {
try {
sketch.addControl(control.newInstance());
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
}
but I'm writing this because it doesn't work.
Any ideas on how to achieve this?
I'm basically trying to draw a JComponent inside another by calling the second component's paint passing it the first component's Graphics.
Components can only be painted when the component has non-zero size. Normally the size of a component is determined by the layout manager.
Your basic code looks reasonable, but unless you have code to size and locate the components you won't see anything. If you just set the size then all components will paint on top of one another.
Or the problem may be that your parent panel doesn't have a size so it is not even painted. The default FlowLayout uses the preferred size of the child components to determine the panels size. Since you don't add components directly to the panel there are no child components so the preferred size will be 0. When you reinvent the wheel you need to reinvent everything.
Without a SSCCE the context of how you use this code is unknown to all we can do is guess.
Edit:
Create a SSCCE when you have a problem and get it working with hard coded values before trying to get it to work dynamically. Something like:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class Sketch extends JComponent
{
private Vector<JComponent> controls = new Vector<JComponent>();
public void addControl(JComponent c)
{
c.setSize(100, 50);
int location = controls.size() * 50;
c.setLocation(location, location);
controls.add(c);
repaint();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for(int i=controls.size()-1; i>=0; i--)
{
JComponent c = controls.get(i);
Point location = c.getLocation();
g.translate(location.x, location.y);
c.paint(g);
g.translate(-location.x, -location.y);
}
}
private static void createAndShowUI()
{
Sketch sketch = new Sketch();
sketch.addControl( new JButton("button") );
sketch.addControl( new JTextField(10) );
sketch.addControl( new JCheckBox("Checkbox") );
JFrame frame = new JFrame("Sketch");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( sketch );
frame.setSize(400, 400);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Some time ago, I've written a framework for such tasks. Maybe you find it useful (the library is Open Source):
Tutorial:
http://softsmithy.sourceforge.net/lib/current/docs/tutorial/swing/customizer/index.html
Javadoc:
http://softsmithy.sourceforge.net/lib/current/docs/api/softsmithy-lib-swing-customizer/index.html
Info about the latest release:
http://puces-blog.blogspot.ch/2012/11/news-from-software-smithy-version-03.html