I am a beginner with programming and I have been struggling with the following problem for some time.
I have a list of BufferedImages (this.docList). At the moment I am trying to
put the images onto a JPanel and put that panel inside a JScrollPane.
If I have more than one image, the second image seems to cover the first one.
With the following code I make and send the ScrollPane to the method getPanel which, when it is finished returns a JPanel containing the ScrollPane which contains the Panel(s) containing the BufferedImages. Which are then added to the JFrame.
public void processView(SecondaryProcessPanel secondary){
this.setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
JScrollPane imageHolder = new JScrollPane();
//imageHolder.setLayout(new BoxLayout(imageHolder BoxLayout.Y_AXIS));
JScrollPane panelBack = secondary.getPanel(imageHolder);
panelBack.setPreferredSize(new Dimension(800,800));
this.add(panelBack, 0);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(600, 800);
this.pack();
}
The method getPanel goes through the list of BufferedImages. When it has finished the JPanel that this class is extended from is added to the JScrollPane and sent back to the processView method.
public JScrollPane getPanel(JScrollPane imageHolder) {
for (int j = 0; j< this.docList.size(); j++){
image = this.docList.get(j);
}
imageHolder = new JScrollPane(this);
return imageHolder;
}
#Override
public void paint(Graphics g) {
g.drawImage(image, 0,0, null);
}
What ever way around I put it all I can get back from a list of two images is the second image.
Any help greatly appreciated.
After your kind answers I have changed my code back to what I had originally!
public void processView(SecondaryProcessPanel secondary){
this.setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
for (int i = 0; i < this.docList.size(); i++){
System.out.println("i = " + i);
JScrollPane imageHolder = new JScrollPane();
JScrollPane panelBack = secondary.getPanel(this.docList.get(i), imageHolder);
panelBack.setPreferredSize(new Dimension(800,800));
this.add(panelBack, i);
}
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(600, 800);
this.pack();
}
public JScrollPane getPanel(BufferedImage image, JScrollPane imageHolder){
this.image = image;
imageHolder = new JScrollPane(this);
return imageHolder;
}
#Override
public void paint(Graphics g){
g.drawImage(image, 0,0, null);
}
My problem remains that if the docList ArrayList contains more than one BufferedImage then I only get output from the second image.
The first thing that jumps out at me is the fact that you're violating the painting process...
#Override
public void paint(Graphics g) {
g.drawImage(image, 0,0, null);
}
First of all, assuming that you're extending from a JPanel, you should be overriding the paintComponent method instead, second of all, you should be calling the methods super method
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0,0, this);
}
See Painting in AWT and Swing and Performing Custom Painting for more details about how painting works in Swing.
Second is, WHY? JLabel can display an image just fine. Have a look at How to Use Labels for more details...
And finally...
public void processView(SecondaryProcessPanel secondary){
this.setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
for (int i = 0; i < this.docList.size(); i++){
System.out.println("i = " + i);
JScrollPane imageHolder = new JScrollPane();
JScrollPane panelBack = secondary.getPanel(this.docList.get(i), imageHolder);
panelBack.setPreferredSize(new Dimension(800,800));
this.add(panelBack, i);
}
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(600, 800);
this.pack();
}
public JScrollPane getPanel(BufferedImage image, JScrollPane imageHolder){
this.image = image;
imageHolder = new JScrollPane(this);
return imageHolder;
}
This all just looks very suspicious, it look like you're trying to add the same instance of a component to multiple JScrollPane's, assigning a different image to the single instance each time...
So, this basically will remove the instance of the component from it's previous parent and then add it to the new container (JScrollPane) and supply a new instance of a BufferedImage for it to draw, basically ignoring every that came before it.
In the future, consider providing a runnable example which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses
So, what you'll end up with is a lot of empty JScrollPanes, but with one which is displaying the last image in the list...
This takes the last image on the list - I dont understand what logic you are trying to implement but keeping the last image only is what it does - maybe you want to paint each image (you have a box layout add them there vertically) as it is being read
for (int j = 0; j< this.docList.size(); j++){
image = this.docList.get(j);
}
--
My suggestion is as follows
GridLayout gr=new GridLayout(0, 6);
gr.setRows(0); gr.setColumns(6);
JPanel cpanel=new JPanel();
cpanel.setLayout(gr);
for (int j = 0; j< this.docList.size(); j++){
image = this.docList.get(j);
JLabel jl=new JLabel(new ImageIcon(image));
cpanel.add(jl);
}
this.add(cpanel); // assuming 'this' is the scrollpane you want
and delete the paint method
Related
im trying to insert a gif as a background for my app. I cut all frames and renamed them f1/f2/f3/f4/f5/f6/..... I would use a timer to change the frame so it looks like an animation.
There is a total of 42 frames, so f42.png is the last frame. The code seems to be fine, but there is no result. Any help?
Global variables:
private String backgroundFile;
public JPanel backgroundPanel, areaImage;
private BufferedImage background;
private javax.swing.Timer timerBackground;
Constructor where the Timer is initialized:
public Game()
{
entryWindow();
this.setLayout(null);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Animation method code:
private void backgroundAnimation()
{
backgroundFile = "f"+backgroundNum+".png";
try{
background=ImageIO.read(new File(backgroundFile));
}
catch(IOException e)
{
}
backgroundPanel = new JPanel()
{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, 1100,800,null);
}
};
backgroundPanel.setBackground(Color.BLACK);
backgroundPanel.setBounds(0, 0, 1100, 800);
if (backgroundNum>42)backgroundNum++;
else backgroundNum=1;
add(backgroundPanel);
backgroundPanel.setVisible(true);
}
Action Listener for timer:
if (ae.getSource() == timerBackground)
{
backgroundAnimation();
}
In order to show JPanel, you need to add it to something like JFrame with an BorderLayout for instance, then you need to show the JFrame. JFrame is a application window, the JPanel can be only added and drawn on Window, it can't be viewed without something on which it can draw (like app Window). Beside that you don't need to create new JPanel each time the animation changes, just make a setter for the current image to show, and after assigning the image call repaint(), the ImagePanel could be like this:
public class ImagePanel extends JPanel {
private volatile BufferedImage image;
public void showImage(BufferedImage image) {
this.image=image;
repaint();
}
public void paintComponent(Graphics g) {
g.drawImage(image, 0,0,getWidth(),getHeight(),null);
}
}
add it to your JFrame at application start, also set the LayoutManager of JFrame to BorderLayout preferably, because without that your panel will have size(0,0) since you didn't set it, and it could be one of reasons why you don't see it (you can't see something which is 0 pixel in size, can you?).
Then in your timer just call the ImagePanel method public void showImage(BufferedImage image) with the image to show. If that's don't solve your problem, then post your entire code. As without that i'm just guessing, but those are common problems, so there's big chance you hit something from this.
I can see a few issues here
1. Assuming your Game class is extending JFrame, You need to add the JPanel to the ContentPane of the JFrame. Use one of the approaches setContentPane(backgroundPanel); or getContentPane().add(backgroundPanel)
You are not using a LayoutManager. So either use a LayoutManager or set the Size of the 'JFrame' and 'JPanel' explicitly using setBounds() method. I would recommend using a LayoutManager.
The JPanel or any Component for that matter does not automatically refresh itself. Once you change the image, you need to call repaint() on your JPanel.
You dont need to create a new JPanel every time you change the image. Just extend the JPanel and override the paintComponent()like you have done. Use the Timer to change the image of that single instance and call repaint() with every change.
The complete example, with hat output you are seeing will help understand the problem better and give you a solution. Please see How to create a Minimal, Complete, and Verifiable example
There are multiple problems here, but first let me answer your question:
You are creating a new JPanel and add it to the Game on every run through. That is wrong, since you add infinite panels to your Game
Also in your if/else you have a wrong condition. You increase the iterator when it is greater 42. You probably mean lesser than 42.
Here is how I would do it:
public class BackgroundPanel extends JPanel {
private int currImage = 0;
private BufferedImage[] backgroundImages;
public BackgroundPanel() {
int numberOfImages = 42;
backgroundImages = new BufferedImage[42];
for(int i = 1; i <= numberOfImages; i++) {
String backgroundFile = "f" + i + ".png";
backgroundImages[i] = ImageIO.read(new File(backgroundFile));
}
}
public void nextImage() {
/*if(currImage <= 42) currImage++;
else currImage = 1;*/
if(currImage++ > 42) currImage = 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImages[currImage], 0, 0, getWidth(), getHeight(), null);
}
}
You need to add this panel ONCE to your "Game":
//Somewhere in your Game
private BackgroundPanel backgroundPanel;
...
...
public Game() {
entryWindow();
this.setLayout(null);
backgroundPanel = new backgroundPanel();
backgroundPanel.setSize(getWidth(), getHeight());
add(backgroundPanel);
timerBackground = new javax.swing.Timer(100,this);
timerBackground.stop();
}
Your timer:
if (ae.getSource() == timerBackground) {
backgroundPanel.nextImage();
}
It's easier to put the background on JLabel. It requires only 3 lines of code and works fine! :) Hope it helps for anyone that will have the same problem :)
All you have to do is copy this code, change the name (i have all pictures in a folder called "Images") with any kind of Java supported picture/video/.... (just change the suffix .gif to your file format) and at last the size. Good luck! :)
public JLabel backgroundGIF;
backgroundGIF = new JLabel(new ImageIcon(getClass().getResource("Images/background.gif")));
backgroundGIF.setBounds(0,0,1100,800);
add(backgroundGIF);
I am currently making a pain program and have encountered into a problem when I am attempting to make g2D alpha-friendly. The problem is, that as soon as the paint method is refreshed, it draws on what the user drew, as well as GUI components that the user has hovered over. This is the code in the pain method:
public void paintComponent(Graphics comp)
{
Graphics2D board = (Graphics2D) comp;
AlphaComposite ac=AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f);
Composite oldComp=board.getComposite();
board.setComposite(ac); //used composite as a suggestion found on stackover flow;
//did not work.
for(int i = 0; i < al.size(); i++)
{
board.setColor(cl.get(i));
board.setStroke(new BasicStroke(tl.get(i),lineEnd.get(i),juncture.get(i)));
board.draw((Shape) al.get(i));
}
board.setComposite(oldComp);
}
Picture of what it looks like:
I have a feeling that the absolute position of the drawing board is in the left hand corner, so it draws it on as you update the paint method. I do not know how to solve that though. Here is my code for the setup of the drawing board if you need it:
public Container(String name, String text, String text2)
{
addMouseListener(mouseListener);
addMouseMotionListener(this);
setBorder(BorderFactory.createLineBorder(Color.black));
}
and in the main JFrame:
items[0].addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
JTextField name = new JTextField();
JTextField width = new JTextField();
JTextField height = new JTextField();
final JComponent[] inputs = new JComponent[] { new JLabel("Creating new image: please fill in the required text fields."), new JLabel("Name: "), name, new JLabel("Width: "), width, new JLabel("Height: "), height};
name.addAncestorListener( new RequestFocusListener() );
JOptionPane.showMessageDialog(null, inputs, "New Image", JOptionPane.PLAIN_MESSAGE);
Container cont = new Container(name.getText(), width.getText(), height.getText());
addContainer(cont);
cont.setPreferredSize(new Dimension(Integer.parseInt(width.getText()),Integer.parseInt(height.getText())));
JScrollPane scroll = new JScrollPane();
JPanel pane = new JPanel();
pane.add(cont);
scroll.setViewportView(pane);
pane.setBackground(Color.lightGray);
tabbed.addTab(name.getText(), scroll);
setCursor("pen");
}
});
Thanks for your help!
EDIT:
Here are the array lists:
static ArrayList<Shape> al = new ArrayList<Shape>();
static ArrayList<Color> cl = new ArrayList<Color>();
static ArrayList<Integer> tl = new ArrayList<Integer>();
static ArrayList<Integer> lineEnd = new ArrayList<Integer>();
static ArrayList<Integer> juncture = new ArrayList<Integer>();
Basically, you've broken the paint chain.
When a paint cycle runs, a top level method is called (typically paint), which then calls a chain of events to perform the actual painting.
Each element in the chain does a particular job and builds on each other, failure to honour this chain will cause you problems.
Generally, the Graphics context for a window is a shared resource, meaning that during any given paint cycle, all the components that are painted share the same Graphics context
To fix the initial problem of " it draws on what the user drew, as well as GUI components that the user has hovered over", you need to call super.paintComponent, for example...
public void paintComponent(Graphics comp) {
super.paintComponent(comp);
Graphics2D board = (Graphics2D) comp;
This will clear the Graphics context from what was painted to it previous.
paintComponent should remain protected as the should no reason to allow anybody else to ever call it directly.
Take a look at Performing Custom Painting and Painting in AWT and Swing for more details
I have this code in which a Gantt Chart will be shown on another JFrame, other than the main UI frame. Is there any way such that the drawn Graphics here will be shown on a JPanel on the main UI frame?
chart= new JFrame();
final ArrayList<Task> tasks = taskList;
final int ET = processET;
chart.setSize(300, 300);
chart.setContentPane(new JPanel(){
public void paint(Graphics g){
for(int j=0;j < tasks.size();j++)
{
int ST = 0;
if(j!= tasks.size()-1) ST = tasks.get(j + 1).getStartTime();
else ST = ET;
int Product = ST * 20;
g.drawRect(50,50,Product,30);
g.drawString("p"+tasks.get(j).getPID(),(55+(tasks.get(j).getStartTime()*20)),70);
g.drawString(""+tasks.get(j).getStartTime(), (50+(tasks.get(j).getStartTime()*20)),100);
}
g.drawString(""+ET,50+(ET*20),100);
}
});
chart.pack();
chart.setLocationRelativeTo(null);
chart.setVisible(true);
I tried this code so that it would put it on a JPanel, but obviously it did not work, hence this inquiry. Please help and thanks :)
panel_2 = new JPanel(){ "same thing" };
panel.add(panel_2);
Is there any way such that the drawn Graphics here will be shown on a JPanel on the main UI frame?
There are a number of ways. See this answer for details. I'm thinking either a CardLayout or JTabbedPane might be the easiest, given that code is already rendered in a JPanel (that is currently added to a frame for display).
private static final long serialVersionUID = 1L;
String[] options = {"1","2","4","8","16","20","40","100","400"} ;
int[] optionsNum = {1,2,4,8,16,20,40,100,400};
JComboBox<String> box = new JComboBox<>(options);
JLabel prompt = new JLabel("How complex do you want the circle to be?");
ImageIcon image;
Circle p = new Circle(1);
int boxindex = 0;
public CircleDrawer(){
image = new ImageIcon(p.getImage());
box.setSelectedIndex(boxindex);
setLayout(new FlowLayout());
add(new JLabel(image));
add(prompt);
add(box);
pack();
setSize(851, 950);
setTitle("Circle Drawer");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
box.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == box){
boxindex = box.getSelectedIndex();
p.setComplexity(optionsNum[boxindex]);
image = new ImageIcon(p.getImage());
add(new JLabel(image));
validate();
}
}
public static void main(String[] args) {
CircleDrawer f = new CircleDrawer();
f.setVisible(true);
}
Basically, I have this code. It references a class called Circle, which calculates some points on the outer edge of a circle and draws them using a paintComponent. In this class, there is a method called getImage, which takes what the paintComponent method drew and puts it into a BufferedImage.
public BufferedImage getImage() {
BufferedImage hello = new BufferedImage(805, 805, BufferedImage.TYPE_INT_ARGB);
Graphics g = hello.getGraphics();
paintComponent( g );
return hello;
}
Like so.
The problem I'm running into is that I can't find a way for it to completely re-draw the JFrame. I've tried clearing the JFrame within the actionPerformed method using removeAll() and then COMPLETELY setting up the frame all over again (add all of the components, pack, setSize, setTitle, etc) and then repaint, revalidate, or just validate it.
If I simply have it add the image and then validate it, I can see the image being updated, but it's just getting tacked on at the end of the JFrame (just like I would expect it to using a FlowLayout) but that's not the behavior I need. It just shows that it is sort of working.
My question is this: How do I make it re-draw the JFrame when the user changes options inside of the JComboBox?
Graphics g = hello.getGraphics();
paintComponent( g );
Don't use getGraphics() and never invoke paintComponent() directly. Swing will invoke the proper painting methods are required.
add(new JLabel(image));
validate();
When you add (remove) components from a visible GUI the general structure of the code is:
add(...);
revalidate(); // to invoke the layout manager
repaint(); to repaint the components
I was using Java JRE 1.6.7 and had a JComponent and a JScrollPane. I couldn't get double buffering to work on this which always resulted in a flicker. I had the buffering taken care of if I used a Canvas, but this resulted in problems when used in conjunction with a JScrollPane.
So I downloaded JRE 1.6.18 in hopes that one of these problems would be fixed. Well now the JComponent inside the JScrollPane is not drawing properly at all. It draws only the outside region of the JComponent, as if the JScrollPane is drawing on top of it except for the borders.
Here is an example of code that is not drawing..this results in a 1-pixel-wide white outline of the area where drawing should occur:
public void paint(Graphics arg0) {
Graphics2D graphics = (Graphics2D) arg0;
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, (int) getWidth(), (int) getHeight());
Any help is greatly appreciated!
-Craig
try to make an override from paintComponent(Graphics g) instead of paint(Graphics g).
paintComponent is the method you have to override for costum drawing.
Are you sure you can see the white rectangle, try to use red or something else you can see good.
It looks like you're making progress, but you might like to see the tutorial examples, too.
Martijn Courteaux's analysis is correct: you should override paintComponent(). Also, it's a bad idea to mix AWT and Swing components. Both ideas are discussed in Painting in AWT and Swing.
Scrolling shouldn't cause flickering. Here's an example that scrolls a grid of components and paints a checkerboard on the background.
import java.awt.*;
import javax.swing.*;
public class Scrolling extends JFrame {
private static final int MAX = 8;
private static final int SIZE = 480;
private static final Color light = new Color(0x40C040);
private static final Color dark = new Color(0x408040);
private static class MyPanel extends JPanel {
public MyPanel() {
super(true);
this.setLayout(new GridLayout(MAX, MAX, MAX, MAX));
this.setPreferredSize(new Dimension(SIZE, SIZE));
for (int i = 0; i < MAX * MAX; i++) {
this.add(new JLabel(String.valueOf(i), JLabel.HORIZONTAL));
}
}
#Override
public void paintComponent(final Graphics g) {
int w = this.getWidth()/MAX;
int h = this.getHeight()/MAX;
for (int row = 0; row < MAX; row++) {
for (int col = 0; col < MAX; col++) {
g.setColor((row + col) % 2 == 0 ? light : dark);
g.fillRect(col * w, row * h, w, h);
}
}
}
}
public Scrolling() {
this.setLayout(new BorderLayout());
final MyPanel panel = new MyPanel();
final JScrollPane scrollPane = new JScrollPane(panel);
scrollPane.getHorizontalScrollBar().setUnitIncrement(16);
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
this.add(scrollPane, BorderLayout.CENTER);
this.pack();
this.setSize(SIZE - SIZE / 3, SIZE - SIZE / 3);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Scrolling().setVisible(true);
}
});
}
}
Ok, I've come up with a slight answer.
Instead of calling
scrollPane.add(containerCanvas);
I'm calling
new JScrollPane(containerCanvas);
This works in a sense. However, it now is disallowing the JScrollPane bars from showing up. I have no idea why this is, but am currently looking into it. But at least the component is drawing again.