I have an undecorated JFrame with some contents on it (Labels, Images, etc.). I need the JFrame to pass all the events through it. For example: when a click is made on that JFrame, I want it to pass that click through, to the window/anything that is underneath the frame.
Problem Example:
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame f = new JFrame("Test");
f.setAlwaysOnTop(true);
Component c = new JPanel() {
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g.create();
g2.setColor(Color.gray);
int w = getWidth();
int h = getHeight();
g2.fillRect(0, 0, w,h);
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(w/4, h/4, w-2*(w/4), h-2*(h/4));
}
};
c.setPreferredSize(new Dimension(300, 300));
f.getContentPane().add(c);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
com.sun.awt.AWTUtilities.setWindowOpaque(f,false);
}
In this case, the JFrame has a border with close/minimize/fullscreen controls, and graphics on the JFrame still catch the events while just the transparent parts pass them through. I need both (transparent parts, and with graphics) to pass through the events.
Video Example of my goal: https://www.youtube.com/watch?v=irUQGDDSk_g
Similar question:
Pass mouse events to applications behind from a Java UI
This question tries to achieve a similar goal, but the JFrame is decorated (has close/minimize controls with an outer frame), and graphics still catch the user control events.
Question: How can I make a JFrame with graphics, that will not catch the events from the user controlling, but to pass in through?
This is not an answer, but a correction to the code example
The example you provide is actually doing some very dangerous things, first it's painting a translucent color onto an opaque component, this means that Swing doesn't know that it should actually be painting anything under the component and could also result in a number of very nasty paint artifacts, as Swing only knows about opaque and transparent component, it doesn't know about semi-transparent components, so you need to trick the API.
When performing custom painting, you should always call super.paintComponent to ensure that the Graphics context is setup correctly before painting. In your case, you should also make the component transparent using setOpaque and passing it false, for example...
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestFrame {
public static void main(String[] args) {
new TestFrame();
}
public TestFrame() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setUndecorated(true);
frame.setAlwaysOnTop(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2d.dispose();
}
}
}
Related
I'm trying to make a software that records the screen when a key is pressed. In order to indicate that the program is now recording, I want to put a red border around the outside of the screen. I'm having trouble getting it to work, here is my attempt so far:
public Main() {
JFrame frame = new JFrame("");
frame.setUndecorated(true);
frame.setAlwaysOnTop(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setSize((int)ss.getWidth(), (int)ss.getHeight());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.createBufferStrategy(3);
BufferStrategy bs = frame.getBufferStrategy();
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
g.setColor(Color.RED);
g.drawRect(0, 0, frame.getWidth()-1, frame.getHeight()-1);
g.dispose();
bs.show();
}
It seems like setting the background transparent makes the graphics object not able to draw onto the jframe, and setting the background of the graphics object to transparent only leaves a white background with a red border, rather than transparent. I'm completely stuck on this one at the moment so any help would be appreciated!
You can't really draw on a component that way, you would need to override paintComponent(g) to do that.
You can simply add a border object:
((JComponent) frame.getContentPane()).setBorder(new LineBorder(Color.RED, 10));
I believe the following code achieves what you want. Notes after the code.
import static java.awt.Frame.MAXIMIZED_BOTH;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
import java.awt.EventQueue;
import java.awt.Color;
import java.awt.Container;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Recorder implements Runnable {
private JFrame frame;
#Override // java.lang.Runnable
public void run() {
showGui();
}
private void showGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
if (contentPane instanceof JComponent) {
JComponent jCmpt = (JComponent) contentPane;
jCmpt.setBorder(BorderFactory.createLineBorder(Color.RED, 5, true));
}
frame.setExtendedState(MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
/**
* Start here
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Recorder());
}
}
setExtendedState() maximizes the JFrame so that it takes up the entire screen.
setUndecorated() removes the title bar and the border of the JFrame.
setBackground() makes the JFrame transparent.
setLocationRelativeTo() is optional since the JFrame is maximized.
Finally I set a thick, red, rounded border around the content pane of the JFrame.
Note that you can close the JFrame by pressing Alt+F4 keys on the computer keyboard.
Optionally, you can also add the following:
frame.setAlwaysOnTop(true);
I am currently using the Applet class to create a simple game. Because there is a flickering effect, I've added double-buffering for Graphics components by creating an off-screen buffer like so:
public class AppletTest extends Applet implements Runnable {
Thread thread;
Image img;
Graphics gfx;
public final int WIDTH = 700, HEIGHT = 500;
public void init() {
this.resize(WIDTH, HEIGHT);
thread = new Thread(this);
thread.start();
img = createImage(WIDTH, HEIGHT); // off-screen buffering
gfx = img.getGraphics();
}
public void draw(Graphics g) {
gfx.setColor(Color.BLACK);
gfx.fillRect(0, 0, WIDTH, HEIGHT);
gfx.setColor(Color.WHITE);
gfx.fillRect(50, 50, 100, 100);
gfx.setFont(new Font("Century", Font.BOLD, 30));
gfx.drawString("I feel good sometimes I don't", 200, 200);
g.drawImage(img, 0, 0, this); // draws the off-screen image
}
public void update(Graphics g) {
draw(g);
}
public void run() {
while(true) {
repaint();
try {
Thread.sleep(5);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
If you run the application, all the Graphics (.fillRect, .drawString, etc.) components/methods are drawn on the off-screen buffer. However, my goal is to add a JButton to the applet - and as expected, there's no off-screen loading for the JButton component (which means flickering).
Graphics gfx;
JButton button1;
public void draw(Graphics g) {
setLayout(null);
button1.setBounds(225, 400, 250, 50);
button1.setFont(new Font("Courier", Font.PLAIN, 17));
button1.setForeground(Color.WHITE);
button1.setBackground(Color.DARK_GRAY);
add(button1); // is it possible to draw the JButton on the off-screen buffer?
}
How would you add off-screen loading to a JButton component?
Applet (and JApplet) are officially deprecated, they are no longer supported by Java, Oracle, Browsers (or the community generally)
Swing components are, by default, double buffered. If you work with the painting system correctly, you shouldn't experience any flickering, if you do, it's clear sign that you're doing something wrong.
I would recommend having a look at Performing Custom Painting and Painting in AWT and Swing for more details about how the Swing painting system works.
Swing is single threaded AND not thread safe. This means that you should not perform any long running operations within the context of the Event Dispatching Thread and you should not update the UI from outside the context of the EDT.
Have a look at Concurrency in Swing for more details.
A simple solution to these problems is to make use a Swing Timer, which can be used to schedule regular updates which are executed within the context of the EDT.
See How to Use Swing Timers for more details...
As a basic runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public static final int WIDTH = 700, HEIGHT = 500;
public TestPane() {
setLayout(new GridBagLayout());
add(new JButton("Big fat button"));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
g2d.setColor(Color.WHITE);
g2d.fillRect(50, 50, 100, 100);
g2d.setFont(new Font("Century", Font.BOLD, 30));
g2d.drawString("I feel good sometimes I don't", 200, 200);
g2d.dispose();
}
}
}
Okay, "But I absolutely, must, no questions asked, use Applet ... 😓, then I feel sorry for you, but that doesn't change the fact that Swing is already double buffered. The above example could easily be applied to a J/Applet simply by creating an instance of the JPanel and adding to an Applet container
Swing makes use of "passive rendering" algorithm, if you absolutely must be complete control, then you can have a look at BufferStrategy which hands complete control of the painting system over to you, but you won't be able to use Swing components, as they are updated by the Swing sub-system
I am trying to make a simple java game, and i got stumped on this. The game requires that you play in a square window, but can resize the sprites on the canvas. I though it would be really annoying if I used a ComponentListener and didn't let the user change size, so I thought that I would just offset the canvas from the left and right side so that it is centered until it is square.
Right now I understand that I need to create a Canvas object for the Game, and then offset it but I'm not sure how.
Game Canvas:
public class GameCanvas extends Canvas {
public Vector size;
public GameCanvas(Vector size) {
this.size = size;
setBackground(Color.BLUE);
setSize(size.x, size.y);
}
}
Any help is appreciated :) thank you.
You could...
Make your own layout manager which maintained the Canvas in a square shape based on the available space of the parent Container.
This might be a little bit of over kill, but, it's generally more efficient and means you can keep using the properties of the Canvas (ie getWidth and getHeight), so it's more de-coupled from other solutions
You could...
Calculate a "playable area" based on the size of the Canvas and use that as the bounds range checking, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Rectangle getPlayableBounds() {
Dimension size = getSize();
int playableSize = Math.min(size.width, size.height);
int x = (size.width - playableSize) / 2;
int y = (size.height - playableSize) / 2;
return new Rectangle(x, y, playableSize, playableSize);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.draw(getPlayableBounds());
g2d.dispose();
}
}
}
This example is not optimised, as calling getPlayableBounds recalculates the playable area, regardless if its changed since the last call or not.
I'd be tempted to use a ComponentListener and make the playableBounds an instance field. When the component is resized, I'd simply invalidate the playableBounds property, which would force the getPlayableBounds to recalculate the value and cache the result
When I try to draw a JLabel or my GUI, whatever I add to my JFrame last is drawn and the rest is just never drawn or painted over. I would appreciate if you could help me find a solution to draw the JLabel at a specific place along with my GUI. I have heard about layout and how it could help me with that, but there were many different people saying different things about this subject. Here is my code.
import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.JPanel;
import javax.swing.JFrame;
import static java.lang.System.*;
import java.awt.event.*;
import java.awt.Graphics.*;
public class Main extends JPanel
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double w = screenSize.getWidth();
double h = screenSize.getHeight();
static JFrame f = new JFrame("Tic-Tac-Toe");
static JPanel p = new JPanel();
int width = (int)w;
int height = (int)h;
int width1a = width/2 - 300;
int width2a = width/2 - 100;
int width3a = width/2 + 100;
int width4a = width/2 + 300;
int height1from = (int)height - 100;
int height1to = (int)height - (int)(height/1.05);
public void paintComponent(Graphics g)
{
super.paintComponent(g);
JLabel l = new JLabel("Hello World !");
f.add(l);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(10));
g.setColor(Color.black);
g2.drawLine(width1a, height1from, width1a, height1to);
g2.drawLine(width2a, height1from, width2a, height1to);
g2.drawLine(width3a, height1from, width3a, height1to);
g2.drawLine(width4a, height1from, width4a, height1to);
}
public static void main(String[] args)
{
Main m = new Main();
f.setSize(400,300);
f.setExtendedState(Frame.MAXIMIZED_BOTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
JLabel l = new JLabel("Hello World !");
f.add(p);
f.add(m);
}
}
Please tell me if I was unclear or anything. I just want the JLabel and the GUI drawings to appear on the JFrame. Feel free to suggest anything I should redo and thank you for your time!
You don't, ever, add components to a component from within a paint method. You should never modify the state of a component in any way from within a paint method, painting is for painting, nothing else.
See Painting in AWT and Swing and
Performing Custom Painting for more details about how painting works in Swing...
You are adding three components to the same position within a BorderLayout, generally, only the last component would normally be shown, as it's the one that is been managed by the BorderLayout
See Laying Out Components Within a Container and How to Use BorderLayout for more details.
You should also make sure that you are creating your UI from within the context of the Event Dispatching Thread, see Initial Threads for more details
Painting is also contextual to the component been painted, that is, the 0x0 is the top left corner of the component, any painting done beyond the visual range of the component is simply lost.
You should also avoid the use of static, generally, this is a good indication that you have a problem with your design, it gets especially messy within a UI. If you really want to hear me whinge about the evils of static, check out this answer
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JPanel blue = new JPanel();
blue.setBackground(Color.BLUE);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JLabel("Hello world"), BorderLayout.NORTH);
frame.add(new TestPane());
frame.add(blue, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
add(new JLabel("Hello World"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// This could actually be achieved using a EmptyBorder and a LineBorder
// but this demonstrates the point...
g2d.setColor(Color.RED);
g2d.drawRect(10, 10, getWidth() - 20, getHeight() - 20);
g2d.dispose();
}
}
}
I'd like to make a Java panel that creates objects where the user clicks. Since my actual application uses a MVC approach I'd like also for these objects to be able to repaint themselves when a model is changed, and provide menus to change their properties.
I think that the best way to control their x and y locations would be to take a canvas based approach whereby the JPanel calls a draw method on these objects from the paintComponent method. This however will only draw the shape on the canvas and does not add the object itself loosing all abilities to control object properties. I'd be very grateful if someone could tell me the best approach for what I want to do.
I've created some sample code which can be seen below. When clicked I'd like the circle to change colour, which is implemented using a MouseListener (it basically represents changing the models properties in this small example). Also I'd just like to make sure that zooming in/out still works with any sample code/advice can provide so I've added buttons to zoom the objects in and out as a quick test.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.geom.Ellipse2D;
public class Main {
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ExamplePanel panel = new ExamplePanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
//I could not get this to with when it extended JLayeredPane
private static class ExamplePanel extends JPanel {
private static final int maxX = 500;
private static final int maxY = 500;
private static double zoom = 1;
private static final Circle circle = new Circle(100, 100);
public ExamplePanel() {
this.setPreferredSize(new Dimension(maxX, maxY));
this.setFocusable(true);
Button zoomIn = new Button("Zoom In");
zoomIn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoom += 0.1;
repaint();
}
});
add(zoomIn);
Button zoomOut = new Button("Zoom Out");
zoomOut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
zoom -= 0.1;
repaint();
}
});
add(zoomOut);
// add(circle); // Comment back in if using JLayeredPane
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
super.paintComponent(g);
circle.paint(g); // Comment out if using JLayeredPane
}
}
static class Circle extends JPanel {
private Color color = Color.RED;
private final int x;
private final int y;
private static final int DIMENSION = 100;
public Circle(int x, int y) {
// setBounds(x, y, DIMENSION, DIMENSION);
this.x = x;
this.y = y;
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
color = Color.BLUE;
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(color);
g2.fillOval(x, y, DIMENSION, DIMENSION);
}
// I had some trouble getting this to work with JLayeredPane even when setting the bounds
// In the constructor
// #Override
// public void paintComponent(Graphics g) {
// Graphics2D g2 = (Graphics2D) g;
// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// g2.setPaint(color);
// g2.fillOval(x, y, DIMENSION, DIMENSION);
// }
#Override
public Dimension getPreferredSize(){
return new Dimension(DIMENSION, DIMENSION);
}
}
}
As an aside I did try using a JLayeredPane(useful because I'd also like to layer my objects) but could not get my objects to even render. I know it has no default layout manager so tried calling setBounds in the circle in the constructor, but sadly it did not work. I know it's better to use a layout manager but can't seem to find one suitable for my needs!
Thanks in advance.
Don't override paint components, use paintComponent and don't forget to call super.paintComponent
A component already has a concept of "location", so when painting, the top left position of your component is actually 0x0
What you are doing is actually painting beyond the boundaries of you component
For example, if you place your Circle at 100x100 and then did...
g2.fillOval(x, y, DIMENSION, DIMENSION);
You would actually start painting at 200x200 (100 for the actual location of the component and 100 for you additional positioning).
Instead use
g2.fillOval(x, y, DIMENSION, DIMENSION);
And go back and try using JLayeredPane.
You could actually write your own layout manager that takes the location of the component and it's preferred size and updates the components bounds and then apply this to a JLayeredPane. This gives you the "benefits" of an absolute layout, but keeps you within how Swing works to update its components when things change.
You should also be careful with doing anything like...
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
The Graphics context is a shared resource. That means, anything you apply to, will still be in effect when the next component is painted. This may produce some strange results.
Instead try using...
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//...
g2.dispose();
Updated
For zooming I would take a closer look at JXLayer (or JLayer in Java 7)
The JXLayer (and excellent PBar extensions) have gone quite on the net, so you can grab a copy from here
(I tried finding a better example, but this is the best I could do with the limited time I have available)
Updated with working zooming example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
public class TestJLayerZoom {
public static void main(String[] args) {
new TestJLayerZoom();
}
public TestJLayerZoom() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JXLayer<JComponent> layer;
private DefaultTransformModel transformModel;
private JPanel content;
public TestPane() {
content = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
JLabel label = new JLabel("Hello");
JTextField field = new JTextField("World", 20);
content.add(label, gbc);
content.add(field, gbc);
gbc.gridy++;
gbc.gridwidth = GridBagConstraints.REMAINDER;
final JSlider slider = new JSlider(50, 200);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
double scale = value / 100d;
transformModel.setScale(scale);
}
});
content.add(slider, gbc);
transformModel = new DefaultTransformModel();
transformModel.setScaleToPreferredSize(true);
Map<RenderingHints.Key, Object> hints = new HashMap<>();
//hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
//hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
layer = TransformUtils.createTransformJXLayer(content, transformModel, hints);
setLayout(new BorderLayout());
add(layer);
}
}
}
I've left the rendering hints in to demonstrate their use, but I found that they screwed with the positing of the cursor within the text field, but you might like to have a play
I'd just like to add that I fixed the zooming issue not in the way suggested by the answer, but just by keeping the line that applied a scaled transform call in the ExamplePanel paintComponent method:
g2.scale(zoom, zoom);
I thought that this was the nicest implementation since none of the components require any knowledge about zooming and it seemed far simpler than JLayer since I only required basic zooming functionalities.