Reputation: 6870
I am just starting to put together a logging tool for my own use that would log statistics from gym/running and the only experience I have with swing/awt is active rendering for games where you have full control over the Graphics2D object and don't rely on implementing swing components with overriden paints.
Anyway, I was hoping to create a dummy JComponent that I can add to one of my panels (this panel will display graphics, statistics etc depending on what I select from another different sidepanel with options) that does nothing else but listen for mouseevents inside the panel mentioned earlier and draws a selection rectangle on mousedrags so that I can zoom in the data if higher resolutions exist. I just don't know how, I have added the component to the panel but it registers nothing inside the panel, instead it seems to have a local space that is limited to the bottom of the panel/frame.
Here is the component
package gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JComponent;
@SuppressWarnings("serial")
public class MarkerRectangle extends JComponent implements MouseListener,
MouseMotionListener {
private boolean draw;
private int startX, endX, startY, endY;
private Color color = new Color(0, 255, 0, 100);
public MarkerRectangle(int width, int height) {
setPreferredSize(new Dimension(width, height));
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse clicked@ " + e.getX() + "," + e.getY());
}
@Override
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse entered @ " + e.getX() + "," + e.getY());
}
@Override
public void mouseExited(MouseEvent e) {
System.out.println("Mouse left @ " + e.getX() + "," + e.getY());
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println("Mouse pressed@ " + e.getX() + "," + e.getY());
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse was released @ " + e.getX() + "," + e.getY());
}
@Override
public void mouseDragged(MouseEvent e) {
System.out.println("Mouse being dragged, currently@ " + e.getX() + ","
+ e.getY());
}
@Override
public void mouseMoved(MouseEvent e) {
System.out.println("I registered a move@ " + e.getX() + "," + e.getY());
}
@Override
// Draw rectangle
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.dispose();
}
}
The panel
public class GraphPane extends JPanel {
public GraphPane(Graph graph) {
this.add(graph);
this.add(new MarkerRectangle(800, 600));
this.setBackground(Color.gray);
this.setPreferredSize(new Dimension(800, 600));
}
}
The graph, holds random data atm
public class Graph extends JComponent {
private GeneralPath data;
private Stroke stroke;
public Graph() {
this.data = new GeneralPath();
this.stroke = new BasicStroke(3f);
this.setPreferredSize(new Dimension(750, 550));
init();
}
private void init() {
data.moveTo(0, 0);
double cntr = 0;
double[][] points = new double[10][1];
for (double[] point : points) {
cntr += 100;
point[0] = Math.random() * 100;
point[1] = Math.random() * 100;
data.lineTo(cntr, point[1]);
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(stroke);
g2d.draw(data);
g2d.dispose();
}
}
I want to implement something like the above because eventually I imagine the GUI to become quite complex and simple events like drawing a rectangle to mark data should not be in the main controller (so as to prevent a lot of if-tests and clutter in code).
Screenshot of what I get:
What I want:
EDIT While the accepted answer below is the better solution I am posting this in the event that someone may want to use it. It will not work if you resize smaller the prefferedSize.
public class Test {
public static void main(String[] args) {
GeneralJFrame frame = new GeneralJFrame(1200, 800);
frame.addPanel(new GraphPane(new Graph()), BorderLayout.CENTER);
frame.addPanel(new ButtonPane(), BorderLayout.SOUTH);
frame.addPanel(new ButtonPane(), BorderLayout.SOUTH);
frame.addPanel(new ButtonPane(), BorderLayout.WEST);
frame.addPanel(new ButtonPane(), BorderLayout.EAST);
frame.addPanel(new ButtonPane(), BorderLayout.NORTH);
frame.start();
}
}
public class GraphPane extends JPanel {
public GraphPane(Graph graph) {
GridBagConstraints c = new GridBagConstraints();
c.gridwidth = GridBagConstraints.REMAINDER;
c.gridheight = GridBagConstraints.REMAINDER;
c.gridx = 0;
c.gridy = 0;
this.setLayout(new GridBagLayout());
this.add(graph);
this.add(new MarkerRectangle(), c);
this.setBackground(new Color(205, 201, 201));
}
}
public class MarkerRectangle extends JComponent implements MouseListener,
MouseMotionListener {
private boolean draw;
private int startX, endX, startY, endY;
public MarkerRectangle() {
this.addMouseListener(this);
this.addMouseMotionListener(this);
setOpaque(false);
setPreferredSize(new Dimension(800, 600));
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
draw = true;
}
@Override
public void mouseReleased(MouseEvent e) {
draw = false;
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
endX = e.getX();
endY = e.getY();
repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
// Draw rectangle
protected void paintComponent(Graphics g) {
System.out.println(getSize());
if (!draw)
return;
int w = endX-startX;
int h = endY - startY;
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(255, 165, 0));
g2d.fillRect(startX, startY, w, h);
g2d.dispose();
}
}
public class ButtonPane extends JPanel {
public ButtonPane() {
add(new JButton("HELLO"));
setBackground(Color.gray);
setBorder(BorderFactory.createEtchedBorder(Color.white,
Color.gray.darker()));
}
}
public class GeneralJFrame {
private JFrame frame;
public GeneralJFrame(int width, int height) {
this.frame = new JFrame("Le Muscles");
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void addPanel(JPanel panel, String location) {
this.frame.add(panel, location);
}
public void start() {
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Outputs: (orange is dragged with mouse)
Upvotes: 2
Views: 1337
Reputation: 285450
I still don't fully understand your question. As I see it, you've
So your program is behaving as expected based on your code.
The question I have is this: how is it not behaving as you expect it to?
You state:
What I want is an invisible (transparent) panel on top of the blue one in the above screenshot that is just as large as the blue one, not a subpanel that is sitting in the bottom. I want the blue one to contain this one (should not be a problem since it is just a jcomponent). What I hope to achieve is a sort over "invisible" overlay that registers mousevents so I don't have to implement these events in the blue panel.
Consider using a JLayer for this. As per the JLayer API:
JLayer is a good solution if you only need to do custom painting over compound component or catch input events from its subcomponents.
OK, I've experimented with it a bit and came up with something like this:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Path2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
@SuppressWarnings("serial")
public class GraphPane2 extends JPanel {
private static final int GRAPH_WIDTH = 1000;
private static final int GRAPH_HEIGHT = 750;
private Graph2 graph2 = new Graph2(GRAPH_WIDTH, GRAPH_HEIGHT);
public GraphPane2() {
LayerUI<Graph2> myLayerUI = new MyLayerUI<Graph2>();
JLayer<Graph2> panelLayer = new JLayer<Graph2>(graph2, myLayerUI);
setLayout(new BorderLayout());
add(panelLayer);
myLayerUI.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (MyLayerUI.MOUSE_RELEASED.equals(evt.getPropertyName())) {
Rectangle rect = (Rectangle) evt.getNewValue();
System.out.println(rect);
}
}
});
}
private static void createAndShowGui() {
GraphPane2 mainPanel = new GraphPane2();
JFrame frame = new JFrame("Graph Pane2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.setResizable(false);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class MyLayerUI<V extends JComponent> extends LayerUI<V> {
private static final Color FILL_COLOR = new Color(0, 128, 0, 128);
public static final String MOUSE_RELEASED = "mouse released";
private Point pressedPt;
private Point draggedPt;
private Rectangle rect;
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (rect != null) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(FILL_COLOR);
g2.fill(rect);
}
}
public void installUI(JComponent c) {
super.installUI(c);
((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
}
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
((JLayer)c).setLayerEventMask(0);
}
@Override
public void eventDispatched(AWTEvent e, JLayer<? extends V> l) {
MouseEvent mEvt = (MouseEvent) e;
int id = mEvt.getID();
int btn = mEvt.getButton();
if (id == MouseEvent.MOUSE_PRESSED && btn == MouseEvent.BUTTON1) {
pressedPt = mEvt.getPoint();
rect = new Rectangle(pressedPt.x, pressedPt.y, 0, 0);
}
if (id == MouseEvent.MOUSE_PRESSED && btn != MouseEvent.BUTTON1) {
pressedPt = null;
}
if (id == MouseEvent.MOUSE_DRAGGED && pressedPt != null) {
draggedPt = mEvt.getPoint();
int x = Math.min(draggedPt.x, pressedPt.x);
int y = Math.min(draggedPt.y, pressedPt.y);
int width = Math.abs(draggedPt.x - pressedPt.x);
int height = Math.abs(draggedPt.y - pressedPt.y);
rect = new Rectangle(x, y, width, height);
}
if (id == MouseEvent.MOUSE_RELEASED && pressedPt != null) {
draggedPt = mEvt.getPoint();
int x = Math.min(draggedPt.x, pressedPt.x);
int y = Math.min(draggedPt.y, pressedPt.y);
int width = Math.abs(draggedPt.x - pressedPt.x);
int height = Math.abs(draggedPt.y - pressedPt.y);
rect = new Rectangle(x, y, width, height);
firePropertyChange(MOUSE_RELEASED, null, rect);
}
l.repaint();
}
}
@SuppressWarnings("serial")
class Graph2 extends JPanel {
private static final int MAX_DATA_POINTS = 100;
private static final int STEP = 10;
private static final Stroke STROKE = new BasicStroke(3f);
private Path2D path2D;
private int width;
private int height;
private int[] data = new int[MAX_DATA_POINTS + 1];
private Random random = new Random();
public Graph2(int width, int height) {
this.width = width;
this.height = height;
init();
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
path2D = new Path2D.Double();
int w = getWidth();
int h = getHeight();
double x = 0;
double y = ((double) MAX_DATA_POINTS - data[0]) * h
/ MAX_DATA_POINTS;
path2D.moveTo(x, y);
for (int i = 1; i < data.length; i++) {
x = (i * w) / (double) MAX_DATA_POINTS;
y = ((double) MAX_DATA_POINTS - data[i]) * h
/ (double) MAX_DATA_POINTS;
path2D.lineTo(x, y);
}
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (path2D != null) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(STROKE);
g2d.draw(path2D);
}
};
private void init() {
// create and fill random data
data[0] = 0;
boolean up = true;
// max and min data values -- used for normalization
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 1; i < data.length; i++) {
up = random.nextInt(4) < 3 ? up : !up;
if (up) {
data[i] = data[i - 1] + random.nextInt(STEP);
} else {
data[i] = data[i - 1] - random.nextInt(STEP);
}
if (data[i] > max) {
max = data[i];
}
if (data[i] < min) {
min = data[i];
}
}
// normalize the data
for (int i = 0; i < data.length; i++) {
int datum = (MAX_DATA_POINTS * (data[i] - min)) / (max - min);
data[i] = datum;
}
}
}
This will look like:
Upvotes: 2