looking to create a JPanel border like that highlighted in the image in red below.. i.e. triangle at one side or two sides. I've successfully implement rounded JPanel borders using the info here and here. Any ideas or tips?
Asked
Active
Viewed 503 times
1
-
2Not "quite" the same, but still a [good demonstration](http://stackoverflow.com/questions/19835289/java-swing-creating-tag-like-dynamic-width-buttons/19851317#19851317) of complex "non-traditional" component design. You should also take a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) – MadProgrammer Apr 24 '15 at 12:06
2 Answers
3
You might be able to use a FlowLayout
:
FlowLayout
: Set to a negative value in a horizontal gap.Override JRadioButton#contains(int, int)
Override JPanel#isOptimizedDrawingEnabled()
import java.awt.*;
import java.awt.geom.*;
import java.io.Serializable;
import java.util.*;
import java.util.List;
import javax.swing.*;
public final class FlowLayoutOverlapTest {
private static final int BORDER = 1;
public JComponent makeUI() {
JPanel p = new JPanel(new GridLayout(0, 1));
p.setBorder(BorderFactory.createEmptyBorder(20, 2, 20, 2));
p.add(makeBreadcrumbList(0, Color.PINK, Arrays.asList("overlap:", "0px", "button")));
p.add(makeBreadcrumbList(5, Color.CYAN, Arrays.asList("overlap:", "5px", "button")));
p.add(makeBreadcrumbList(9, Color.ORANGE, Arrays.asList("overlap:", "9px", "button")));
return p;
}
private static AbstractButton makeButton(String title, Color color) {
final ToggleButtonBarCellIcon icon = new ToggleButtonBarCellIcon();
AbstractButton b = new JRadioButton(title) {
//http://java-swing-tips.blogspot.jp/2008/11/rounded-corner-jbutton.html
@Override public boolean contains(int x, int y) {
if (Objects.nonNull(icon) && Objects.nonNull(icon.area)) {
return icon.area.contains(x, y);
} else {
return super.contains(x, y);
}
}
};
b.setIcon(icon);
b.setContentAreaFilled(false);
b.setBorder(BorderFactory.createEmptyBorder());
b.setHorizontalTextPosition(SwingConstants.CENTER);
b.setFocusPainted(false);
b.setOpaque(false);
b.setBackground(color);
return b;
}
private static JPanel makePanel(int overlap) {
//http://java-swing-tips.blogspot.com/2013/12/breadcrumb-navigation-with-jradiobutton.html
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEADING, -overlap, 0)) {
@Override public boolean isOptimizedDrawingEnabled() {
return false;
}
};
p.setBorder(BorderFactory.createEmptyBorder(BORDER, overlap + BORDER, BORDER, BORDER));
p.setOpaque(false);
return p;
}
private static JComponent makeBreadcrumbList(int overlap, Color color, List<String> list) {
JPanel p = makePanel(overlap + 1);
ButtonGroup bg = new ButtonGroup();
for (String title : list) {
AbstractButton b = makeButton(title, color);
p.add(b);
bg.add(b);
}
return p;
}
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new FlowLayoutOverlapTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
//http://java-swing-tips.blogspot.com/2012/11/make-togglebuttonbar-with-jradiobuttons.html
class ToggleButtonBarCellIcon implements Icon, Serializable {
private static final long serialVersionUID = 1L;
private static final int W = 10;
private static final int H = 21;
public Shape area;
public Shape getShape(Container parent, Component c, int x, int y) {
int w = c.getWidth() - 1;
int h = c.getHeight() - 1;
int h2 = (int)(h * .5 + .5);
int w2 = W;
Path2D.Float p = new Path2D.Float();
p.moveTo(0, 0);
p.lineTo(w - w2, 0);
p.lineTo(w, h2);
p.lineTo(w - w2, h);
p.lineTo(0, h);
if (c != parent.getComponent(0)) {
p.lineTo(w2, h2);
}
p.closePath();
return AffineTransform.getTranslateInstance(x, y).createTransformedShape(p);
}
@Override public void paintIcon(Component c, Graphics g, int x, int y) {
Container parent = c.getParent();
if (Objects.isNull(parent)) {
return;
}
area = getShape(parent, c, x, y);
Color bgc = parent.getBackground();
Color borderColor = Color.GRAY.brighter();
if (c instanceof AbstractButton) {
ButtonModel m = ((AbstractButton) c).getModel();
if (m.isSelected() || m.isRollover()) {
bgc = c.getBackground();
borderColor = Color.GRAY;
}
}
Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(bgc);
g2.fill(area);
g2.setPaint(borderColor);
g2.draw(area);
g2.dispose();
}
@Override public int getIconWidth() {
return 100;
}
@Override public int getIconHeight() {
return H;
}
}

aterai
- 9,658
- 4
- 35
- 44
2
The main problem with the triangles is that the two components need to overlap each other, while the standard Java layouts are designed to prevent overlaps.
You have 3 options:
- You will either need to "fake" the overlap by having one component draw the triangle for the component to the left.
- Or you can create a component to go inbetween two rectangular components and which renders the overlap.
- Or you will need to use a custom layout manager that overlaps them in the desired way.
Either way you are also going to have the problem that clicking in the overlapping area would only go to one of the buttons unless you add special logic to forward the clicks appropriately.

Tim B
- 40,716
- 16
- 83
- 128