My component is bigger than the screen and parts of it are not shown (I will use scrollbars).
When I receive a call in paintComponent(g) how do I know what area should I paint?
I'm not sure if this is what you mean, but the problem is you will have to call repaint() on the JScrollPane each time you get a call in paintComponent(Graphics g) of the JPanel or else updates on the JPanel will not be visible in the JScrollPane.
Also I see you want to use JScrollBar (or maybe you confused the terminology)? I'd recommend a JScrollPane
I made a small example which is a JPanel with a grid that will change its colour every 2 seconds (Red to black and vice versa). The JPanel/Grid is larger then the JScrollPane; regardless we have to call repaint() on the JScrollPane instance or else the grid wont change colour:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().createAndShowUI();
}
});
}
private void createAndShowUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents(frame);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setVisible(true);
}
private void initComponents(JFrame frame) {
JScrollPane jsp = new JScrollPane();
jsp.setViewportView(new Panel(800, 800, jsp));
frame.getContentPane().add(jsp);
}
}
class Panel extends JPanel {
private int across, down;
private Panel.Tile[][] tiles;
private Color color = Color.black;
private final JScrollPane jScrollPane;
public Panel(int width, int height, JScrollPane jScrollPane) {
this.setPreferredSize(new Dimension(width, height));
this.jScrollPane = jScrollPane;
createTiles();
changePanelColorTimer();//just something to do to check if its repaints fine
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
g.setColor(color);
for (int k = 0; k < 5; k++) {
g.drawRect(tiles[i][j].x + k, tiles[i][j].y + k, tiles[i][j].side - k * 2, tiles[i][j].side - 2 * k);
}
}
}
updateScrollPane();//refresh the pane after every paint
}
//calls repaint on the scrollPane instance
private void updateScrollPane() {
jScrollPane.repaint();
}
private void createTiles() {
across = 13;
down = 9;
tiles = new Panel.Tile[across][down];
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
tiles[i][j] = new Panel.Tile((i * 50), (j * 50), 50);
}
}
}
//change the color of the grid lines from black to red and vice versa every 2s
private void changePanelColorTimer() {
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (color == Color.black) {
color = Color.red;
} else {
color = Color.black;
}
}
});
timer.setInitialDelay(2000);
timer.start();
}
private class Tile {
int x, y, side;
public Tile(int inX, int inY, int inSide) {
x = inX;
y = inY;
side = inSide;
}
}
}
In the Panel class if we comment the line updateScrollPane(); in paintComponent(Graphics g) we wont see the grid change colour.
You can find out the area that actually has to be painted by querying the clip bounds of the Graphics object.
The JavaDoc seems to be a bit out-dated for this method: It says, that it may return a null clip. However, this is obviously never the case (and other Swing classes also rely on the clip never being null!).
The follwing MCVE illustrates the difference between using a the clip or painting the whole component:
It contains a JPanel with a size of 800x800 in a scroll pane. The panel paints a set of rectangles, and prints how many rectangles have been painted.
One can use the "Use clip bounds" checkbox to enable and disable using the clip. When the clip is used, only the visible area of the panel is repainted, and the number of rectangles is much lower. (Note that the test whether a rectangle has to be painted or not is rather simple here: It only performs an intersection test of the rectangle with the visible region. For a real application, one would directly use the clip bounds to find out which rectangles have to be painted).
This example also shows some of the tricky internals of scroll panes: When the blinking is switched off, and the scroll bars are moved, one can see that - although the whole visible area changes - only a tiny area actually has to be repainted (namely the area that has become visible due to the scrolling). The other part is simply moved as-it-is, by blitting the previous contents. This behavior can be modified with JViewport.html#setScrollMode.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class PaintRegionTest
{
public static void main(String[] args) throws Exception
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final PaintRegionPanel paintRegionPanel = new PaintRegionPanel();
paintRegionPanel.setPreferredSize(new Dimension(800, 800));
final Timer timer = new Timer(1000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.changeColor();
}
});
timer.setInitialDelay(1000);
timer.start();
JScrollPane scrollPane = new JScrollPane(paintRegionPanel);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JPanel controlPanel = new JPanel(new FlowLayout());
final JCheckBox blinkCheckbox = new JCheckBox("Blink", true);
blinkCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if (blinkCheckbox.isSelected())
{
timer.start();
}
else
{
timer.stop();
}
}
});
controlPanel.add(blinkCheckbox);
final JCheckBox useClipCheckbox = new JCheckBox("Use clip bounds");
useClipCheckbox.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.setUseClipBounds(
useClipCheckbox.isSelected());
}
});
controlPanel.add(useClipCheckbox);
frame.getContentPane().add(controlPanel, BorderLayout.SOUTH);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class PaintRegionPanel extends JPanel
{
private Color color = Color.BLACK;
private boolean useClipBounds = false;
void setUseClipBounds(boolean useClipBounds)
{
this.useClipBounds = useClipBounds;
}
void changeColor()
{
if (color == Color.BLACK)
{
color = Color.RED;
}
else
{
color = Color.BLACK;
}
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(color);
Rectangle clipBounds = g.getClipBounds();
Rectangle ownBounds = new Rectangle(0,0,getWidth(),getHeight());
System.out.println("clipBounds: " + clipBounds);
System.out.println(" ownBounds: " + ownBounds);
Rectangle paintedRegion = null;
if (useClipBounds)
{
System.out.println("Using clipBounds");
paintedRegion = clipBounds;
}
else
{
System.out.println("Using ownBounds");
paintedRegion = ownBounds;
}
int counter = 0;
// This loop performs a a simple test see whether the objects
// have to be painted. In a real application, one would
// probably use the clip information to ONLY create the
// rectangles that actually have to be painted:
for (int x = 0; x < getWidth(); x += 20)
{
for (int y = 0; y < getHeight(); y += 20)
{
Rectangle r = new Rectangle(x + 5, y + 5, 10, 10);
if (r.intersects(paintedRegion))
{
g.fill(r);
counter++;
}
}
}
System.out.println("Painted "+counter+" rectangles ");
}
}
An aside: For many application cases, such an "optimization" should hardly be necessary. The painted elements are intersected against the clip anyhow, so one will probably not gain much performance. When "preparing" the elements to be painted is computationally expensive, one can consider this as one option. (In the example, "preparing" refers to creating the Rectangle instance, but there may be more complicated patterns). But in these cases, there may also be more elegant and easier solutions than manually checking the clip bounds.
All answers are wrong. So I decided to answer the question despide the fact that the question is two years old.
I believe that the correct answer is calling g.getClipBounds() inside of paintComponent(Graphics g) method. It will return the rectangle in the control's coordinate system of the area which is invalidated and must be redrawn.
Related
I know there are already hundreds of threads but I just cant understand it..
I have this very simple class thats drawing a grid. I would like to add like a 0.2 second delay after each square. Thread.sleep doesnt work. What is the simplest way?
public Screen() {
repaint();
}
public void paint(Graphics g) {
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
g.drawRect(50 * i, 50 * j, 50, 50);
//Add delay
}
}
}
The simplest way to achieve delayed drawing is by using a Swing Timer, which is a class that won't block the EDT when executed. This will allow you to create a delay without blocking your UI (and making everything appear at once).
You'll have a single JPanel that's going to handle the painting in the paintComponent(...) method and not paint(...) as you did in your code above. This JPanel will repaint every Rectangle shape from the Shape API.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class DelayedDrawing {
private JFrame frame;
private JPanel pane;
private Timer timer;
private int xCoord = 0;
private int yCoord = 0;
private static final int GAP = 10;
private static final int WIDTH_HEIGHT = 10;
private static final int ROWS = 5;
private static final int COLS = 5;
private List<Rectangle> rectangles;
#SuppressWarnings("serial")
private void createAndShowGUI() {
//We create the JFrame
frame = new JFrame(this.getClass().getSimpleName());
//We create a list of Rectangles from the Shape API
rectangles = new ArrayList<>();
createRectangle();
//Creates our JPanel that's going to draw every rectangle
pane = new JPanel() {
//Specifies the size of our JPanel
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
}
//This is where the "magic" happens, it iterates over our list and repaints every single Rectangle in it
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Rectangle r : rectangles) {
System.out.println(r.x + " " + r.y);
g2d.draw(r);
}
}
};
//This starts our Timer
timer = new Timer(200, listener);
timer.setInitialDelay(1000);
timer.start();
//We add everything to the frame
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//Creates a new Rectangle and adds it to the List
private void createRectangle() {
Rectangle r = new Rectangle(xCoord * WIDTH_HEIGHT + GAP, yCoord * WIDTH_HEIGHT + GAP, WIDTH_HEIGHT, WIDTH_HEIGHT);
rectangles.add(r);
}
//This will be executed everytime the Timer is fired
private ActionListener listener = e -> {
if (xCoord < ROWS) {
if (yCoord < COLS) {
yCoord++;
} else {
yCoord = 0;
xCoord++;
if (xCoord == ROWS) {
timer.stop();
return;
}
}
}
createRectangle();
pane.repaint();
};
public static void main(String[] args) {
SwingUtilities.invokeLater(new DelayedDrawing()::createAndShowGUI);
}
}
I am creating a basic Tic-Tac-Toe application in Java Swing, and for this purpose, I have started investigating drawing. However, I have encountered an issue when a subclass of JPanel contains more than one instance of a subclass of JLabel, which overrides the paintComponent(Graphic) method, in a GridLayout array format embedded in itself. The issue is that only the first element in this array is painted.
An example for the customized subclass of JLabel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class DrawLabel extends JLabel {
private boolean hasPaint;
public DrawLabel() {
super();
this.hasPaint = false;
}
public void draw() {
hasPaint = true;
repaint();
}
public void clear() {
hasPaint = false;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (hasPaint) {
g2.drawOval(getX(), getY(), getWidth(), getHeight());
} else {
g2.clearRect(getX(), getY(), getWidth(), getHeight());
}
}
}
An example for the customized subclass of JPanel:
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class DrawPanel extends JPanel {
private Dimension size;
private DrawLabel[][] fields;
public DrawPanel(Dimension d) {
super();
this.size = d;
this.fields = new DrawLabel[size.height][size.width];
this.setLayout(new GridLayout(size.height, size.width));
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
this.fields[i][j] = new DrawLabel();
this.add(fields[i][j]);
}
}
}
public void draw(int row, int col) {
fields[row][col].draw();
}
public void clear() {
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
fields[i][j].clear();
}
}
}
}
An example of an issue-provoking scenario:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Driver {
public static void main(String[] args) {
DrawPanel board = new DrawPanel(new Dimension(2, 1));
JFrame canvas = new JFrame();
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas.add(board, BorderLayout.CENTER);
canvas.pack();
canvas.setLocationRelativeTo(null);
canvas.setVisible(true);
board.draw(0, 0); // This gets painted.
board.draw(0, 1); // This does not get painted!
}
}
Curiously, if the DrawPanel.draw(int, int) is changed to set the text of the element rather than draw upon it, only the second element is updated, while the first is not, which is the exact opposite of the original problem.
I have tried to look up other issues and questions related to painting of subcomponents, but I have yet to find an issue like this one, where it seems that every instance beyond the first in the GridLayout are not drawable in the same way. What could be wrong?
Thank you for your time and effort!
g2.drawOval(getX(), getY(), getWidth(), getHeight());
Painting of a component is done relative to the component, not the panel the component is painted in. The getX/Y() methods return the location of the component relative to the parent component.
So if the size of each component is (200, 200) then the oval of the first component will be painted using
g2.drawOval(0, 0, 200, 200);
The second component will be painted at:
g2.drawOval(200, 0, 200, 200);
but since the component is only 200 pixels wide, the start x location of 200 is outside the bounds of the component so there is nothing to paint.
Instead just use:
g2.drawOval(0, 0, getWidth(), getHeight());
I have an informatic project with JAVA langage for class and it's rated to graduate from high school.
Btw my program consists in drawing geometrical forms in the JPanel with clic control but I don't know how to put, in my main program, fonctions which draw figures as I want like drawSquare(x,y,length). For the moment the program show this window (it's a JFrame with my JPanel inside):
http://i.imgur.com/YetG3VB.png
I have a method readClick() which give to a Point point the coordinates point.x and point.y in the frame but I don't know how, in my main, I can do to call a graphical drawing.
"My problem is that I want to put in my main program something like
Point clic;
clic= Fenetre.readClick() ; // I have this method which wait me to clic on the frame
x = clic.x ; //and give to point the coordonates
y= clic.y; // point.x && point.y
clic = Fenetre.readClick();
a = clic.x ;
b = clic.y ;
"Function which draws rectangle for example"(x,y,a,b);
//It's this fonction I want to create and
// I want it to draw the rectangle in the JPanel
But I don't know how to make the function which adds a drawing in the JPanel "
My main program :
public class Geogebra {
#SuppressWarnings("unused")
public static void main(String args[]){
Fenetre fen = new Fenetre();
}}
My window class :
import java.awt.Color;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.concurrent.SynchronousQueue;
import javax.swing.JFrame;
#SuppressWarnings({ "unused", "serial" })
public class Fenetre extends JFrame {
static Panneau component = null;
private final static SynchronousQueue<MouseEvent> clicks = new SynchronousQueue<MouseEvent>();
public Fenetre(){
JFrame frame = new JFrame();
component = new Panneau();
frame.setTitle("ISNOGEBRA");
frame.setSize(900, 700); // Taille initiale de la fenetre : 900 * 700
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(component);
frame.setVisible(true);
component.addMouseListener( new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
clicks.offer(e);
}
});
}
public static MouseEvent readMouse() {
try {
return clicks.take();
}
catch (InterruptedException e) {
throw new AssertionError();
}
}
public static Point readClick() {
return readMouse().getPoint();
}
}
My JPanel class (the two class Mathwrapper and Menu just draw the interface, I'll show them if needed but it's really long) :
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.swing.JPanel;
#SuppressWarnings({ "serial", "unused" })
public class Panneau extends JPanel implements MouseListener {
Menu menu;
MathWrapper mw;
public Panneau(){
super();
this.setBackground(Color.WHITE);
menu = new Menu(20, 20);
mw = MathWrapper.getInstance(0, 80);
this.setOpaque(true);
this.addMouseListener(this);
}
#Override
public void paintComponent(Graphics g){
menu.draw(g);
mw.draw(g);
}
public void mouseClicked(MouseEvent e) {
//That shows a blue square behind the icon in the menu I click on
//and draws a black one on the one which was selected
if((e.getX() > 20 && e.getX() < 60) && (e.getY()) > 20 && e.getY() < 60){
menu.btns[menu.activeTool].toggleClicked();
menu.btns[0].toggleClicked();
menu.activeTool = 0;
}else if((e.getX() > 80 && e.getX() < 120) && (e.getY()) > 20 && e.getY() < 60){
menu.btns[menu.activeTool].toggleClicked();
menu.btns[1].toggleClicked();
menu.activeTool = 1;
}else if((e.getX() > 140 && e.getX() < 180) && (e.getY()) > 20 && e.getY() < 60){
menu.btns[menu.activeTool].toggleClicked();
menu.btns[2].toggleClicked();
menu.activeTool = 2;
}else if((e.getX() > 200 && e.getX() < 240) && (e.getY()) > 20 && e.getY() < 60){
menu.btns[menu.activeTool].toggleClicked();
menu.btns[3].toggleClicked();
menu.activeTool = 3;
}else if((e.getX() > 260 && e.getX() < 300) && (e.getY()) > 20 && e.getY() < 60){
menu.btns[menu.activeTool].toggleClicked();
menu.btns[4].toggleClicked();
menu.activeTool = 4;
}
mw.setActiveTool(menu.activeTool);
this.repaint();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
}
Please help me I've read plents of documents about JPanel and drawing but I still don't understand ..
If I understand your question, you basically want some function from which you can
wait for user interaction and get information about it
draw something to your window
(I'm ignoring right now that you want to do it from the main method, since I'm pretty sure you'll be happy to do it from another method as well)
Basically you are facing two obstacles in that project:
Drawing in most windowing systems is event driven, i. e. if the user puts the window to front, it has to know how to draw its current state
User interaction is also event driven, i. e. you will get callbacks when the user clicks.
The first obstacle is easy to solve: Create a BufferedImage somewhere that both your UI code and the function can access, and draw inside it. (BufferedImage has a createGraphics method you can use to draw inside the image's buffer). Then the paintComponent method just draws the image whenever somebody puts the window to foreground.
The second obstacle is a bit harder. Basically you'll need threading and synchronization for it. It used to be a lot of tweaking with wait and notify and synchronized calls up to Java 1.4, but since Java 1.5 the synchronization (for this special case) can be handled by BlockingQueue class. So your UI code waits (with MouseListener) for a mouse click and adds the coordinates (as a java.awt.Point) to BlockingQueue, and your UI code will just have to wait for the next point whenever it needs one. You will still have a bit of experience using multithreaded applications (so you should know how to start a Thread and that a Thread cannot interact directly with the UI), so depending on your experience of Java it may be a steep learning curve - but certainly doable.
EDIT: I see you are already using some kind of synchronization for the mouse events, so probably the only parts you still need is starting a thread and drawing to a BufferedImage instead of the real window itself.
EDIT2: Here is a very simplified example that shows you how you can draw to a BufferedImage from a second thread. It will ask for coordinates and colors from the console (standard input) and paint them to the image, which will show in the JPanel. It is your task to combine this example with what you already have to move the mouse position across etc.
import java.awt.*;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.util.Scanner;
public class BufferedImagePainting extends JFrame {
public static void main(String[] args) {
BufferedImage img = new BufferedImage(800, 600, BufferedImage.TYPE_3BYTE_BGR);
JPanel drawPanel = new DrawPanel(img);
new InteractionThread(img, drawPanel).start();
new BufferedImagePainting(drawPanel);
}
public BufferedImagePainting(final JPanel drawPanel) {
super();
setLayout(new GridLayout(1, 1));
add(drawPanel);
pack();
setVisible(true);
}
private static class DrawPanel extends JPanel {
private BufferedImage img;
public DrawPanel(BufferedImage img) {
this.img = img;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
}
private static class InteractionThread extends Thread {
private BufferedImage img;
private JPanel drawPanel;
public InteractionThread(BufferedImage img, JPanel drawPanel) {
this.img = img;
this.drawPanel = drawPanel;
}
#Override
public void run() {
#SuppressWarnings("resource")
Scanner s = new Scanner(System.in);
while (true) {
System.out.println("Enter a draw color and a fill color, separated by spaces");
System.out.println("Enter colors as 6-digit hex number, i. e. 000000 = black, ffffff = white");
Color drawColor = new Color(Integer.parseInt(s.next(), 16));
Color fillColor = new Color(Integer.parseInt(s.next(), 16));
System.out.println("Enter coordinates in form x y width height, separated by spaces");
int x = s.nextInt();
int y = s.nextInt();
int w = s.nextInt();
int h = s.nextInt();
Graphics g = img.createGraphics();
g.setColor(fillColor);
g.fillRect(x, y, w, h);
g.setColor(drawColor);
g.drawRect(x, y, w, h);
g.dispose();
drawPanel.repaint();
}
}
}
}
You must set visibility of your JFrame. Try this in your main class.
public class Geogebra {
#SuppressWarnings("unused")
public static void main(String args[]){
Fenetre fen = new Fenetre();
fen.setVisible(true);
}}
I'm about to embark on writing a (I think it will be) quick program involving a pulling headlines from a stock website and inserting them into an ArrayList of a class I'm going to create called Funds/Tickers/WhatHaveYou. That's not the huge problem. The main problem I have is this:
I want to have just one window, and that window will just continuously scroll the headlines. You can click on the headlines and that will bring up a browser to the article to read.
I initially thought you could do this with JLabels, but I did a few experiments and I could only get Strings to move accross the screen, not JLabels/JButtons/clickable things. Is there a way I can have JLabels or hyperlinks scroll accross the window in Java?
Cheers,
David
I want to have just one window, and that window will just continuously scroll the headlines.
You can use the Marquee Panel for this.
You can click on the headlines and that will bring up a browser to the article to read.
The above class doesn't support clicking on the components. However, I have been playing around a little with this concept. It seems to work OK when using a JLabel but I don't think it works for other components.
1) First you need to add a couple of methods to the MarqueePanel class that will translate the mouse point to map to the real component on the panel (in case you are using the wrapping option):
#Override
public Component getComponentAt(int x, int y)
{
Point translated = getTranslatedPoint(x, y);
for (Component c: getComponents())
{
if (c.getBounds().contains(translated))
return c;
}
return null;
}
public Point getTranslatedPoint(int x, int y)
{
int translatedX = x + scrollOffset;
if (isWrap())
{
int preferredWidth = super.getPreferredSize().width;
preferredWidth += getWrapAmount();
translatedX = translatedX % preferredWidth;
}
return new Point(translatedX, y);
}
2) Then you can add a MouseListener to the panel. With code like the following you can now access the label that was clicked:
marquee.addMouseListener( new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
Component c = marquee.getComponentAt(e.getPoint());
if (c == null) return;
if (c insstanceof JLabel)
{
JLabel label = (JLabel)c;
System.out.println(label.getText());
}
}
});
In this example, entries from an RSS feed are added to a JTextPane. A HyperlinkListener can be added to receive events representing clicks on hyerlinks.
Maybe what you need is a a EditorPane:
http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html
Hope it helps.
I was working on this yesterday, when you first posted your question. I got tired and stopped messing with it. Basically what I did was use JPanel, and set their locations with each timer tick. I doubt it's the most optimal way, and I got really tired of trying to fix some of the problem, so I just stopped. You're free to play around with it, see if you can get any better results from it. The most frustrating part is when you click the panel, everything shifts for a quick second. Hopefully you can do something with it, or at least get some ideas. I just didn't want to scrap it, in case maybe you could make something of it.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestLabelMove {
List<MovingLabel> labels;
private int count = 1;
private JLabel statusLabel;
private final int SEPARATION = 100;
private final int SCREEN_W = 800;
int XLOC = SCREEN_W;
public TestLabelMove() {
statusLabel = new JLabel("Status");
statusLabel.setHorizontalAlignment(JLabel.CENTER);
JFrame frame = new JFrame("Test Labels");
frame.add(statusLabel, BorderLayout.CENTER);
frame.add(new LabelPanel(), BorderLayout.SOUTH);
frame.setSize(800, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private class LabelPanel extends JPanel {
private static final int INC = 5;
public LabelPanel() {
labels = new LinkedList<>();
for (int i = 0; i < 8; i++) {
MovingLabel label = new MovingLabel(XLOC);
labels.add(label);
XLOC -= SEPARATION;
add(label);
}
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (MovingLabel label : labels) {
if (isWrap(label.getXLoc())) {
label.setXLoc(SCREEN_W);
label.setLocation(label.getXLoc(), 0);
} else {
label.setLocation(label.getXLoc(), 0);
label.setXLoc(label.getXLoc() - INC);
}
}
}
});
timer.start();
}
}
public boolean isWrap(int x) {
return x <= -40;
}
private class MovingLabel extends JPanel {
int xLoc;
String phrase;
public MovingLabel(int xLoc) {
this.xLoc = xLoc;
phrase = "Panel " + count;
count++;
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
statusLabel.setText(phrase);
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Font font = new Font("Helvetica", Font.BOLD, 14);
FontMetrics fm = g.getFontMetrics(font);
int w = fm.stringWidth(phrase);
int h = fm.getAscent();
g.setFont(font);
g.drawString(phrase, getWidth()/2 - w/2, getHeight()/2 + h/2);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 20);
}
public void setXLoc(int xLoc) {
this.xLoc = xLoc;
}
public int getXLoc() {
return xLoc;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestLabelMove();
}
});
}
}
Right now I am trying to make it so that the connect 4 grid on the gui is always a 7x8 no matter what window size. I have been trying to set the button array with a setMaximumSize and it's not working.
Here is the code that sets the JButton array
void ResetGame()
{
JLabel label = new JLabel("Click a column to drop piece");
for(int r=0;r<gameBoard.length;r++)
{
java.util.Arrays.fill(gameBoard[r],0,gameBoard[r].length,'0');
//loop through board columns
for(int c=0;c<gameBoard[r].length;c++)
{
gameButtons[r][c]= new JButton(empty);
panel.add(gameButtons[r][c]);
gameButtons[r][c].setPreferredSize(new Dimension(70,70));
//Allows buttons to be arranged as grid.
GridLayout grid = new GridLayout(0,8);
//Sets into grid.
gameButtons[r][c].setLayout(grid);
gameButtons[r][c].setMaximumSize(new Dimension(0,10));
}
panel.add(label);
}
// loop through array setting char array back to ' ' and buttons array back to empty pic
// reset currentPlayer and numMoves variables
}
Just in case I'll also include the window creation method here.
public void CreateWindow()
{
//Sets window title and create window object.
JFrame aWindow = new JFrame("Connect Four");
//Set window position and size
aWindow.setBounds(200,100,600,800);
//What close button does.
aWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Make window visible.
aWindow.setVisible(true);
//Sets content area to work with stuff.
aWindow.setContentPane(panel);
//Gets content pane.
Container content = aWindow.getContentPane();
}
Not sure of what you are trying to achieve with setMaximumSize. Without explicit and precise requirements, we can hardly help you.
So, I would suggest that you take a look at the following snippet (which is an SSCCE) and try to find out what you are doing wrong:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Connect4 {
public class GameButton extends JPanel {
private final int row;
private final int column;
private Color color;
public GameButton(final int row, final int column) {
super();
this.row = row;
this.column = column;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Game button " + row + " " + column + " has been pressed");
}
});
}
public void setColor(Color color) {
this.color = color;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int size = Math.min(getWidth(), getHeight());
int offset = (int) ((double) size / 10);
size = size - 2 * offset;
if (color == null) {
g.setColor(Color.BLACK);
g.drawOval(offset, offset, size, size);
} else {
g.setColor(color);
g.fillOval(offset, offset, size, size);
}
}
}
protected void initUI() {
JPanel gridPanel = new JPanel(new GridLayout(7, 8));
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 8; j++) {
GameButton gameButton = new GameButton(i, j);
gridPanel.add(gameButton);
}
}
JFrame frame = new JFrame(Connect4.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(gridPanel);
frame.setSize(600, 600);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Connect4().initUI();
}
});
}
}
setMaximumSize() puts a bound on how large something can be. Depending on what you are using for a layout manager you want either setPreferredSize() or setSize().