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).
-
http://stackoverflow.com/questions/3439112/java-custom-shape-panels/3440826#3440826 might help. – camickr Feb 03 '13 at 18:51
-
I looked at that before and still can't get the shape to change. – yanman1234 Feb 03 '13 at 19:01
-
I think the problem with that example is it makes two shapes act like two buttons, but doesn't actually make two circle buttons. – yanman1234 Feb 03 '13 at 19:18
3 Answers
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
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();
}
}
}

- 343,457
- 22
- 230
- 366
-
I believe you have solved my problem but is `setOpaque(false);` used to make the component transparent? I've been trying to that and it doesn't seem to do anything. I know this because I have a JPanel under this one which is being completely covered by this one. – yanman1234 Feb 03 '13 at 20:20
-
Yes, it should, but it depends on what you are doing in your `paintComponent` method... – MadProgrammer Feb 03 '13 at 20:48
-
All I'm doing is creating a `fillOval` and setting it's bounds then opaque. I know it won't change the shape of the panel but it should still make the panel below it visible right? – yanman1234 Feb 03 '13 at 21:19
-
I'm about to leave for work, I'll try and put together a quick example when I get a free moment – MadProgrammer Feb 03 '13 at 21:24
-
Tried this and wasn't able to solve the problem and the it still wasn't transparent on the outside of the panel. Nice example though – yanman1234 Feb 03 '13 at 23:52
-
Then I'd suggest there is something wrong with your code that you're going to need to share in order for use to help you fix, because this example works. – MadProgrammer Feb 04 '13 at 00:00
-
I actually just spun off of your your example that allowed me to do something that should work for my purposes. Thank you very much for your time and knowledge. – yanman1234 Feb 04 '13 at 00:04
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"));
}

- 2,517
- 8
- 22
- 33

- 114,158
- 16
- 130
- 208
-
1Thanks for the fast answer and I looked at the examples but can't find how they changed the shape of the window. Just to be sure, this would be applicable to JPanel's too right? – yanman1234 Feb 03 '13 at 18:38
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"));
}
}

- 1,398
- 10
- 27
-
-
I need the actual panel a different circle, not the layout. Thanks though. – yanman1234 Feb 03 '13 at 23:51