Monday, July 14, 2008

JTabbedPane with close button that sets the tab title correctly

Java 6 includes the abilility to add a component (like a "x" to close the tab) to a tab pane, and Alexander Potochkin, has elegantly put together a "ButtonTabComponent" class to easily handle this.

I've made just a few changes to get the "x" to look more like the "x"'s on the rest of my application, and most importantly for me, to set the title of the JTabbedPane so that calling getTitleAt(tabPosition) doesn't return an empty string, since this is now handled completely by the ButtonTabComponent class. BTW, I'm mixing regular tabs with tabs that can be closed, so I want to be able to just have to call one method to check what each's title is, i.e.:

int existingTabPosition = 0;
int tabCount = tabbedPane.getTabCount();
for(int i = 0; i < tabCount; i++) {
String thisTabTitle = tabbedPane.getTitleAt(i);
if(thisTabTitle.equals(menuSelectedTabTitle)) {
existingTabPosition = i;
}
}

if(existingTabPosition != 0) {
//report already exists - just force focus to it
tabbedPane.setSelectedIndex(existingTabPosition);
} else {
//no report exists by this name - add new tab
tabbedPane.add(jrView);
int newTabPosition = tabbedPane.getTabCount() - 1;

tabbedPane.setTabComponentAt(newTabPosition, new ButtonTabComponent(menuSelectedTabTitle, tabbedPane));
tabbedPane.setSelectedIndex(newTabPosition);
}

Here's my updated "paintComponent" method of the TabButton inner class:
      //paint the "x" and update the title
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Stroke stroke = g2.getStroke();

//shift the image for pressed buttons
//changed by DG
//if(!getModel().isPressed()) {
// g2.translate(-1, -1);
//}

g2.setStroke(new BasicStroke(2));
g.setColor(Color.BLACK);
if(getModel().isRollover()) {
Color highlight = new Color(0, 51, 153); //dark blue - changed by DG
g.setColor(highlight);
}
int delta = 5; //changed by DG
g.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
g.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);

//NOTE: only needed if button pressed in effect is used above
//changed by DG
//leave the graphics unchanged
//if(!getModel().isPressed()) {
// g.translate(1, 1);
//}
g2.setStroke(stroke);

//add the title here - added by DG
int i = pane.indexOfTabComponent(ButtonTabComponent.this);
pane.setTitleAt(i, label.getText());
}
I assume the title is set here for a specific reason, though my next experiment will be to remove the title from here and add it back to the JTabbedPane - though the way it is might actually be a bit nicer to work with. Alternatively, you can set both at the same time - it works without problems, but it seems like bad form to have to manually set the same thing twice.

1 comment:

Phaedrus said...

I furthered this approach by subclassing JTabbedPane like so:

public class UJTabbedPane extends JTabbedPane {

@Override
public void setTitleAt(int index, String title) {
ButtonTabComponent.setTitleAt(index, title);
super.setTitleAt(index, title);
}

}

And then some changes to the ButtonTabComponent:

public class ButtonTabComponent extends JPanel {
private final JTabbedPane pane;
private final JLabel label;
private final JButton button = new TabButton();

public static void setTitleAt(int index, String title){
ButtonTabComponent btc = (ButtonTabComponent) UTabManager.getTheOnlyTabbedPane().getTabComponentAt(index);
btc.setTitle(title);
}

public void setTitle(String title){
label.setText(title);
}

}