I'm trying to do something which I'd think would be fairly easy but can't find a straight forward answer to. Basically I want to change a JPanel's default shape to a circular shape (or any other shape other than a rectangle).
You will need to provide your own custom painting routines.
The other problem you will have is getting the layout managers to work with it, but you can supply your own insets to provided an area within the panel that can safely used
You'll also want to make the component transparent, to allow the area outside the circle position of the component to be transparent.
Check out
Custom Painting
JCompnent#getInsets
You might need to also manipulate the clipping rectangle of the Graphics context. This is tricky and dangerous and if you can avoid it, I would.
Updated with example
public class CirclePaneTest {
public static void main(String[] args) {
new CirclePaneTest();
}
public CirclePaneTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
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() {
setBackground(Color.RED);
setLayout(new GridBagLayout());
CirclePane circlePane = new CirclePane();
JLabel label = new JLabel("This is a test");
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
// This is a test to show the usable bounds
label.setBorder(new LineBorder(Color.RED));
circlePane.setLayout(new BorderLayout());
circlePane.add(label);
add(circlePane);
}
}
public class CirclePane extends JPanel {
public CirclePane() {
setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected int getRadius() {
// Determines the radius based on the smaller of the width
// or height, so we stay symmetrical
return Math.min(getWidth(), getHeight());
}
#Override
public Insets getInsets() {
int radius = getRadius();
int xOffset = (getWidth() - radius) / 2;
int yOffset = (getHeight() - radius) / 2;
// These are magic numbers, you might like to calculate
// your own values based on your needs
Insets insets = new Insets(
radius / 6,
radius / 6,
radius / 6,
radius / 6);
return insets;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int radius = getRadius();
int xOffset = (getWidth() - radius) / 2;
int yOffset = (getHeight() - radius) / 2;
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getBackground());
g2d.fillOval(xOffset, yOffset, radius, radius);
g2d.setColor(Color.GRAY);
g2d.drawOval(xOffset, yOffset, radius, radius);
// This is test code to test the insets/usable area bounds...
// Insets insets = getInsets();
// g2d.drawRect(xOffset + insets.left,
// yOffset + insets.top,
// (xOffset + radius) - (insets.right + insets.left),
// (yOffset + radius) - (insets.bottom + insets.top));
g2d.dispose();
}
}
}
If you want only layout to be circle you can use circle layout:
class CircleLayout implements LayoutManager
{
public void addLayoutComponent(String name,
Component comp)
{}
public void removeLayoutComponent(Component comp)
{}
public void setSizes(Container parent)
{
if (sizesSet) return;
int n = parent.getComponentCount();
preferredWidth = 0;
preferredHeight = 0;
minWidth = 0;
minHeight = 0;
maxComponentWidth = 0;
maxComponentHeight = 0;
// compute the maximum component widths and heights
// and set the preferred size to the sum of
// the component sizes.
for (int i = 0; i < n; i++)
{
Component c = parent.getComponent(i);
if (c.isVisible())
{
Dimension d = c.getPreferredSize();
maxComponentWidth = Math.max(maxComponentWidth,
d.width);
maxComponentHeight = Math.max(maxComponentHeight,
d.height);
preferredWidth += d.width;
preferredHeight += d.height;
}
}
minWidth = preferredWidth / 2;
minHeight = preferredHeight / 2;
sizesSet = true;
}
public Dimension preferredLayoutSize(Container parent)
{
setSizes(parent);
Insets insets = parent.getInsets();
int width = preferredWidth + insets.left
+ insets.right;
int height = preferredHeight + insets.top
+ insets.bottom;
return new Dimension(width, height);
}
public Dimension minimumLayoutSize(Container parent)
{
setSizes(parent);
Insets insets = parent.getInsets();
int width = minWidth + insets.left + insets.right;
int height = minHeight + insets.top + insets.bottom;
return new Dimension(width, height);
}
public void layoutContainer(Container parent)
{
setSizes(parent);
// compute center of the circle
Insets insets = parent.getInsets();
int containerWidth = parent.getSize().width
- insets.left - insets.right;
int containerHeight = parent.getSize().height
- insets.top - insets.bottom;
int xcenter = insets.left + containerWidth / 2;
int ycenter = insets.top + containerHeight / 2;
// compute radius of the circle
int xradius = (containerWidth - maxComponentWidth) / 2;
int yradius = (containerHeight - maxComponentHeight) / 2;
int radius = Math.min(xradius, yradius);
// lay out components along the circle
int n = parent.getComponentCount();
for (int i = 0; i < n; i++)
{
Component c = parent.getComponent(i);
if (c.isVisible())
{
double angle = 2 * Math.PI * i / n;
// center point of component
int x = xcenter + (int)(Math.cos(angle) * radius);
int y = ycenter + (int)(Math.sin(angle) * radius);
// move component so that its center is (x, y)
// and its size is its preferred size
Dimension d = c.getPreferredSize();
c.setBounds(x - d.width / 2, y - d.height / 2,
d.width, d.height);
}
}
}
private int minWidth = 0;
private int minHeight = 0;
private int preferredWidth = 0;
private int preferredHeight = 0;
private boolean sizesSet = false;
private int maxComponentWidth = 0;
private int maxComponentHeight = 0;
}
You can use it like this:
class CircleLayoutFrame extends JFrame
{
public CircleLayoutFrame()
{
setTitle("CircleLayoutTest");
Container contentPane = getContentPane();
contentPane.setLayout(new CircleLayout());
contentPane.add(new JButton("Yellow"));
contentPane.add(new JButton("Blue"));
contentPane.add(new JButton("Red"));
contentPane.add(new JButton("Green"));
contentPane.add(new JButton("Orange"));
contentPane.add(new JButton("Fuchsia"));
contentPane.add(new JButton("Indigo"));
}
}
Read this article: http://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html
It shows in details how to create oval transparent windows. From the code [see How to Implement a Shaped Window section]:
Translucency:
TranslucentWindowDemo tw = new TranslucentWindowDemo();
// Set the window to 55% opaque (45% translucent).
tw.setOpacity(0.55f);
Oval:
Oval:addComponentListener(new ComponentAdapter() {
// Give the window an elliptical shape.
// If the window is resized, the shape is recalculated here.
#Override
public void componentResized(ComponentEvent e) {
setShape(new Ellipse2D.Double(0,0,getWidth(),getHeight()));
}
});
setUndecorated(true);
setSize(300,200);
Demo:
public ShapedWindowDemo() {
super("ShapedWindow");
setLayout(new GridBagLayout());
// It is best practice to set the window's shape in
// the componentResized method. Then, if the window
// changes size, the shape will be correctly recalculated.
addComponentListener(new ComponentAdapter() {
// Give the window an elliptical shape.
// If the window is resized, the shape is recalculated here.
#Override
public void componentResized(ComponentEvent e) {
setShape(new Ellipse2D.Double(0,0,getWidth(),getHeight()));
}
});
setUndecorated(true);
setSize(300,200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new JButton("I am a Button"));
}
Related
First question ever here. I'm trying to scale a custom drawing with JSlider. However, it doesn't do anything and I cannot for the life of me figure out why. My code grabs a custom shape and draws it fine initially, but it won't scale.
class DrawFrame extends JFrame {
private int CarWidth = 50;
private CarShape shape = new CarShape(150, 150, CarWidth);
public DrawFrame()
{
setTitle("Draw a Car");
setSize(400, 400);
JSlider slider = new JSlider(JSlider.VERTICAL, 1, 100, 50);
slider.setMajorTickSpacing(5);
slider.setPaintTicks(true);
slider.addChangeListener((new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
int x = (int)source.getValue();
CarWidth = x;
repaint();
}
}));
add(slider, BorderLayout.WEST);
add(shape);
}
}
public class CarShape extends JPanel {
private int x;
private int y;
private int width;
public CarShape(int x, int y, int width)
{
this.x = x;
this.y = y;
this.width = width;
}
public void update(int x){
x = width;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body
= new Rectangle2D.Double(x, y + width / 6,
width - 1, width / 6);
Ellipse2D.Double frontTire
= new Ellipse2D.Double(x + width / 6, y + width / 3,
width / 6, width / 6);
Ellipse2D.Double rearTire
= new Ellipse2D.Double(x + width * 2 / 3, y + width / 3,
width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1
= new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2
= new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3
= new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4
= new Point2D.Double(x + width * 5 / 6, y + width / 6);
Line2D.Double frontWindshield
= new Line2D.Double(r1, r2);
Line2D.Double roofTop
= new Line2D.Double(r2, r3);
Line2D.Double rearWindshield
= new Line2D.Double(r3, r4);
g2.draw(body);
g2.draw(frontTire);
g2.draw(rearTire);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
}
public class SliderTester {
public static void main(String[] args)
{
DrawFrame frame = new DrawFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Every time the change listener is called, it creates a new CarShape object, but this has no effect on the CarShape object that is displayed. Better perhaps would be to resize the visualized object... OK, did you just change the code on me or am I imagining things?
Now you're changing CarWidth (which should be re-named carWidth), but that's not going to change the state of the visualized CarShape object. Instead give your CarShape class a setCarWidth(int width) method, one that changes its state, and then call that method within your stateChange method.
e.g.,
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
#SuppressWarnings("serial")
class DrawFrame extends JFrame {
private int carWidth = 50;
private CarShape shape = new CarShape(150, 150, carWidth);
public DrawFrame() {
setTitle("Draw a Car");
setSize(400, 400);
JSlider slider = new JSlider(JSlider.VERTICAL, 1, 100, 50);
slider.setMajorTickSpacing(5);
slider.setPaintTicks(true);
slider.addChangeListener((new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
carWidth = (int) source.getValue();
shape.setCarWidth(carWidth);
repaint();
}
}));
add(slider, BorderLayout.WEST);
add(shape);
}
}
#SuppressWarnings("serial")
class CarShape extends JPanel {
private int x;
private int y;
private int width;
public CarShape(int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
}
public void setCarWidth(int w) {
this.width = w;
}
// this method is just messed up -- you're setting the parameter!
public void update(int x) {
x = width; // no!!!
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6,
width - 1, width / 6);
Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y
+ width / 3, width / 6, width / 6);
Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y
+ width / 3, width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1 = new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2 = new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4 = new Point2D.Double(x + width * 5 / 6, y + width / 6);
Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
Line2D.Double roofTop = new Line2D.Double(r2, r3);
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);
g2.draw(body);
g2.draw(frontTire);
g2.draw(rearTire);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
}
public class SliderTester {
public static void main(String[] args) {
DrawFrame frame = new DrawFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
as you see, 5x5 window is contiguous to 0,0 coordinates. I want to move them like 10,10. How can I do it ?
I think the problem is in my BorderLayout. North and East is freee. How can i fill them with 10,10 dimension
import java.awt.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SOS game = new SOS(5);
// JPanel for sos template
SOSCanvas window = new SOSCanvas(game);
// JPanel which includes info panel, time and exit panel
SOSGUIPanel info = new SOSGUIPanel(window, "Cihangir", "Mercan");
JFrame jf = new JFrame();
jf.setTitle( "SOS game");
jf.setSize(340, 450);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
jf.setLayout( new BorderLayout() );
jf.add(window, BorderLayout.CENTER );
jf.add(info, BorderLayout.SOUTH );
}
}
The grid is provided with this class;
import java.awt.*;
import javax.swing.*;
public class SOSCanvas extends JPanel {
// PROPERTIES
protected SOS game;
// CONSTRUCTORS
public SOSCanvas( SOS game )
{
this.game= game;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int dimension = game.getDimension();
g.drawRect (0, 0, 300, 300);
// drawing horizontal lines
for ( int i = 1; i < dimension; i++ )
{
g.drawLine(0, i * (300 / dimension), 300, i * (300 / dimension) );
}
// drawing vertical lines
for ( int i = 1; i < dimension; i++ )
{
g.drawLine( i * (300 / dimension), 0, i * (300 / dimension), 300);
}
}
}
The first thing you need to do, is stop relying on magic numbers, you need to know the current size of the component.
Instead of using g.drawRect (0, 0, 300, 300);, you need to get the component's current width and height. From this you can make decisions about how you might handle the differences in your expectations and the actual width and height of the component.
For example, the following is a scalable grid, which can grow and shrink based on the available space, but will always remain square. It will always attempt to center itself within the available space as well...
public class SOSCanvas extends JPanel {
public SOSCanvas() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int dimension = 5;//game.getDimension();
int width = getWidth() - 1;
int height = getHeight() - 1;
int cellSize = Math.min(width, height);
int xOffset = (width - cellSize) / 2;
int yOffset = (height - cellSize) / 2;
g.drawRect(xOffset, yOffset, cellSize, cellSize);
// drawing horizontal lines
for (int i = 1; i < dimension; i++) {
g.drawLine(
xOffset,
yOffset + (i * (cellSize / dimension)),
xOffset + cellSize,
yOffset + (i * (cellSize / dimension)));
}
// drawing vertical lines
for (int i = 1; i < dimension; i++) {
g.drawLine(
xOffset + (i * (cellSize / dimension)),
yOffset,
xOffset + (i * (cellSize / dimension)),
yOffset + cellSize);
}
}
}
Even if you wanted a static grid of (lets say) 300x300, the x offset you would need to center the grid horizontally would be calculated using something like xOffset = (getWidth() - 300) / 2, then all you horizontal drawing would need to start from this offset...
So this is going to be very basic, but I can't figure it out. I have a CarIcon, which implements an Icon interface and a Resizable interface (I'll be implementing that later) and I have everything set up, but I can't figure out how to add my icon in to the JPanel or JFrame from main. I mean, I don't have the Component and Graphics information in order to call paintIcon method in my CarIcon class, so what do I do?
Here's some relevant code:
CarIcon
import java.awt.*;
import java.awt.geom.*;
public class CarIcon implements Icon, Resizable{
private int width;
/**
* Construct a car of a given width.
* #param width: the width of the car
*/
public CarIcon(int aWidth){
width = aWidth;
}
public int getIconWidth(){
return width;
}
public int getIconHeight(){
return width/2;
}
public void paintIcon(Component c, Graphics g, int x, int y){
Graphics2D g2 = (Graphics2D) g;
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6, width -1, width / 6);
Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y + width / 3, width / 6, width /6);
Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y + width / 3, width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1 = new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2 = new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4 = new Point2D.Double(x + width * 5 /6, y + width / 6);
Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
Line2D.Double roofTop = new Line2D.Double(r2, r3);
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);
g2.fill(frontTire);
g2.fill(rearTire);
g2.setColor(Color.RED);
g2.fill(body);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
#Override
public void resize(int y) {
// TODO Auto-generated method stub
width += y;
}
#Override
public void setIconWidth(int x) {
// TODO Auto-generated method stub
width = x;
}
}
SliderTester (main)
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SliderTester extends JPanel{
private static final int DEFAULT_WIDTH = 400;
private static final int DEFAULT_HEIGHT = 200;
final static Icon myCar = new CarIcon(20);
final static JPanel panel = new JPanel();
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame = new JFrame();
frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Thanks for your help in advance and Happy Easter!
The following won't work:
final static Icon myCar = new CarIcon(20);
final static JLabel label = new JLabel(myCar);
Compiler says "The constructor JLabel(Icon) is undefined"
Another edit:
Can there be something wrong with my Eclipse? Here is a screen shot where I take your code and paste it in my program and it throws an error:
Icons don't naturally go into JPanels, but they do naturally and easily go into JLabels, and then JLabels can easily go into a JPanel, and I think that this is exactly what you should do:
Create a JLabel with your Icon. You can do this either by passing the Icon into the JLabel's constructor or by calling setIcon(...) on the JLabel.
Add your JLabel to your JPanel using whatever appropriate layout manager you need
Add that JPanel to another JPanel or to a top level window or wherever it needs to go.
If you've implemented your Icon appropriately, you'd do something like:
JLabel myLabel = new JLabel(myIcon);
myPanel.add(myLabel);
Or for example with your code:
JLabel label = new JLabel(myCar);
panel.add(label);
JFrame frame = new JFrame();
frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
frame.add(panel);
Or more simply:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JLabel label = new JLabel(new CarIcon(40));
JPanel panel = new JPanel();
panel.add(label);
JOptionPane.showMessageDialog(null, panel);
}
});
}
Here's my whole test program:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class TestCarIcon {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JLabel label = new JLabel(new CarIcon(80));
JPanel panel = new JPanel();
panel.add(label);
JOptionPane.showMessageDialog(null, panel);
}
});
}
}
class CarIcon implements Icon, Resizable {
private int width;
/**
* Construct a car of a given width.
*
* #param width
* : the width of the car
*/
public CarIcon(int aWidth) {
width = aWidth;
}
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return width / 2;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2 = (Graphics2D) g;
// !! Added to smooth images
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Rectangle2D.Double body = new Rectangle2D.Double(x, y + width / 6,
width - 1, width / 6);
Ellipse2D.Double frontTire = new Ellipse2D.Double(x + width / 6, y
+ width / 3, width / 6, width / 6);
Ellipse2D.Double rearTire = new Ellipse2D.Double(x + width * 2 / 3, y
+ width / 3, width / 6, width / 6);
// The bottom of the front windshield
Point2D.Double r1 = new Point2D.Double(x + width / 6, y + width / 6);
// The front of the roof
Point2D.Double r2 = new Point2D.Double(x + width / 3, y);
// The rear of the roof
Point2D.Double r3 = new Point2D.Double(x + width * 2 / 3, y);
// The bottom of the rear windshield
Point2D.Double r4 = new Point2D.Double(x + width * 5 / 6, y + width / 6);
Line2D.Double frontWindshield = new Line2D.Double(r1, r2);
Line2D.Double roofTop = new Line2D.Double(r2, r3);
Line2D.Double rearWindshield = new Line2D.Double(r3, r4);
g2.fill(frontTire);
g2.fill(rearTire);
g2.setColor(Color.RED);
g2.fill(body);
g2.draw(frontWindshield);
g2.draw(roofTop);
g2.draw(rearWindshield);
}
#Override
public void resize(int y) {
width += y;
}
#Override
public void setIconWidth(int x) {
width = x;
}
}
interface Resizable {
void resize(int y);
void setIconWidth(int x);
}
Which shows this:
For some reason painting an image does not appear to work on my Graphics object, please see the Triangle -> paintComponent() method.
Could I be missing something critical? Perhaps to do with not casting the Graphics object to Graphics2D? A transparency mask?
final JFrame jFrame = new JFrame();
jFrame.add(new Triangle());
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(500,500);
jFrame.repaint();
jFrame.setVisible(true);
Triangle class:
public class Triangle extends JPanel {
private int x = 50;
private int y = 50;
private int width = 100;
private int length = 300;
private int direction = 0;
private BufferedImage renderedImage = null;
private Image getRenderedImage() {
if (renderedImage == null) {
BufferedImage image = new BufferedImage(100, 300, BufferedImage.TYPE_3BYTE_BGR);
drawGraphics(image.getGraphics());
}
return renderedImage;
}
private void drawGraphics(Graphics graphics) {
graphics.setColor(Color.BLUE);
// left
graphics.drawLine(
(int) Math.round(x - (width / 2.0)), y,
(int) x, y + length);
// right
graphics.drawLine(
(int) x, y + length,
(int) Math.round(x + (width / 2.0)), y);
// bottom
graphics.drawLine(
(int) Math.round(x - (width / 2.0)), y,
(int) Math.round(x + (width / 2.0)), y);
}
protected void paintComponent(Graphics graphics) {
// this works
//drawGraphics(graphics);
// this doesn't work
graphics.drawImage(getRenderedImage(), 0, 0, null);
}
}
In connection with question Resizing a component without repainting is my question how to create resiziable custom Graphics2d in form
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ZoomWithSelectionInViewport implements MouseWheelListener {
private JComponent b;
private int hexSize = 3;
private int zoom = 80;
private JScrollPane view;
public ZoomWithSelectionInViewport() throws Exception {
b = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(700, 700);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = ((Graphics2D) g);
int vertOffsetX, vertOffsetY, horizOffsetX, horizOffsetY;
vertOffsetX = (int) ((double) hexSize * Math.sqrt(3.0f));
vertOffsetY = (int) ((double) -hexSize - 1 * Math.sqrt(3.0f) / 2.0f);
horizOffsetX = (int) ((double) hexSize * Math.sqrt(3.0f));
horizOffsetY = (int) ((double) hexSize + 1 * Math.sqrt(3.0f) / 2.0f);
for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
int[] xcoords = new int[6];
int[] ycoords = new int[6];
for (int i = 0; i < 6; i++) {
xcoords[i] = (int) ((hexSize + x * horizOffsetX + y * vertOffsetX)
+ (double) hexSize * Math.cos(i * 2 * Math.PI / 6));
ycoords[i] = (int) (((getSize().height / 2) + x * horizOffsetY
+ y * vertOffsetY) + (double) hexSize * Math.sin(i * 2 * Math.PI / 6));
}
g2d.setStroke(new BasicStroke(hexSize / 2.5f));
g2d.setColor(Color.GRAY);
g2d.drawPolygon(xcoords, ycoords, 6);
}
}
}
};
view = new JScrollPane(b);
b.addMouseWheelListener(this);
JFrame f = new JFrame();
f.setLocation(10, 10);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(view);
f.setPreferredSize(b.getPreferredSize());
f.pack();
f.setVisible(true);
}
public void mouseWheelMoved(MouseWheelEvent e) {
zoom = 100 * -Integer.signum(e.getWheelRotation());
if (hexSize - Integer.signum(e.getWheelRotation()) > 0) {
hexSize -= Integer.signum(e.getWheelRotation());
}
Dimension targetSize = new Dimension(b.getWidth() + zoom, b.getHeight() + zoom);
b.setPreferredSize(targetSize);
b.setSize(targetSize);
b.revalidate();
b.repaint();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
ZoomWithSelectionInViewport example = new ZoomWithSelectionInViewport();
} catch (Exception ex) {
//
}
}
});
}
}
If I understand correctly, you want the scroll pane's scroll bars to reflect the current zoom state. I see two alternatives:
Don't override getPreferredSize() in the component, and adjust the preferred size in the mouse listener to include the zoomed image; it appears slightly truncated on the right.
Do override getPreferredSize() in the component, and adjust the returned Dimension (now a constant) to include the zoomed boundary implicit in paintComponent().
I'd prefer the latter. I've also found it helpful to write explicit transformation functions to convert zoomed and un-zoomed coordinates, as shown here. An inverse AffineTransform, shown here, is also possible.