问题
I'm building a Clojure program that includes an option dialog where the user can select the font used in an editor. Just like many other programs, I would like to present the user with a ComboBox where the dropdown list displays the font names in the font itself (e.g. 'Calibri' is displayed in the Calibri font, 'Arial' is displayed in Arial, and so on.)
In the past in Java, I have used a cell factory to customize the appearance of each cell in the list.
My translation into Clojure is not working though.
Here is what I have come up with so far:
(defn build-font-list-cell
"Return a Cell with an overridden updateItem implementation for
the cells in the font list combo. Format the name of the font in
the actual font."
[]
(proxy [TextFieldListCell] []
(updateItem [^String family mt]
(proxy-super updateItem family mt)
(if mt
(.setText this nil)
(do
(.setFont this (Font/font family))
(.setText this family))))))
(defn build-font-list-cell-factory
[]
(proxy [Callback] []
(call [list-view]
(build-font-list-cell))))
(defn build-font-face-combo
"Build, configure, and return the combo box used to select the font
face for the editor."
[]
(let [family-list (FXCollections/observableArrayList (Font/getFamilies))
font-face-combo (ComboBox. family-list)
current-face @tentative-font-face]
(.setEditable font-face-combo true)
(.addListener (.selectedItemProperty (.getSelectionModel font-face-combo))
^ChangeListener (face-combo-listener font-face-combo))
(.setCellFactory font-face-combo (build-font-list-cell-factory))
(select-item-in-combo font-face-combo current-face)
font-face-combo))
The compiler throws a ExceptionInInitializerError
on this in the build-font-list-cell
function at the declaration of the proxy
. The IDE (IntelliJ) shows a warning about the updateItem
argument in the call to 'super-proxy`, saying it cannot be resolved. I don't understand why not since it doesn't complain about the override on the line above.
This seems like a relatively straightforward translation of Java code that has worked before, but I'm clearly missing something. Or is this even the right approach to take?
EDIT: Adding the following MCVE. It compiles and runs as shown, but does not format the font face names of course. Attempting to create a cell factory by un-commenting the code in the listing produces something that the compiler chokes on.
(ns ffcbd.core
(:gen-class
:extends javafx.application.Application)
(:import (javafx.application Application)
(javafx.collections FXCollections)
(javafx.scene.control ComboBox)
(javafx.scene.control.cell TextFieldListCell)
(javafx.scene.text Font)
(javafx.scene.layout BorderPane)
(javafx.scene Scene)
(javafx.stage Stage)
(javafx.util Callback)))
;(defn build-font-list-cell []
; (proxy [TextFieldListCell] []
; (updateItem [family mt]
; (proxy-super updateItem family mt)
; (if mt
; (.setText this nil)
; (do
; (.setFont this (Font/font family))
; (.setText this family))))))
;(defn build-font-list-cell-factory []
; (proxy [Callback] []
; (call [list-view]
; (build-font-list-cell))))
(defn build-font-face-combo []
(let [family-list (FXCollections/observableArrayList (Font/getFamilies))
font-face-combo (ComboBox. family-list)]
; (.setCellFactory font-face-combo (build-font-list-cell-factory))
(.select (.getSelectionModel font-face-combo) 0)
font-face-combo))
(defn -start [this stage]
(let [root (BorderPane.)
scene (Scene. root)]
(.setTop root (build-font-face-combo))
(.add (.getChildren root) (build-font-face-combo))
(.setMinSize root 300 275)
(doto stage
(.setScene scene)
(.setTitle "Font Face ComboBox Demo")
(.show))))
(defn -main [& args]
(Application/launch ffcbd.core args))
Another difference from the Java version is that the list cell in Java is a ListCell
. But I need to call super.updateItem
. As I understand the docs, proxy
does not allow you to call super unless the method is public
. It is protected
in ListCell
, but public
in TextFieldListCell
.
EDIT #2: Here is an example of code that works in Java that I keep referring to.
package FontFaceDialog;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.scene.control.ComboBox;
import javafx.util.Callback;
public class Main extends Application {
private ComboBox<String> buildFontFaceCombo() {
ObservableList<String> lst = FXCollections.observableList(javafx.scene.text.Font.getFamilies());
ComboBox<String> cb = new ComboBox<String>(lst);
cb.getSelectionModel().select(0);
cb.setCellFactory((new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> listview) {
return new ListCell<String>() {
@Override
protected void updateItem(String family, boolean empty) {
super.updateItem(family, empty);
if (empty) {
setText(null);
} else {
setFont(Font.font(family));
setText(family);
}
}
};
}
}));
return cb;
}
@Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
root.setTop(buildFontFaceCombo());
primaryStage.setTitle("Font Face Dialog Example");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
回答1:
There is no this
in Clojure. I suspect that is the heart of the error.
The method signature is
public void updateItem(T item, boolean empty)
I would also suggest deleting the unnecessary ^String
hint. If you haven't seen it yet, there are some examples on ClojureDocs.org
回答2:
Unfortunately, I have been unable to figure out how to do this in Clojure so far. Since I need to get this working, I'm using another alternative. Since I know how to do it in Java, why not just do the mystery bits in Java? It's not as aesthetically pleasing, but it works.
Working with polyglot programs in lein
is possible but kind of fiddly.
First, here is the Clojure part of the demo.
ns ffcbd.core
(:gen-class
:extends javafx.application.Application)
(:import (com.example FontFaceListCell)
(javafx.application Application)
(javafx.collections FXCollections)
(javafx.scene.control ComboBox)
(javafx.scene.text Font)
(javafx.scene.layout BorderPane)
(javafx.scene Scene)
(javafx.stage Stage)
(javafx.util Callback)))
(defn build-font-list-cell-factory []
(proxy [Callback] []
(call [list-view]
(FontFaceListCell.))))
(defn build-font-face-combo []
(let [family-list (FXCollections/observableArrayList (Font/getFamilies))
font-face-combo (ComboBox. family-list)]
(.setCellFactory font-face-combo (build-font-list-cell-factory))
(.select (.getSelectionModel font-face-combo) 0)
font-face-combo))
(defn -start [this stage]
(let [root (BorderPane.)
scene (Scene. root)]
(.setTop root (build-font-face-combo))
(.add (.getChildren root) (build-font-face-combo))
(.setMinSize root 400 275)
(doto stage
(.setScene scene)
(.setTitle "Font Face ComboBox Demo")
(.show))))
(defn -main [& args]
(Application/launch ffcbd.core args))
Notice the import of com.example.FontFaceListCell
near the top. That's the Java part. Here's the listing of that little class.
package com.example;
import javafx.scene.control.ListCell;
import javafx.scene.text.Font;
public class FontFaceListCell extends ListCell<String> {
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setFont(Font.font(item, 16.0d));
setText(item);
}
}
}
This class extends ListCell
and overrides the updateItem
method. When you run the program and click on the ComboBox
, I get something like this on my system.

As mentioned above, to get this working with lein
, you need a few more fiddly bits.
In order to compile Java code, lein
needs to know where Java is. I added this line to my global profile.
:java-cmd "C:\\Program Files\\Java\\jdk1.8.0_121\\bin\\java.exe"
This kinda sucks because now I can't use the same profiles.clj
file on Windows and Linux.
Getting this to work also requires a few changes to the project.clj
to tell lein
where the Java code is and which options to pass to the compiler. Here is what I used.
(defproject ffcbd "0.1.0-SNAPSHOT"
:description "A demo of a styled font selection ComboBox in Clojure."
:dependencies [[org.clojure/clojure "1.8.0"]]
:java-source-paths ["java"]
:javac-options ["-target" "1.8" "-source" "1.8"]
:aot :all
:main ffcbd.core)
I've put up a Mercurial repo on Bitbucket including an IntelliJ IDEA project for anyone interested.
It would still be nice to get all of this working only using Clojure though. I'm sure gen-class
would do it, but haven't figured it out yet.
来源:https://stackoverflow.com/questions/43002816/javafx-font-face-dialog-in-clojure