Monday, March 23, 2009

JavaFX Text Component

WARNING: This blog entry was imported from my old blog on blogs.sun.com (which used different blogging software), so formatting and links may not be correct.


At the Roundup a few weeks ago we rewrote the old Swing JFlubber application to JavaFX. We had a brand new graphical design done in PhotoShop, and we hooked up the functionality with some swanky effects. I might get into that in another blog entry.



But one challenge we ran into was that of displaying text. In the Flubber application we need to have an editable text area where flub times are automatically inserted and the user can add additional comments. JavaFX makes it easy to throw some simple text in a design, but edit, scroll, and so on? Not yet.



Proper GUI components for JavaFX is coming. But in the mean time, there are some tricks you can use. Put simply, you can use a Swing text area, but without looking like Swing. Here's how. First, create a JavaFX scenegraph node for the Swing text component:


var textPane = new JTextPane();
var scrollPane = new JScrollPane(textPane);
var text: SwingComponent = SwingComponent.wrap(scrollPane);

Let's see how that looks:




Ugh!! Not what we want. Obviously, we don't want the text area to look like Swing... we have a nice background as part of the PhotoShop design that we want to use under below the text. But that's not hard to fix. Java colors have an alpha channel, for opacity. We can use a completely transparent background on the text area to make it just pass through its background (and we have to make the scroll pane nonopaque as well):

textPane.setBackground(new java.awt.Color(0,0,0,0)); // 0 opacity => 100% transparent!
textPane.setForeground(java.awt.Color.WHITE);
textPane.setCaretColor(java.awt.Color.WHITE);
scrollPane.setOpaque(false);

Let's try again:




Much better! But there is still a gray rectangle on top of the graphics, placed there by the border of the scrollpane. We can get rid of that too:

scrollPane.setBorder(new EmptyBorder(new Insets(0,0,0,0)));





Now we're talking! But the font is too small. Dick told me he wants to use a Courier font. So let's try this one:

textPane.setFont(new java.awt.Font("American Typewriter", 0, 14));

Here's what we get:




Ewwwwww! The font is not antialiased. I tried a bunch of things to fix this -- for example setting antialias rendering hints on the component itself, setting the aa system property, etc. It looks like there is a bug here in that the rendering hints aren't picked up properly for the wrapped Swing components. But I did find a workaround that works: We can set the antialiasing rendering hints directly on the graphics object ourselves. That means we need to get involved in the painting of the component itself, but luckily that's trivial. Instead of creating a new JTextPane, we'll create a simple subclass of it where we override the paint method to add the rendering hints:


import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JTextPane;

public class AntiAliasedTextArea extends JTextPane
{
@Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
super.paint(g);
}
}


Now we can just change

var textPane = new JTextPane();

to

var textPane = new AntiAliasedTextPane();


and we get this:




Ta-da!



But there is one more problem... What if we add a lot of text:




Scrollbars! We don't want those. This text pane won't be used for much text, so you can just move the caret around like in a text field to navigate the overflow content. So let's turn off the scrollbars:


scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);


and with that, the text pane behaves exactly the way we want. Now you can manipulate text in the usual way: textPane.setText(string), textPane.getText(), etc.



While we're at it, let's fix the selection colors as well such that when you select text you don't get the telltale Swing light blue:


textPane.setSelectionColor(new Color(255, 255, 255, 50));
textPane.setSelectedTextColor(new Color(255, 255, 255, 255));

Note again how we can use transparency on this colors to give a nice glass effect on the selection (I also bumped up the font size):



Hopefully these tricks will help you with your text needs until we have real JavaFX components...



No comments:

Post a Comment