I would like a review on my Console
class I made in JavaFX 8. It features the following:
- A text field where you can enter input, the input gets copied into the text area.
- A text area where you can observe output and see the input.
- When you press enter, you send the message.
- When you press arrow up, you can go back in history.
- When you press arrow down, you can go forward in history.
- You can hook an event listener when a message gets sent.
public class Console extends BorderPane {
protected final TextArea textArea = new TextArea();
protected final TextField textField = new TextField();
protected final List<String> history = new ArrayList<>();
protected int historyPointer = 0;
private Consumer<String> onMessageReceivedHandler;
public Console() {
textArea.setEditable(false);
setCenter(textArea);
textField.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
switch (keyEvent.getCode()) {
case ENTER:
String text = textField.getText();
textArea.appendText(text + System.lineSeparator());
history.add(text);
historyPointer++;
if (onMessageReceivedHandler != null) {
onMessageReceivedHandler.accept(text);
}
textField.clear();
break;
case UP:
if (historyPointer == 0) {
break;
}
historyPointer--;
runSafe(() -> {
textField.setText(history.get(historyPointer));
textField.selectAll();
});
break;
case DOWN:
if (historyPointer == history.size() - 1) {
break;
}
historyPointer++;
runSafe(() -> {
textField.setText(history.get(historyPointer));
textField.selectAll();
});
break;
default:
break;
}
});
setBottom(textField);
}
@Override
public void requestFocus() {
super.requestFocus();
textField.requestFocus();
}
public void setOnMessageReceivedHandler(final Consumer<String> onMessageReceivedHandler) {
this.onMessageReceivedHandler = onMessageReceivedHandler;
}
public void clear() {
runSafe(() -> textArea.clear());
}
public void print(final String text) {
Objects.requireNonNull(text, "text");
runSafe(() -> textArea.appendText(text));
}
public void println(final String text) {
Objects.requireNonNull(text, "text");
runSafe(() -> textArea.appendText(text + System.lineSeparator()));
}
public void println() {
runSafe(() -> textArea.appendText(System.lineSeparator()));
}
}
public final class GUIUtils {
private GUIUtils() {
throw new UnsupportedOperationException();
}
public static void runSafe(final Runnable runnable) {
Objects.requireNonNull(runnable, "runnable");
if (Platform.isFxApplicationThread()) {
runnable.run();
}
else {
Platform.runLater(runnable);
}
}
}
public class WordWrapConsole extends Console {
public WordWrapConsole() {
super();
textArea.setWrapText(true);
}
}
The calls that modify textArea
and textField
are wrapped in a GUIUtils.runSafe()
, because modifications on JavaFX UI elements may only be done from the JavaFX thread and it is quite common that code from the public methods can run in different threads.