0

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).

mKorbel
  • 109,525
  • 20
  • 134
  • 319
yanman1234
  • 1,009
  • 9
  • 27

3 Answers3

2

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

enter image description here

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();

        }
    }
}
MadProgrammer
  • 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
0

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"));
}
AlexR
  • 114,158
  • 16
  • 130
  • 208
  • 1
    Thanks 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
0

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"));
   }
}
Vuk Vasić
  • 1,398
  • 10
  • 27