TL;DR: What is the easiest and the most correct way to set up a Graphics
instance to use default settings to render strings? Or how to render a string using default anti-aliasing settings, so that it looks like a JLabel? More detailed description of the issue follows...
I'm trying to create a custom JComponent subclass. In fact, it's a sort of a TableCellRenderer
that is able display rich text. Extending JEditorPane
is too heavy and slow (tried it actually), and JLabel can't display rich text, so I decided to implement my own, lightweight and fast. Now, it (obviously) needs to draw some text in paintComponent()
, and I would like this text to look like in all other text components, like JLabel.
However, when I do it, it seems to use different anti-aliasing settings from the rest of the app, so it looks rather ugly. I realize that I can just cast Graphics
to Graphics2D
to use the appropriate API, but the question is, what exact settings to use? That is, what to pass to setRenderingHint()
as the second parameter?
I can get it look fine on my system by playing with various AA values, but then won't it suddenly look awful on some other system with different default AA settings?
I tried to look at JLabel and LabelUI sources, but they seem to use a lot of black magic, like querying some occult properties using JComponent.getClientProperty()
and SwingUtilities2
which isn't even a part of the official Swing. Of course, I could try to mimic that, but that's a) too tedious and b) bound to use some not-too-documented features that aren't event guaranteed to have a stable API.
Or maybe there is a way to reuse some existing UI delegate? Basically, I just want my text to look exactly as displayed by a JLabel
. I could even use an instance of JLabel
or its subclass as a sort of "rubber stamp" to draw the text, but that looks a bit ugly and I'm not sure about performance. I realize that JTable
actually does exactly that, but using a JLabel
to draw an entire cell is one thing, using it to draw parts of the cell just doesn't feel right. For example, it could happen that some L&F decorates these JLabel
s in a special way that will just look ugly.
Ideally, I would like to have something like this (imaginary code):
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
JLabel.getDefaultUI().getRenderingHint());
Or better yet (set up everything, not just AA):
JLabel.getDefaultUI().setupGraphics(g); // this sets up g just like for drawing a JLabel
But there is seems to be no such simple thing as far as I can see.
Sometimes we can't see the forest for the trees...
and JLabel can't display rich text
If your need is having rich text in a table renderer, then (maybe) that's the question!
JLabel
in fact does render rich text through HTML [Doc], and even you can use JXLabel
from SwingX for further functionality (rotation! line break!).
Considering that DefaultTableRenderer
is in fact a JLabel
maybe you can extend it and do a simple modification:
public class AATableRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
DefaultTableCellRenderer c = (DefaultTableCellRenderer) super
.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
String text = c.getText();
// Do some style transformations maybe...
c.setText("<html>" + text + "</html>");
return c;
}
}
or you can modify your JTable
in the prepareRenderer()
method
public class AATable extends JTable {
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
if (c instanceof JLabel) {
JLabel l = (JLabel) c;
String text = l.getText();
// Do some style transformations maybe...
l.setText("<html>" + text + "</html>");
}
return c;
}
}
But maybe you can't use the HTML thing... In that case, if you have to stick to your custom component you might have to do it per look and feel as each is managed on their own way. For instance note that sometimes this properties are set in the system and most Swing L&Fs respect them (Windows 7 configures this in My Computer > Properties > Advanced System Configuration > Performance > Smooth fonts (a check in the list)).
I don't think you can reuse the LabelUI
(the basic one) because maybe all that magic interferres in the way you want to paint when you just want the smoothing... Not sure, it's not impossible but I can't tell.
THIS IS THE ANSWER TO THE ORIGINAL QUESTION
Short answer
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Long answer
The reason why your component uses a different settings is because Swing uses a delegation architecture for the painting: the L&F holds the delegate prototypes for each type of component, when a component is instantiated the delegate is created and injected, and there the painting (and behavior) is done. When you just implement the paintComponent(Graphics)
method you're skipping all that architecture and doing the painting directly and you're missing those settings that might be done in the delegates*. [Doc]
The most proper ("proper" is not always the best solution!) way to do your custom component is building your UI delegate, registering it and doing the appropriate procedures. [Doc]
Anyway this is a long tedious work, so let's be practical: If your application just uses one look and feel, if you're not going to reuse this component, if your time constraints do not allow you to spend a lot of time developing a single component... if you just need to set your component anti-aliased, use this code snippet:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JComponent;
public class AAComponent extends JComponent {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // Maybe you want to skip this, up to your needs...
Graphics2D g2 = null;
try {
g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Your painting procedures
} finally {
if (g2 != null) {
g2.dispose();
}
}
}
}
Maybe the instantiation of g2
looks a bit weird but it's quite optimal, it's a way not to propagate changes in your Graphics
and the computational cost is less than minimal. Also if you'd want to propagate the antialiasing to the super class painting (in this case JComponent
does nothing) just call the super implementation after setting the rendering hint.
* This is a bit inaccurate because the configuration does not necessarily rely on the L&F but in the combination with delegates, but I think it's a good way to illustrate what happens.