JavaFX是Java程式語言的圖形使用者介面工具包,它可以輕鬆創建豐富、現代化的使用者介面。在實際開發中,有時候需要載入JavaFX映像並將其儲存到資料庫中。本文將為您介紹如何實現這項操作,透過簡單易懂的步驟,幫助您解決這個問題。讓我們一起來探討吧!
我現在正在努力將一個小圖像保存在資料庫中並將其加載以用作 javafx.scene.image.Image。我嘗試過的幾個解決方案涉及使用 SwingFXUtils.fromFXImage 和 SwingFXUtils.toFxImage,但該類別似乎需要 module-info.java 檔案中的一個條目,而我甚至不打算使用該條目。
我在嘗試使用 SwingFXUtils 時遇到此例外:java.lang.NoClassDefFoundError: javafx/embed/swing/SwingFXUtils
。
當我使用模組系統時,它破壞了我正在使用的另一個庫(itextpdf)。 所以我想避免這種情況,只是找到另一種方法來保存和載入資料庫中的 JavaFX 映像。 有什麼建議嗎?
您提到:
這不是真的。儘管 javafx 僅支援作為命名模組載入1,但 javafx 並不要求您自己的程式碼是模組化的。獲得 noclassdeffounderror
的唯一方法是在執行時間未能將類別包含在模組路徑/類別路徑中(它必須在編譯時存在,否則您的程式碼將無法編譯)。我建議閱讀 Getting Started with JavaFX,了解如何使用主要 java ide 和/或建置工具之一來設定基本 javafx 應用程式。但是,如果不知道您如何配置項目以及如何運行項目,我們將無法告訴您您的設定具體出了什麼問題。
也就是說,您想要完成的整體目標確實是可能的。以下是一個範例,可讓您瀏覽電腦上的圖像並將其保存到記憶體中的 h2 資料庫中。映像的 id 和名稱被放入 tableview
中,其中包含一個帶有「開啟」按鈕的列,可讓您查看從記憶體資料庫載入的映像。影像以 png 格式作為 blob 儲存在資料庫中,無論其原始格式為何。 swingfxutils
和 imageio
類別用於將 javafx 映像轉換為 png「檔案」2。
此範例未向您展示如何部署應用程式(例如,透過 jpackage
)。
以下是我用於建立和運行範例的庫和工具的版本。
java 21.0.1(eclipse adoptium / temurin)
javafx 21.0.1
h2 2.2.224
gradle 8.4
JavaFX Gradle Plugin 0.1.0
在comment 至previous question2 中,您聲明您正在使用gradle,因此我在範例中使用的是gradle。
沒有 module-info.java
檔案。
imagerecord.java
package com.example; public record imagerecord(int id, string name) {}
main.java
#package com.example; import java.io.file; import java.util.concurrent.executor; import java.util.concurrent.executors; import java.util.function.consumer; import javafx.application.application; import javafx.beans.property.simpleintegerproperty; import javafx.beans.property.simpleobjectproperty; import javafx.beans.property.simplestringproperty; import javafx.concurrent.task; import javafx.geometry.insets; import javafx.geometry.pos; import javafx.scene.scene; import javafx.scene.control.button; import javafx.scene.control.scrollpane; import javafx.scene.control.tablecell; import javafx.scene.control.tablecolumn; import javafx.scene.control.tableview; import javafx.scene.image.image; import javafx.scene.image.imageview; import javafx.scene.layout.borderpane; import javafx.scene.layout.hbox; import javafx.stage.filechooser; import javafx.stage.stage; import javafx.stage.stagestyle; import javafx.stage.window; public class main extends application { private final executor executor = executors.newvirtualthreadpertaskexecutor(); private final imagesdatabase db = new imagesdatabase("test"); private final imagerepository imagerepo = new imagerepository(db); private file lastdirectory; @override public void start(stage primarystage) { var table = createtable(record -> displayimage(primarystage, record)); var choosebtn = new button("choose image..."); choosebtn.setonaction( e -> { e.consume(); var image = chooseimage(primarystage); if (image != null) { executor.execute(createsaveimagetask(image, table.getitems()::add)); } }); var root = new borderpane(); root.settop(choosebtn); root.setcenter(table); borderpane.setalignment(choosebtn, pos.center); borderpane.setmargin(choosebtn, new insets(10)); primarystage.setscene(new scene(root, 600, 400)); primarystage.show(); } @override public void stop() throws exception { db.close(); } private image chooseimage(window owner) { var chooser = new filechooser(); chooser.settitle("choose image file"); chooser .getextensionfilters() .add(new filechooser.extensionfilter("image files", "*.jpeg", "*.jpg", "*.png")); if (lastdirectory != null) { chooser.setinitialdirectory(lastdirectory); } var file = chooser.showopendialog(owner); if (file != null) { lastdirectory = file.getparentfile(); return new image(file.touri().tostring()); } return null; } private void displayimage(window owner, imagerecord record) { var view = new imageview(); var task = creategetimagetask(record, view::setimage); executor.execute(task); var sp = new scrollpane(view); sp.setpannable(true); var window = new stage(stagestyle.utility); window.initowner(owner); window.settitle(record.name()); window.setscene(new scene(sp, 500, 300)); window.setonhiding(e -> task.cancel()); window.show(); } private tableview<imagerecord> createtable(consumer<imagerecord> onopen) { var table = new tableview<imagerecord>(); table.setcolumnresizepolicy(tableview.constrained_resize_policy_flex_last_column); var idcol = new tablecolumn<imagerecord, number>("id"); idcol.setcellvaluefactory(data -> new simpleintegerproperty(data.getvalue().id())); table.getcolumns().add(idcol); var namecol = new tablecolumn<imagerecord, string>("name"); namecol.setcellvaluefactory(data -> new simplestringproperty(data.getvalue().name())); table.getcolumns().add(namecol); var openbtncol = new tablecolumn<imagerecord, imagerecord>(); openbtncol.setcellvaluefactory(data -> new simpleobjectproperty<>(data.getvalue())); openbtncol.setcellfactory(tc -> createopenbuttoncell(onopen)); table.getcolumns().add(openbtncol); return table; } private tablecell<imagerecord, imagerecord> createopenbuttoncell(consumer<imagerecord> onopen) { return new tablecell<>() { final hbox container = new hbox(); final button openbutton = new button("open"); { container.getchildren().add(openbutton); container.setalignment(pos.center); openbutton.setonaction( e -> { e.consume(); var item = isempty() ? null : getitem(); if (item != null) { onopen.accept(item); } }); } @override protected void updateitem(imagerecord item, boolean empty) { super.updateitem(item, empty); if (empty || item == null) { setgraphic(null); } else { setgraphic(container); } } }; } private task<?> createsaveimagetask(image image, consumer<imagerecord> onsuccess) { return new task<imagerecord>() { @override protected imagerecord call() throws exception { return imagerepo.insertimage(image); } @override protected void succeeded() { onsuccess.accept(getvalue()); } @override protected void failed() { getexception().printstacktrace(); } }; } private task<?> creategetimagetask(imagerecord record, consumer<image> onsuccess) { return new task<image>() { @override protected image call() throws exception { return imagerepo.getimage(record).orelsethrow(); } @override protected void succeeded() { onsuccess.accept(getvalue()); } @override protected void failed() { getexception().printstacktrace(); } }; } }
imagerepository.java
package com.example; import static java.sql.statement.return_generated_keys; import java.io.bytearrayinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.inputstream; import java.io.uncheckedioexception; import java.sql.sqlexception; import java.util.arraylist; import java.util.list; import java.util.objects; import java.util.optional; import java.util.concurrent.atomic.atomicinteger; import javafx.embed.swing.swingfxutils; import javafx.scene.image.image; import javax.imageio.imageio; public class imagerepository { private static final string select_all_records_sql = "select id, name from images"; private static final string select_image_sql = "select image from images where id = ?"; private static final string insert_sql = "insert into images (name, image) values (?, ?)"; private final atomicinteger generatednamecount = new atomicinteger(); private final imagesdatabase db; public imagerepository(imagesdatabase db) { this.db = db; } public list<imagerecord> getrecords() throws sqlexception { return db.execute( conn -> { try (var stat = conn.createstatement()) { var result = stat.executequery(select_all_records_sql); var records = new arraylist<imagerecord>(); while (result.next()) { int id = result.getint(1); var name = result.getstring(2); records.add(new imagerecord(id, name)); } return records; } }); } public optional<image> getimage(imagerecord record) throws sqlexception { return getimage(record.id()); } public optional<image> getimage(int recordid) throws sqlexception { if (recordid <= 0) { throw new illegalargumentexception("recordid <= 0: " + recordid); } return db.execute( conn -> { try (var stat = conn.preparestatement(select_image_sql)) { stat.setint(1, recordid); var result = stat.executequery(); if (result.next()) { var image = new image(result.getbinarystream(1)); return optional.of(image); } else { return optional.empty(); } } }); } public imagerecord insertimage(image image) throws sqlexception { objects.requirenonnull(image); return db.execute( conn -> { try (var stat = conn.preparestatement(insert_sql, return_generated_keys)) { var name = getimagename(image); stat.setstring(1, name); stat.setbinarystream(2, imagetoinputstream(image)); stat.executeupdate(); var keys = stat.getgeneratedkeys(); if (keys.next()) { int id = keys.getint(1); return new imagerecord(id, name); } else { throw new illegalstateexception("generated key not returned"); } } }); } private string getimagename(image image) { var source = image.geturl(); return source == null ? generateimagename() : source; } private string generateimagename() { return "generated image name " + generatednamecount.incrementandget(); } private inputstream imagetoinputstream(image image) { var out = new bytearrayoutputstream(); try { imageio.write(swingfxutils.fromfximage(image, null), "png", out); } catch (ioexception ex) { throw new uncheckedioexception(ex); } return new bytearrayinputstream(out.tobytearray()); } }
imagesdatabase.java
package com.example; import java.sql.connection; import java.sql.sqlexception; import java.util.objects; import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; import javax.sql.datasource; import org.h2.jdbcx.jdbcdatasource; public class imagesdatabase implements autocloseable { private static final string create_table_sql = "create table images (id identity, name varchar(255), image blob)"; @functionalinterface public interface sqlfunction<t> { t execute(connection connection) throws sqlexception; } private final lock mutex = new reentrantlock(); private final datasource source; private connection connection; private boolean open = true; private boolean initialized; public imagesdatabase(string name) { if (name.isblank()) { throw new illegalargumentexception("blank name"); } var source = new jdbcdatasource(); source.seturl("jdbc:h2:mem:" + name + ";db_close_delay=-1"); this.source = source; } public <t> t execute(sqlfunction<t> function) throws sqlexception { objects.requirenonnull(function); mutex.lock(); try { checkopen(); return function.execute(getoropenconnection()); } finally { mutex.unlock(); } } private connection getoropenconnection() throws sqlexception { if (connection == null || connection.isclosed()) { connection = source.getconnection(); initialize(connection); } return connection; } private void initialize(connection conn) throws sqlexception { if (!initialized) { try (var stat = conn.createstatement()) { stat.executeupdate(create_table_sql); } initialized = true; } } private void shutdown() throws sqlexception { if (initialized) { try (var conn = getoropenconnection(); var stat = conn.createstatement()) { stat.execute("shutdown"); } connection = null; } } private void checkopen() { if (!open) { throw new illegalstateexception("closed"); } } @override public void close() throws sqlexception { mutex.lock(); try { if (open) { open = false; shutdown(); } } finally { mutex.unlock(); } } }
我使用了 kotlin dsl,但如果您願意,您也可以使用 groovy dsl。
settings.gradle.kts
rootproject.name = "h2images-example"
build.gradle.kts
#plugins { id("org.openjfx.javafxplugin") version "0.1.0" application } group = "com.example" version = "1.0" javafx { modules("javafx.controls", "javafx.swing") version = "21.0.1" } application { mainclass.set("com.example.main") } repositories { mavencentral() } dependencies { implementation("com.h2database:h2:2.2.224") }
您可以使用以下命令執行上述內容:
./gradlew run
注意 ./gradlew
呼叫 Gradle Wrapper。如果您的電腦上安裝了 gradle 版本,則可以透過以下方式產生版本 8.4 的包裝器:
gradle wrapper --gradle-version 8.4
1. javafx 在技術上不支援從類別路徑載入。這意味著理想情況下,javafx 模組應該位於模組路徑上並解析為命名模組,即使您自己的程式碼和其他依賴項是從類別路徑載入的。但是,我不知道如果 javafx 位於類別路徑上(至少從 javafx 21 開始),會發生什麼中斷,除了您的主類別不能再是 javafx.application 的子類別。 application
(您需要一個單獨的「啟動器類別」作為主類別)。只需要知道,由於 javafx 位於類別路徑上而導致的任何問題都不太可能被 javafx 團隊修復。
請注意,openjfx 提供的 gradle 和 maven 外掛程式會配置這些建置工具以將 javafx 放在模組路徑上。
2. 根據您的兩個問題的上下文,該範例將 image
物件轉換為 png 位元組。但是,如果您已經以位元組形式接收映像(即作為本地或遠端檔案),那麼將這些位元組直接放入資料庫可能會更容易、更有效率。
以上是載入 JavaFX 映像並將其儲存到資料庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!