【问题标题】:JavaFX font face dialog in ClojureClojure 中的 JavaFX 字体对话框
【发布时间】:2017-03-24 15:01:14
【问题描述】:

我正在构建一个 Clojure 程序,其中包含一个选项对话框,用户可以在其中选择编辑器中使用的字体。就像许多其他程序一样,我想向用户展示一个 ComboBox,其中下拉列表显示字体本身的字体名称(例如,“Calibri”以 Calibri 字体显示,“Arial”以 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))

编译器在proxy 的声明处的build-font-list-cell 函数中为此抛出一个ExceptionInInitializerError。 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,除非方法是 public。在ListCell 中是protected,但在TextFieldListCell 中是public

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

【问题讨论】:

  • 我想指出; 的那堵墙是不必要的。您可以使用将“注释宏”#_ 放置在表单之前来注释掉整个表单。
  • @Carcigenicate:哇!我不知道。我只是用IDE命令注释掉了一段代码。很酷。
  • 是的。用于注释掉整个测试函数。

标签: javafx clojure


【解决方案1】:

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

方法签名是

public void updateItem(T item, boolean empty)

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

【讨论】:

  • this 是问题的一部分,您可能是对的,但 Clojure 中确实存在 this。正如您指出的文档(和其他人)所说,它对于proxy 是隐含的,但对于reify 甚至是显式的。我将它们放入以消除 IDE 中的错误标志。删除它们没有任何效果——仍然会引发异常。我在函数后面添加了^String 提示以消除反射警告。我将在问题中添加更多上下文,并尝试组合一个简单的 MCVE。
【解决方案2】:

不幸的是,到目前为止,我一直无法弄清楚如何在 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 会这样做,但还没有弄清楚。

【讨论】:

    猜你喜欢
    • 2014-05-08
    • 2013-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-03
    • 1970-01-01
    • 2016-09-20
    • 2023-04-10
    相关资源
    最近更新 更多