I'm writing a Swing application and I'm trying to figure out a way to display text while being able to control the following text properties:
font family and size
text overline
lower index
Of course the more flexible the approach, the better, but those are strictly mandatory for me.
What I already tried:
using JTextPane and setting text-decoration: overline attribute -
doesn't work, because JTextPane html/css engine is dated and doesn't support this
using JTextPane with setting upper border via css -
doesn't work, because it can only be applied to html nodes with
display:block (such as divs), and I need this inline (setting display: inline-block is not
supported in JTextPane, so I can't use that)
using WebView from JavaFX - this is where I'm currently at, but I can't figure out how to load a custom font programmatically. It only seems to be working when the font is loaded at OS level (e.g. when the font file is present in ~/.fonts on Linux)
This is a working example. Mostly taken from Integrating JavaFX into Swing Applications.
Mind the font file, which you might have to specify yourself (here's the file I used):
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.text.Font;
import javafx.scene.web.WebView;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
private static void initAndShowGUI() {
JFrame frame = new JFrame("Swing and JavaFX");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setSize(300, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Platform.runLater(() -> initFX(fxPanel));
}
private static void initFX(JFXPanel fxPanel) {
Scene scene = createScene();
fxPanel.setScene(scene);
}
private static Scene createScene() {
var wb = new WebView();
// ???
var f = Font.loadFont("lmroman10-regular.otf", 10);
assert f != null;
wb.getEngine().loadContent(
"<html><body>" +
"<span style=\"" +
"text-decoration: overline; " +
"font-size: 24px;" +
"font-family: 'LM Roman 10';\">" +
"foo" +
"</span>" +
"bar" +
"</body></html>");
return new Scene(wb);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> initAndShowGUI());
}
}
Related
Overview
Using FlyingSaucer within a JavaFX application, to avoid WebView for various reasons:
doesn't provide direct API access to its scrollbars for synchronous behaviour;
bundles JavaScript, which is a huge bloat for my use case; and
failed to run on Windows.
FlyingSaucer uses Swing, which requires wrapping its XHTMLPanel (a subclass of JPanel) in a SwingNode to use alongside JavaFX. Everything works great, the application renders Markdown in real-time, and is responsive. Here's a demo video of the application running on Linux.
Problem
The text rendering on Windows is blurry. When running in a JFrame, not wrapped by a SwingNode, but still part of the same application shown in the video, the quality of the text is flawless. The screen capture shows the application's main window (bottom), which includes the SwingNode along with the aforementioned JFrame (top). You may have to zoom into the straight edge of the "l" or "k" to see why one is sharp and the other blurry:
This only happens on Windows. When viewing the font on Windows through the system's font preview program, the fonts are antialiased using LCD colours. The application uses grayscale. I suspect that if there is a way to force the rendering to use colour for antialiasing instead of grayscale, the problem may disappear. Then again, when running within its own JFrame, there is no problem and LCD colours are not used.
Code
Here's the code for the JFrame that has a perfect render:
private static class Flawless {
private final XHTMLPanel panel = new XHTMLPanel();
private final JFrame frame = new JFrame( "Single Page Demo" );
private Flawless() {
frame.getContentPane().add( new JScrollPane( panel ) );
frame.pack();
frame.setSize( 1024, 768 );
}
private void update( final org.w3c.dom.Document html ) {
frame.setVisible( true );
try {
panel.setDocument( html );
} catch( Exception ignored ) {
}
}
}
The code for the blurry SwingNode is a little more involved (see full listing), but here are some relevant snippets (note that HTMLPanel extends from XHTMLPanel only to suppress some undesired autoscrolling during updates):
private final HTMLPanel mHtmlRenderer = new HTMLPanel();
private final SwingNode mSwingNode = new SwingNode();
private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
// ...
final var context = getSharedContext();
final var textRenderer = context.getTextRenderer();
textRenderer.setSmoothingThreshold( 0 );
mSwingNode.setContent( mScrollPane );
// ...
// The "preview pane" contains the SwingNode.
final SplitPane splitPane = new SplitPane(
getDefinitionPane().getNode(),
getFileEditorPane().getNode(),
getPreviewPane().getNode() );
Minimal Working Example
Here's a fairly minimal self-contained example:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.xhtmlrenderer.simple.XHTMLPanel;
import javax.swing.*;
import static javax.swing.SwingUtilities.invokeLater;
import static javax.swing.UIManager.getSystemLookAndFeelClassName;
import static javax.swing.UIManager.setLookAndFeel;
public class FlyingSourceTest extends Application {
private final static String HTML = "<!DOCTYPE html><html><head" +
"><style type='text/css'>body{font-family:serif; background-color: " +
"#fff; color:#454545;}</style></head><body><p style=\"font-size: " +
"300px\">TEST</p></body></html>";
public static void main( String[] args ) {
Application.launch( args );
}
#Override
public void start( Stage primaryStage ) {
invokeLater( () -> {
try {
setLookAndFeel( getSystemLookAndFeelClassName() );
} catch( Exception ignored ) {
}
primaryStage.setTitle( "Hello World!" );
final var renderer = new XHTMLPanel();
renderer.getSharedContext().getTextRenderer().setSmoothingThreshold( 0 );
renderer.setDocument( new W3CDom().fromJsoup( Jsoup.parse( HTML ) ) );
final var swingNode = new SwingNode();
swingNode.setContent( new JScrollPane( renderer ) );
final var root = new SplitPane( swingNode, swingNode );
// ----------
// Here be dragons? Using a StackPane, instead of a SplitPane, works.
// ----------
//StackPane root = new StackPane();
//root.getChildren().add( mSwingNode );
Platform.runLater( () -> {
primaryStage.setScene( new Scene( root, 300, 250 ) );
primaryStage.show();
} );
} );
}
}
Blurry capture from the minimal working example;
zooming in reveals letter edges are heavily antialiased rather than sharp contrasts:
Using a JLabel also exhibits the same fuzzy render:
final var label = new JLabel( "TEST" );
label.setFont( label.getFont().deriveFont( Font.BOLD, 128f ) );
final var swingNode = new SwingNode();
swingNode.setContent( label );
Attempts
Here are most of the ways I've tried to remove the blur.
Java
On the Java side, someone suggested to run the application using:
-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false
None of the text rendering hints have helped.
Setting the content of the SwingNode within SwingUtilities.invokeLater has no effect.
JavaFX
Someone else mentioned that turning caching off helped, but that was for a JavaFX ScrollPane, not one within a SwingNode. It didn't work.
The JScrollPane contained by the SwingNode has its alignment X and alignment Y set to 0.5 and 0.5, respectively. Ensuring a half-pixel offset is recommended elsewhere. I cannot imagine that setting the Scene to use StrokeType.INSIDE would make any difference, although I did try using a stroke width of 1 to no avail.
FlyingSaucer
FlyingSaucer has a number of configuration options. Various combinations of settings include:
java -Dxr.text.fractional-font-metrics=true \
-Dxr.text.aa-smoothing-level=0 \
-Dxr.image.render-quality=java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC
-Dxr.image.scale=HIGH \
-Dxr.text.aa-rendering-hint=VALUE_TEXT_ANTIALIAS_GASP -jar ...
The xr.image. settings only affect images rendered by FlyingSaucer, rather than how the output from FlyingSaucer is rendered by JavaFX within the SwingNode.
The CSS uses points for the font sizes.
Research
https://stackoverflow.com/a/26227562/59087 -- looks like a few solutions may be helpful.
https://bugs.openjdk.java.net/browse/JDK-8089499 -- doesn't seem to apply because this is using SwingNode and JScrollPane.
https://stackoverflow.com/a/24124020/59087 -- probably not relevant because there is no XML scene builder in use.
https://www.cs.mcgill.ca/media/tech_reports/42_Lessons_Learned_in_Migrating_from_Swing_to_JavaFX_LzXl9Xv.pdf -- page 8 describes shifting by 0.5 pixels, but how?
https://dlsc.com/2014/07/17/javafx-tip-9-do-not-mix-swing-javafx/ -- suggests not mixing JavaFX and Swing, but moving to pure Swing isn't an option: I'd sooner rewrite the app in another language.
Accepted as a bug against OpenJDK/JavaFX:
https://bugs.openjdk.java.net/browse/JDK-8252255
JDK & JRE
Using Bellsoft's OpenJDK with JavaFX bundled. To my knowledge, the OpenJDK has had Freetype support for a while now. Also, the font looks great on Linux, so it's probably not the JDK.
Screen
The following screen specifications exhibit the problem, but other people (viewing on different monitors and resolutions, undoubtedly) have mentioned the issue.
15.6" 4:3 HD (1366x768)
Full HD (1920x1080)
Wide View Angle LED Backlight
ASUS n56v
Question
Why does FlyingSaucer's XHTMLPanel when wrapped within SwingNode become blurry on Windows, and yet displaying the same XHTMLPanel in a JFrame running in the same JavaFX application appears crisp? How can the problem be fixed?
The problem involves SplitPane.
There are a few options that you might try although I have to admit that I do not know FlyingSaucer and its API.
FlyingSaucer has different renderers. Thus it might be possible to avoid the Swing/AWT rendering completely by using this library instead in order to do all the rendering directly in JavaFX. https://github.com/jfree/fxgraphics2d
Another possibility is to let FlyingSaucer render into an image which can the be displayed in JavaFX very efficiently via direct buffers. See the AWTImage code in my repository here: https://github.com/mipastgt/JFXToolsAndDemos
I wasn't able to reproduce the issue myself, so there may be some issue in the combination of JDK/JavaFX version you are using. It's also possible that the issue only arises with a specific combination of display size and screen scaling.
My setup is the following:
JavaFX 14
OpenJDK 14
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
import org.xhtmlrenderer.simple.XHTMLPanel;
import javax.swing.*;
public class FlyingSourceTest extends Application {
private final static String HTML_PREFIX = "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<body>\n";
private static final String HTML_CONTENT =
"<p style=\"font-size:500px\">TEST</p>";
private final static String HTML_SUFFIX = "<p style='height=2em'> </p></body></html>";
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
primaryStage.setTitle("Hello World!");
XHTMLPanel mHtmlRenderer = new XHTMLPanel();
mHtmlRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);
SwingNode mSwingNode = new SwingNode();
JScrollPane mScrollPane = new JScrollPane(mHtmlRenderer);
String htmlContent = HTML_PREFIX + HTML_CONTENT + HTML_SUFFIX;
Document jsoupDoc = Jsoup.parse(htmlContent);
org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(jsoupDoc);
mHtmlRenderer.setDocument(w3cDoc);
mSwingNode.setContent(mScrollPane);
// AnchorPane anchorPane = new AnchorPane();
// anchorPane.getChildren().add(mSwingNode);
// AnchorPane.setTopAnchor(mSwingNode, 0.5);
// AnchorPane.setLeftAnchor(mSwingNode, 0.5);
// mSwingNode.setTranslateX(0.5);
// mSwingNode.setTranslateY(0.5);
StackPane root = new StackPane();
root.getChildren().add(mSwingNode);
Platform.runLater(() -> {
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
});
});
}
}
The issue has been accepted as a bug against OpenJDK/JavaFX:
https://bugs.openjdk.java.net/browse/JDK-8252255
Neither of Mipa's suggestions would work in practice. FlyingSaucer is tightly integrated with a JScrollPane, which precludes the possibility of forcing FlyingSaucer to render onto a JavaFX-based panel.
Another possibility is to go the opposite direction: create a Swing application and embed JavaFX controls, such as using a JFXPanel; however, it would seem more prudent to accept the blurry behaviour until the bug is bashed.
Context: Java 11, JavaFx 11 (where applicable), Linux Mint 19.3, Eclipse 2020.06. Java apps run as desktop applications.
Background: The Mongolian script is considered "complex" and, to be shaped correctly, requires OpenType tables as isol, init, medi, fina, rlig in the font(s) implementing it.
I am trying to insert Mongolian strings in Java UI and I am having unexpected results both with Swing and with JavaFX.
Basically, if a Mongolian string like "ᠪᠠᠢᠨᠠ" is inserted in a Swing JLabel or in an Fx Label alone, it is rendered without any shaping (more or less as in "ᠪ ᠠ ᠢ ᠨ ᠠ"), while if a string in another "complex" string is added in the same label, say "अनुच्छेद ᠪᠠᠢᠨᠠ" or "ᠪᠠᠢᠨᠠ अनुच्छेद", then it is rendered correctly.
Furthermore, I noticed that in Swing, either a Devanāgarī or an Arabic string turn on the correct shaping, while in JavaFx a Devanāgarī addition does the job but an Arabic addition does not.
Of course, I cannot add arbitrary strings just to turn the correct shaping on. Am I forgetting some important initialisation or is the Java shaping tier of the font stack broken? (I suppose it is HarfBuzz, which usually works quite well...).
Thanks for any suggestion!
A Swing minimal example, which comments showing which strings are rendered correctly and which are not:
package com.vistamaresoft.swingtest;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Main
{
private JFrame frame;
private static Main window;
public Main()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setTitle("Mongolian Swing Test");
frame.setBounds(100, 100, 300, 200);
//Set up the GUI.
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JLabel label = new JLabel("ᠪᠠᠢᠨᠠ"); // NOT CORRECT
panel.add(label);
label = new JLabel("Abc ᠪᠠᠢᠨᠠ"); // NOT CORRECT
panel.add(label);
label = new JLabel("ᠪᠠᠢᠨᠠ كتاب "); // CORRECT
panel.add(label);
label = new JLabel("كتاب ᠪᠠᠢᠨᠠ"); // CORRECT
panel.add(label);
label = new JLabel("ᠪᠠᠢᠨᠠ अनुच्छेद"); // CORRECT
panel.add(label);
label = new JLabel("अनुच्छेद ᠪᠠᠢᠨᠠ"); // CORRECT
panel.add(label);
frame.setContentPane(panel);
}
public static void main(String[] args)
{
window = new Main();
window.frame.setVisible(true);
}
}
A JavaFx minimal example. Note that the WebView is not any better than a Label and that copy and pasting a label text into the HTML input field yields the same results:
package com.vistamaresoft.fxtest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class Main
{
static final String html =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n\"http://www.w3.org/TR/html4/strict.dtd\">\n" +
"<html>\n<body>\n" +
"<p style='font-size: 18pt;'>Sample text: ᠴᠣᠴᠡᠭᠠ</p>" + // NOT CORRECT!
"<p style='font-size: 18pt;'>ᠪᠠᠢᠨᠠ كتاب </p>" + // NOT CORRECT!
"<p style='font-size: 18pt;'>अनुच्छेद ᠴᠣᠴᠡᠭᠠ</p>" + // CORRECT!
"<p><input type='text' name='title' size='20' maxlength='50' style='font-size: 18pt;' /></p>" +
"</body></html>\n";
public static void main(String[] args)
{
Application.launch(MainApp.class, args);
}
public static class MainApp extends Application
{
#Override public void start(Stage primaryStage) throws Exception
{
WebView webView = new WebView();
webView.getEngine().loadContent(html);
Label lab1 = new Label("ᠪᠠᠢᠨᠠ"); // NOT CORRECT
lab1.setStyle("-fx-font-size: 24pt;");
Label lab2 = new Label("ᠪᠠᠢᠨᠠ كتاب"); // NOT CORRECT
lab2.setStyle("-fx-font-size: 24pt;");
Label lab3 = new Label("كتاب ᠪᠠᠢᠨᠠ"); // NOT CORRECT
lab3.setStyle("-fx-font-size: 24pt;");
Label lab4 = new Label("ᠪᠠᠢᠨᠠ अनुच्छेद"); // CORRECT
lab4.setStyle("-fx-font-size: 24pt;");
Label lab5 = new Label("अनुच्छेद ᠪᠠᠢᠨᠠ"); // CORRECT
lab5.setStyle("-fx-font-size: 24pt;");
VBox vBox = new VBox(webView, lab1, lab2, lab3, lab4, lab5);
Scene scene = new Scene(vBox, 800, 600);
primaryStage.setTitle("JavaFX Mongolian Test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
}
EDIT:
These issues have been filed as bugs by the Java bug database:
the AWT bug: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8252091
the JavaFx bug: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8252220
I want to set bold font style for selected text in JTextArea instance.
I tried this way:
textArea.getSelectedText().setFont(new Font("sansserif",Font.BOLD, 12));
But it does not work. Also I've tried JTextPane and JEditorPane instead of JTextArea but without effect.
How can I do that?
I want to set bold font style for selected text in JTextArea instance.
You can't do this for a JTextArea. You need to use a JTextPane.
Then you can use the default Action provided by the StyledEditorKit. Create a JButton or JMenuItem to do this:
JButton boldButton = new JButton( new StyledEditorKit.BoldAction() );
JMenuItem boldMenuItem = new JMenuItem( new StyledEditorKit.BoldAction() );
Add the button or menu item to the frame. Then the use can click on the button/menu item to bold the text after it has been selected. This is the way most editor work. You can also add an acceleration to the Action to the Action can be invoked just by using the keyboard.
Read the section from the Swing tutorial on Text Component Features for more information and a working example.
Introduction
The (useful) answers for how to do what you want to do have already been posted by #Freek de Bruijn and #Gilbert Le Blanc, but none of them explain why what you're trying to do doesn't work. This isn't an answer for
How can I do that?
but an explanation for
But it does not work.
Edit: #camickr posted what I believe is the correct approach.
Answer
From the tutorial about about JTextArea:
You can customize text areas in several ways. For example, although a given text area can display text in only one font and color, you can set which font and color it uses.
(all emphasis in quotes are mine) and
If you want the text area to display its text using multiple fonts or other styles, you should use an editor pane or text pane.
This is because JTextArea uses PlainDocument (see this):
PlainDocument provides a basic container for text where all the text is displayed in the same font.
However, a JTextPane uses DefaultStyledDocument:
a container for styled text in no particular format.
You have to set up a caret listener on a JTextPane to listen for when some or all of the text is selected.
Here's the GUI I created.
And here's the code:
package com.ggl.testing;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
public class JTextPaneTest implements Runnable {
private JTextPane textPane;
private StyledDocument styledDocument;
public static void main(String[] args) throws BadLocationException {
SwingUtilities.invokeLater(new JTextPaneTest());
}
public JTextPaneTest() throws BadLocationException {
this.styledDocument = new DefaultStyledDocument();
this.styledDocument.insertString(0, displayText(), null);
addStylesToDocument(styledDocument);
}
#Override
public void run() {
JFrame frame = new JFrame("JTextPane Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
textPane = new JTextPane(styledDocument);
textPane.addCaretListener(new SelectedText());
textPane.setPreferredSize(new Dimension(250, 125));
JScrollPane scrollPane = new JScrollPane(textPane);
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
private String displayText() {
return "This is some sample text. Pick part of the text to select "
+ "by double clicking on a word.";
}
private void addStylesToDocument(StyledDocument styledDocument) {
Style def = StyleContext.getDefaultStyleContext().getStyle(
StyleContext.DEFAULT_STYLE);
Style s = styledDocument.addStyle("bold", def);
StyleConstants.setBold(s, true);
}
private class SelectedText implements CaretListener {
#Override
public void caretUpdate(CaretEvent event) {
int dot = event.getDot();
int mark = event.getMark();
if (dot != mark) {
if (dot < mark) {
int temp = dot;
dot = mark;
mark = temp;
}
boldSelectedText(mark, dot);
}
}
private void boldSelectedText(int mark, int dot) {
try {
int length = dot - mark;
String s = styledDocument.getText(mark, length);
styledDocument.remove(mark, length);
styledDocument.insertString(mark, s,
styledDocument.getStyle("bold"));
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}
}
You could use a JTextPane component similar to changing the color as described in the following answer: How to set font color for selected text in jTextArea.
For example:
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.WindowConstants;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
public class BoldSelected {
public static void main(final String[] args) {
new BoldSelected().launchGui();
}
private void launchGui() {
final String title = "Set bold font style for selected text in JTextArea instance";
final JFrame frame = new JFrame("Stack Overflow: " + title);
frame.setBounds(100, 100, 800, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
final JTextPane textPane = new JTextPane();
textPane.setText(title + ".");
final Style style = textPane.addStyle("Bold", null);
StyleConstants.setBold(style, true);
textPane.getStyledDocument().setCharacterAttributes(4, 33, style, false);
frame.getContentPane().add(textPane);
frame.setVisible(true);
}
}
I am trying to use the Net beans Visual Library in Javafx and I am referring to example here https://platform.netbeans.org/graph/examples.html.
I am particularly using the DemoGraphscene.java in the javaone.demo4 package. While I am using am using the example in my javafx project, I am not sure how to display the graph.
Here is what I have written in my controller class:
public class GraphicalViewController implements Initializable {
/**
* Initializes the controller class.
*/
#FXML
private Pane pane1;
#FXML
private Label label;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
GraphScene scene = new DemoGraphScene ();
String helloNodeID = "Hello";
String worldNodeID = "World";
String edge = "edge";
Widget hello = scene.addNode (helloNodeID);
Widget world = scene.addNode (worldNodeID);
scene.addEdge (edge);
scene.setEdgeSource(edge, helloNodeID);
scene.setEdgeTarget(edge, worldNodeID);
hello.setPreferredLocation (new Point (100, 100));
world.setPreferredLocation (new Point (400, 200));
pane1.getChildren().add(scene.getView());
}
}
So I have (argument mismatch; JComponent cannot be converted to Node) in the line pane1.getChildren().add(scene.getView());
How do I come about this problem?
I did this:
SwingNode swingScene = new SwingNode();
SwingNode swingScene1 = new SwingNode();
swingScene.setContent(new JButton("Click me!"));
swingScene1.setContent(scene.getView());
pane1.getChildren().add(swingScene1);
When I do pane1.getChildren().add(swingScene1), I see nothing is displayed, but pane1.getChildren().add(swingScene) does show the button.
Use a SwingNode
The NetBeans Visual Library is Swing based. If you want to use it in JavaFX, you need to wrap the Swing component in a JavaFX node.
#FXML Pane pane1;
. . .
GraphScene scene = new DemoGraphScene();
. . .
SwingNode swingScene = new SwingNode();
swingScene.setContent(scene.getView());
. . .
pane1.getChildren().add(swingScene);
Notes of Confusion
It is especially confusing because the visual library works with a scene and the JavaFX library also works with a scene, but they are different scenes from different libraries, so you need to wrap the Visual Library scene view in a JavaFX node in order to display it in a JavaFX scene.
Additionally, when mixing Swing and JavaFX code, ensure you execute Swing code on the Swing event dispatch thread and JavaFX code on the JavaFX application thread - otherwise things could go badly wrong.
I'm not a big fan of mixing Swing and JavaFX, usually I recommend that if you want to use Swing, then write a 100% Swing App and if you want to use JavaFX, write a 100% JavaFX App.
Executable Sample
Here is an executable sample. The sample relies on the source code at: https://platform.netbeans.org/graph/examples.html. To use it, download the sample project from that link, paste the sample code below into the
NetBeans project and right-click to run it.
import java.awt.Dimension;
import java.awt.Point;
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import org.netbeans.api.visual.graph.GraphScene;
import org.netbeans.api.visual.widget.Widget;
public class FXGraphDemo extends Application {
#Override
public void start(final Stage stage) {
final SwingNode swingNode = new SwingNode();
createAndSetSwingContent(swingNode);
Scene scene = new Scene(new Group(swingNode), 400, 300);
stage.setScene(scene);
stage.show();
}
private void createAndSetSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final GraphScene graphScene = new DemoGraphScene();
String helloNodeID = "Hello";
String worldNodeID = "World";
String edge = "edge";
Widget hello = graphScene.addNode (helloNodeID);
Widget world = graphScene.addNode (worldNodeID);
graphScene.addEdge (edge);
graphScene.setEdgeSource(edge, helloNodeID);
graphScene.setEdgeTarget(edge, worldNodeID);
hello.setPreferredLocation (new Point (100, 100));
world.setPreferredLocation (new Point (300, 200));
final JComponent sceneView = graphScene.createView();
final JScrollPane panel = new JScrollPane (sceneView);
panel.getHorizontalScrollBar().setUnitIncrement (32);
panel.getHorizontalScrollBar().setBlockIncrement (256);
panel.getVerticalScrollBar().setUnitIncrement (32);
panel.getVerticalScrollBar().setBlockIncrement (256);
panel.setPreferredSize(new Dimension (400, 300));
swingNode.setContent(panel);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Caveat
The sample (often, but not always) has a issue when it initially paints, it will flash black for a second and log a NullPointerExcepton when trying to calculate a Rectangle (for reasons unbeknownst to me - seems a bit like a race condition somewhere). After that though, it seems to display and work OK. To get around the black flash and NullPointerException, you can display the graph in a JFrame rather than a SwingNode, but then it shows in its own separate window rather than being embedded in a JavaFX scene.
NetBeans Visual Library JavaFX port
https://github.com/theanuradha/visual-library-fx/releases
https://github.com/theanuradha/visual-library-fx/tree/master/org-netbeans-api-visualfx/src/test/java/javaone/demo4
DemoGraphScene scene = new DemoGraphScene();
String helloNodeID = "Hello";
String worldNodeID = "World";
String edge = "edge";
Widget hello = scene.addNode(helloNodeID);
Widget world = scene.addNode(worldNodeID);
scene.addEdge(edge);
scene.setEdgeSource(edge, helloNodeID);
scene.setEdgeTarget(edge, worldNodeID);
hello.setPreferredLocation(new Point(0, 0));
world.setPreferredLocation(new Point(400, 200));
final SceneNode sceneView = scene.createView();
I would like to understand why a JLabel, rendering HTML, shifts the vertical position of it's output text, whereas a JLabel which renders non-HTML, does not.
Java version used: 1.6.0_37
Swing Look and Feel used: Windows ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
OS: Windows 7 64 Bit
I did not put a SSCCE together since the code is really trivial. But if it helps please say so.
I rather give an examples using an images displaying the behavior:
I put a JPanel as the container around the JLabel to visualize the label's bounds. After setting the font and text for the JLabel, the
jLabel.getPreferredSize()
method returns the bounds of the rendered plain text or HTML (and this is the exact size I set for the surrounding JPanel). You can clearly see, that, if rendering HTML, the whole text is shifted a small amount down.
I would like to know why this happens and what I can do to correct the placement.
One workaround would be to translate the Graphics2D on which to render the text, to compensate the vertical shift, like this:
g2d.translate( 0, -20 );
But I don't know the correct y value in relation to the font metrics (e.g. font size). Anyway, this workaround also feels "wrong".
I really appreciate your answers, thanks a lot!
It seems that if we set the Font (family,size etc) for the HTML JLabel using setFont(..) the font is not rendered to the correct metrics of JLabel.
Here is an example I made to demonstrate (Both JLabels shown are using HTML):
A simple work around is to the the font size, family etc in HTML too.
As we can see the cyan HTML JLabel used setFont(..) (and was incorrectly rendered) while the green HTML JLabel used HTML to set the font and was rendered correctly:
JLabel labelHtml2 = new JLabel("<html><font size=10 family='Calibri'>" + text + "</font></html>");
Test.java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static Font font = new Font("Calibri", Font.PLAIN, 38);
public Test() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Test();
}
});
}
private void initComponents() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String text = "Hello world";
//this label will not render correctly due to setting font via setFont(..)
JLabel labelHtml1 = new JLabel("<html>" + text + "</html>");
labelHtml1.setBackground(Color.CYAN);
labelHtml1.setOpaque(true);//so background will be painted
labelHtml1.setFont(font);
//this label will render correcty font is set via html
JLabel labelHtml2 = new JLabel("<html><font size=10 family='Calibri'>" + text + "</font></html>");
labelHtml2.setBackground(Color.GREEN);
labelHtml2.setOpaque(true);
//labelHtml2.setFont(font);
frame.add(labelHtml1, BorderLayout.NORTH);
frame.add(labelHtml2, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
}