Clojure中的JavaFX字体对话

时间:2017-03-24 15:01:14

标签: javafx clojure

我正在构建一个包含选项对话框的Clojure程序,用户可以在其中选择编辑器中使用的字体。就像许多其他程序一样,我想向用户呈现一个ComboBox,其中下拉列表显示字体本身的字体名称(例如' Calibri'以Calibri字体显示,' Arial& #39;以Arial显示,依此类推。)

在过去的Java中,我使用了一个单元工厂来自定义列表中每个单元格的外观。

我对Clojure的翻译不起作用。

这是我到目前为止所提出的:

(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))

编译器在ExceptionInInitializerError声明的build-font-list-cell函数中抛出proxy。 IDE(IntelliJ)在“超级代理”调用中显示有关updateItem参数的警告,表示无法解析。我不明白为什么不这样做,因为它并没有抱怨上面一行的覆盖。

这似乎是一个相对简单的Java代码翻译,之前已经有效,但我显然遗漏了一些东西。或者这是采取正确的方法吗?

编辑:添加以下MCVE。它如图所示编译和运行,但当然不会格式化字体名称。尝试通过取消注释列表中的代码来创建单元工厂会产生编译器阻塞的内容。

(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))

与Java版本的另一个区别是Java中的列表单元格是ListCell。但我需要致电super.updateItem。据我了解文档,proxy不允许您调用super,除非方法为publicprotectedListCellpublicTextFieldListCell

编辑#2 :以下是我一直参考的Java代码示例。

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);
    }
}

2 个答案:

答案 0 :(得分:0)

Clojure中没有this。我怀疑这是错误的核心。

方法签名是

public void updateItem(T item, boolean empty)

我还建议删除不必要的^String提示。如果您还没有看到它,there are some examples on ClojureDocs.org

答案 1 :(得分:0)

不幸的是,到目前为止,我还无法弄清楚如何在Clojure中做到这一点。由于我需要让这个工作,我正在使用另一种选择。既然我知道如何用Java做到这一点,为什么不只是用Java中的神秘位呢?它并不像美学上那样令人愉悦,但却有效。

使用lein中的多语言程序是可能的,但有点繁琐。

首先,这是演示的Clojure部分。

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))

请注意在顶部附近导入com.example.FontFaceListCell。那是Java的一部分。这是小班的清单。

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);
        }
    }
}

此类扩展ListCell并覆盖updateItem方法。当您运行程序并单击ComboBox时,我的系统上会出现类似的内容。

如上所述,为了使用lein,你需要更多一点点。

为了编译Java代码,lein需要知道Java的位置。我将此行添加到我的全球个人资料中。

:java-cmd "C:\\Program Files\\Java\\jdk1.8.0_121\\bin\\java.exe"

这有点糟糕,因为现在我无法在Windows和Linux上使用相同的profiles.clj文件。

要使其工作,还需要对project.clj进行一些更改,以告知lein Java代码所在的位置以及传递给编译器的选项。这是我使用的。

(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)

我在Bitbucket上贴了一个Mercurial repo,包括一个IntelliJ IDEA项目,供所有感兴趣的人使用。

尽管如此,使用Clojure仍然能够完成所有这些工作仍然很好。我确信gen-class会做到这一点,但还没有想出来。