I have been having a some issues with Swing lately. I'm trying to make something like in the image below on the fly, to illustrate the data structures for algorithms.
(source: ius.edu)
All that I was trying to do in the following class is draw out some rectangles with numbers on them. and translate them, however the last rectangle draws at 0,0. I'm stumped.
If you add the JPanel (commented out) after the loop then it draws as expected.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DrawingRect {
public static void main(String[] args) {
DrawingRect d = new DrawingRect();
}
public DrawingRect() {
JFrame frame = new JFrame("Drawing a rect");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel swingPanel = new JPanel();
swingPanel.setPreferredSize(new Dimension(500, 500));
swingPanel.setVisible(true);
swingPanel.setLayout(new BorderLayout(0, 0));
int base = 15;
for (int i = 1; i <= 25; i++) {
Graphic re = new Graphic(i);
//translating the graphic
re.setBounds(base + 30 * i, base + 20 * i, 110, 110);
swingPanel.add(re);
}
// if say I add a JPanel in here as the last element
// then the boxes will draw correctly.
//swingPanel.add(new JPanel());
swingPanel.setPreferredSize(new Dimension(800, 600));
frame.getContentPane().add(swingPanel);
frame.pack();
frame.setVisible(true);
}
public class Graphic extends JComponent {
private static final long serialVersionUID = 1L;
private static final int PREF_W = 100;
private static final int PREF_H = 100;
int id;
public Graphic(int id) {
this.id = id;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
g2.setColor(Color.black);
g2.drawRoundRect(0, 0, 30, 30, 20, 20);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("", Font.PLAIN, 13);
g2.setFont(font);
g2.drawString("" + id, 15, 20);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
}
The default layout of JPanel is FlowLayout, but you're positioning components as if the layout were null. Moreover, you're drawing content that a nested component could do automatically.
Instead, add several JPanels containing JLabel's to a containing panel having GridLayout. Use empty panels as required. Override paintComponent() in the outer panel to draw connecting lines.
Addendum: Anytime one is tempted to use Absolute Positioning, JInternalFrame may be an alternative. Related examples may be found here and here.
Addendum: If the project grows beyond the prototype stage, also consider a Custom Layout Manager.
It is because you are using BorderLayout. You can test this by changing your add line to include a specific layout location: swingPanel.add(re,BorderLayout.SOUTH).
As for a solution, why not draw all the rectangles on a picture and update it at the end of the loop? Or is the adding to JPanel part of your demonstration?
Related
I want to draw lines and more on a JPanel, add that to a JFrame and using .pack() afterwards. My problem is that I dont get how to use a Layout Manager in that particular case. Usually I add a button or something to the panel by using a gridBagLayout and I totally understand that. But with graphics 2D I kind of just draw directly to the panel. Therfore I cant use .pack() properly. Does somebody know how to pack() that jPanel the right way? My code looks like that:
public class NetworkViewPanel extends JPanel implements KeyListener, ActionListener {
public NetworkViewPanel(NetworkAI network) {
this.network = network;
this.netList = network.getLayerList();
addKeyListener(this);
setFocusable(true);
this.setLayout(new GridLayout(2, 2, 2, 2)); // does that even make sense ?
}
public void paint(Graphics g) {
super.paint(g);
g2 = (Graphics2D) g;
if (showStandardView) {
drawRectangles();
drawLines();
} else {
drawRectangles();
drawLinesSpecial(listIndex, xIndex);
}
}
Greetings :)
You can layout a JPanel with a layout manager, and do custom painting on top of it.
This does not prevent you from using pack()1.
The following mre 2 demonstrates painting a line on a JPanel using a GridLayout:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class NetworkViewPanel extends JPanel{
private final List<JLabel> labels;
public NetworkViewPanel() {
this.setLayout(new GridLayout(2, 2, 2, 2));
this.setPreferredSize(new Dimension(400,300));//used by pack()
labels = new ArrayList<>();
addLabels(new String[]{ "A", "B" , "C" , "D"});
}
private void addLabels(String[] text){
for(String t: text){
JLabel label = new JLabel(t);
label.setBorder(BorderFactory.createLineBorder(Color.BLUE));
label.setHorizontalAlignment(JLabel.CENTER);
add(label);
labels.add(label);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //draw panel as layed out by layout manager
drawLines(g);
}
private void drawLines(Graphics g) {
//draw line between centers of first and last components
int x1 = labels.get(0).getBounds().x + labels.get(0).getBounds().width /2;
int y1 = labels.get(0).getBounds().y + labels.get(0).getBounds().height /2;
int x2 = labels.get(labels.size()-1).getBounds().x + labels.get(labels.size()-1).getBounds().width/2;
int y2 = labels.get(labels.size()-1).getBounds().y + labels.get(labels.size()-1).getBounds().height/2;
g.setColor(Color.RED);
g.drawLine(x1, y1, x2, y2);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new NetworkViewPanel());
f.pack();
f.setVisible(true);
}
}
1 See: What does .pack() do?
2 Consider posting mre when asking or answering
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 am trying to create a translucent window which has no border or background other than the JLabel image's I put in it, using OverlayLayout and an extended JPanel...
My problem is when I try to add more components over the one I initially added which would be the background, I have no idea how to enable changing of the new components position.. x,y etc...
Please if possible show me what I can do and don't just point me to layoutmanagers, I need an example please if anyone is willing to show me.
Or better yet, show me what I need to do to my code in order to get the desired effect.. like changing "text" (A JLabel) position to be 10,10 ... x and y.
package core;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.OverlayLayout;
public class App {
// Window & Panel...
public JWindow frame;
public TranslucentPanel panel;
// OverlayLayout
public LayoutManager overlay;
// Components
public JLabel bg;
public JLabel test;
// Constructor
public App() {
try {
// Basics...
frame = new JWindow();
frame.setBackground(new Color(0, 0, 0, 0));
// Overlay
panel = new TranslucentPanel();
overlay = new OverlayLayout(panel);
panel.setLayout(overlay);
frame.setContentPane(panel);
// initComponents
initComponents();
// Finalize Frame
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
catch(Exception e) {
e.printStackTrace();
}
}
// Initialize Additional Components
public void initComponents() throws Exception {
test = new JLabel("test");
test.setForeground(Color.WHITE);
frame.add(test);
bg = new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/ball.png"))));
frame.add(bg);
// What must I do to be able to do this???
test.setLocation(10, 0);
}
// TranslucentPanel Class...
public class TranslucentPanel extends JPanel {
private static final long serialVersionUID = 1L;
public TranslucentPanel() {
setOpaque(false);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(0.0f));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
}
}
}
One way would be to discard the Overlayout manager, set the TranslucentPanel's layout manager to something like BorderLayout and use the JLabel, bg as a container in of itself...
bg = new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/ball.png"))));
frame.add(bg);
// Set the layout of the JLabel
bg.setLayout(new GridBagLayout());
test = new JLabel("test");
test.setForeground(Color.WHITE);
// Add the test label to the bg JLabel...
bg.add(test);
Personally, I don't like this, as JLabel doesn't take into consideration the components (or the layout manager) when it makes it's calculations for it's preferred size.
Personally, I would create a custom background component that was responsible for painting the background image. Then, onto this, I would place the other components, using what ever combination of components and layout managers I need to produce the desired results.
Pixel perfect layouts are an illusion within modern UI design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
After reading the following pieces from your codes :
// What must I do to be able to do this???
test.setLocation(10, 0);
If I understand correctly , you want to arrange position of your component based on custom coordinates. If so then You can use Insets class http://docs.oracle.com/javase/7/docs/api/java/awt/Insets.html to achieve that.
So you can set position of your component according to position you want
Insets insets = panel.getInsets();
Dimension size =test.getPreferredSize();
// just replace 10 & 0 according to X & Y postion you want.
test.setBounds(10 + insets.left, 0 + insets.top,size.width, size.height);
Here is you modified version:
*Note that I don't have your Icon , so I just put text on your label to help you see the result.
import java.awt.*;
import javax.swing.*;
public final class App{
// Window & Panel...
public JWindow frame;
public TranslucentPanel panel;
// OverlayLayout
public LayoutManager overlay;
// Components
public JLabel bg;
public JLabel test;
// Constructor
public App() {
try {
// Basics...
frame = new JWindow();
// Overlay
// Overlay
panel = new TranslucentPanel();
overlay = new OverlayLayout(panel);
panel.setLayout(overlay);
frame.add(panel);
initComponents();
// Finalize Frame
frame.pack();
frame.setSize(400,400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
catch(Exception e) { e.printStackTrace();}
}
// Initialize Additional Components
public void initComponents() throws Exception {
test = new JLabel("test");
test.setForeground(Color.RED);
panel.setLayout(null);
panel.add(test);
Insets insets = panel.getInsets();
Dimension size =test.getPreferredSize();
test.setBounds(10 + insets.left, 0 + insets.top,
size.width, size.height);
frame.add(panel);
}
// TranslucentPanel Class...
class TranslucentPanel extends JPanel {
private static final long serialVersionUID = 1L;
public TranslucentPanel() {
setOpaque(false);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(0.0f));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
}
}
public static void main (String args []){
App ap = new App();
}
}
The output :
If you declare your position as test.setBounds(500 + insets.left, 10 + insets.top,size.width, size.height); then the output would be :
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.
If you create a JScrollPane that has a viewport larger than the JScrollPane's component, it displays that component in the upper left.
Is there any way to change this behavior so it displays the component centered?
example program below.
clarification:
I have a component which has (width, height) = (cw,ch).
I have a JScrollPane with a viewport which has (width, height) = (vw, vh).
The component may become larger or smaller. I would like a way to use the scrollbars to position the component's centerpoint relative to the viewport's center point, so if the one or both of the component's dimensions are smaller than the viewport, the component shows up in the center of the viewport.
The default scrollpane behavior positions the component's upper left corner relative to the viewport's upper left corner.
All I'm asking is how to change the reference point. I am not sure how easy this is to do with the default JScrollPane, so if it's not easy, then I'll learn what I can and think of a different approach.
package com.example.test.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class SimpleScroller extends JFrame {
static class Thingy extends JPanel
{
private double size = 20.0;
#Override public Dimension getPreferredSize() {
int isize = (int) this.size;
return new Dimension(isize, isize);
}
#Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
int[] x = {0, 100, 100, 0, 0, 75, 75, 25, 25, 50};
int[] y = {0, 0, 100, 100, 25, 25, 75, 75, 50, 50};
Graphics2D g2d = (Graphics2D) g;
AffineTransform at0 = g2d.getTransform();
g2d.scale(size/100, size/100);
g.drawPolyline(x, y, x.length);
g2d.setTransform(at0);
}
public void setThingySize(double size) {
this.size = size;
revalidate();
repaint();
}
public double getThingySize() { return this.size; }
}
public SimpleScroller(String title) {
super(title);
final Thingy thingy = new Thingy();
setLayout(new BorderLayout());
add(new JScrollPane(thingy), BorderLayout.CENTER);
final SpinnerNumberModel spmodel =
new SpinnerNumberModel(thingy.getThingySize(),
10.0, 2000.0, 10.0);
spmodel.addChangeListener(new ChangeListener() {
#Override public void stateChanged(ChangeEvent e) {
thingy.setThingySize((Double) spmodel.getNumber());
}
});
add(new JSpinner(spmodel), BorderLayout.NORTH);
}
public static void main(String[] args) {
new SimpleScroller("simple scroller").start();
}
private void start() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
}
Put a JPanel into the scroll-pane
Set the layout of the panel to a GridBagLayout.
Put one component into the panel with no constraint. It will be centered.
This is the technique used in the Nested Layout Example, which places the red/orange image into the center of the parent.
I simply added a JPanelto the JScrollPane to which I added the THINGY JPanel. Hope this is what you wanted :
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class SimpleScroller extends JFrame {
static class Thingy extends JPanel
{
private double size = 20.0;
#Override public Dimension getPreferredSize() {
int isize = (int) this.size;
return new Dimension(isize, isize);
}
#Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
int[] x = {0, 100, 100, 0, 0, 75, 75, 25, 25, 50};
int[] y = {0, 0, 100, 100, 25, 25, 75, 75, 50, 50};
Graphics2D g2d = (Graphics2D) g;
AffineTransform at0 = g2d.getTransform();
g2d.scale(size/100, size/100);
g.drawPolyline(x, y, x.length);
g2d.setTransform(at0);
}
public void setThingySize(double size) {
this.size = size;
revalidate();
repaint();
}
public double getThingySize() { return this.size; }
}
public SimpleScroller(String title) {
super(title);
final Thingy thingy = new Thingy();
setLayout(new BorderLayout());
JPanel panel = new JPanel();
panel.add(thingy);
JScrollPane scroll = new JScrollPane();
scroll.setViewportView(panel);
add(scroll, BorderLayout.CENTER);
final SpinnerNumberModel spmodel =
new SpinnerNumberModel(thingy.getThingySize(),
10.0, 2000.0, 10.0);
spmodel.addChangeListener(new ChangeListener() {
#Override public void stateChanged(ChangeEvent e) {
thingy.setThingySize((Double) spmodel.getNumber());
}
});
add(new JSpinner(spmodel), BorderLayout.NORTH);
}
public static void main(String[] args) {
new SimpleScroller("simple scroller").start();
}
private void start() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
}
Here is the output :
Accepting your both comments, but
yes... although I'd really like to arbitrarily position it
there are
extract or to create own JViewport
important methods to avoiding flickering or jumping on the JViewport
JViewport.setScrollMode(JViewport.BLIT_SCROLL_MODE);
JViewport.setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
JViewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
use GlassPane (is very simple to set for possition to the desired Point or/and Dimmension)
and
I don't want to paint to the JViewport, at least not directly -- My real code is a component subclassed from JPanel, as in my example, but it also has other GUI elements in it.
you can use JXLayer (Java6) or parts of methods from JXLayer implemented to the JLayer (Java7) directly
could be too hard, but in all cases you can overlay whatever with JLabel
Notice
all my suggestion to consume MouseEvent by default, J(X)Layer and JLabel with KeyEvents too, but there no issue with redispatch events from ---> to another JComponent
EDIT
wait: no, I don't want to paint the spiral (what you called "snake") in the center of the JScrollPane; I want to paint my component and have my component be displayed in the center of the JScrollPane.
your JPanel could be wider than JViewport by default
a) JPanel returns Dimension
b) JViewport return Dimension
then without any issue, flickering or freeze
a) you can centering Dimmension from JPanel (Point) to the center of JViewport (Point)
b) if is there JComponent you can move the JViewport to any getBounds() tha returned JComponent or Point from JPanel inside JScrollPane
With the suggestion from Andrew, you can't have the view smaller than the viewport (and still centered).
The cleanest way is doing it directly from the layout manager, overriding ViewportLayout. There is an implementation in the source of:
http://www.randelshofer.ch/multishow
Look for the class ZoomableViewportLayout, which does the job perfectly.