【UI编程】将Java awt/swing应用移植到JavaFX纪实

news2025/1/16 0:23:23

1. 背景

最近想做一个实用的小工具,能屏幕截图,录屏和录制课件,简单的图像处理,和制作gif表情包。翻出了很久以前用Java awt/swing写的一个屏幕截图小程序,能运行,但是屏幕截图到剪贴板后,发现不能直接粘贴到网页,很纳闷。研究了半天,猜想是剪贴板上的BufferedImage格式不被网页识别,但如果贴到别的应用中,如word, QQ聊天中,又是可以的。不得已,切换到JavaFX提供的剪贴板功能,发现居然是可以的,看来JavaFX比swing更好用一点。于是决定将该程序移植到JavaFX框架上。

问题缘起

Java屏幕截图到剪贴板:

Robot ro = new Robot();
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension di = tk.getScreenSize();
Rectangle rec = new Rectangle(0, 0, di.width, di.height);
BufferedImage bi = ro.createScreenCapture(rec);
// 接下来选择要截图的区域, 然后粘贴到剪贴板
Transferable trans = new Transferable() {
  ......  // 此处代码网上到处可见,省略掉            
};
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trans, null);

在网页中采用Javascript读取剪贴板上的图片,目前web编辑器支持直接粘贴图片是必备功能。

window.addEventListener("paste", function (e) {
	var items = e.clipboardData.items;
    for (var i = 0; i < items.length; i++) {
         if (items[i].type.indexOf("image") == -1) continue;
         // Retrieve image on clipboard as blob
         var blob = items[i].getAsFile();
         var canvas = document.getElementById("mycanvas");
         var ctx = canvas.getContext('2d');

         // Create an image to render the blob on the canvas
         var img = new Image();
         // Once the image loads, render the img on the canvas
         img.onload = function () {
             // Update dimensions of the canvas with the dimensions of the image
              canvas.width = this.width;
              canvas.height = this.height;

              // Draw the image
              ctx.drawImage(img, 0, 0);
          };
         // Crossbrowser support for URL
         var URLObj = window.URL || window.webkitURL;

         // Creates a DOMString containing a URL representing the object 
         // namely the original Blob
         img.src = URLObj.createObjectURL(imageBlob);
     }            
});

上述代码都堂堂正正,无可指摘,但就是不work。于是切换到JavaFX:

public static void copyToClipboardImage(BufferedImage image) {
        final javafx.scene.input.Clipboard clipboard = javafx.scene.input.Clipboard.getSystemClipboard();
        final ClipboardContent content = new ClipboardContent();
        javafx.scene.image.Image _image = SwingFXUtils.toFXImage(image, null);
        content.putImage(_image);
        clipboard.setContent(content);
    }

这里,将java.awt.BufferedImage转换为javafx.scene.image.Image,才能放到剪贴板。这样网页才能识别了。

2. 重新学习AWT/Swing和JavaFX

上个世纪我就开始用java.awt.Applet进行编程,虽然界面简陋,但也能满足需求。很快swing就出来了,UI控件非常丰富,如果美工给力,完全不输于MFC/WPF等界面框架。总而言之,做跨平台的桌面应用开发,awt/swing是选择之一。目前,其它的界面框架如QT,Electron, 还有SWT,也都是常见选项。但awt/swing灵活性不如JavaFX,目前正慢慢老去,但还不会马上退出历史舞台。
当然,JavaFX还没有完全替代awt/swing的全部功能,例如系统托盘的实现,还得依赖awt类库。

AWT/Swing

为了方便了解swing的UI体系,可以看下面两张图:
在这里插入图片描述
再看一张:
在这里插入图片描述

SWT/JFace

由于不满于swing的性能和丑陋的界面,IBM于2001左右推出了接近原生UI的编程框架swt。该UI类体系和swing也有很大的相似性:
在这里插入图片描述
最基本的SWT例子:

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class SWTFirst {
  public static void main(String[] args) {
    Display display = new Display();
    Shell shell = new Shell(display);

    shell.setText("Hello, world!");

    shell.open();
    // Set up the event loop.
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch()) {
        // If no more entries in the event queue
        display.sleep();
      }
    }
    display.dispose();
  }
}

Shell相当于JFrame。在此基础上,IBM进一步推出JFace和RCP框架,让开发者一度看到了Java统治桌面端开发的希望。可惜,世易时移,桌面开发已经逐步让位于web和移动开发。这些优秀的技术也没能大面积使用开来。但福音就是,她们还活着,而且还在发展。

JavaFX基础

在这里插入图片描述

JavaFX提供了几种预定义的布局,如HBox, VBox, Border Pane, Stack Pane, Text Flow, Anchor Pane, Title Pane, Grid Pane, Flow Panel。

State舞台是一个类似于Swing中的JWindow的顶级容器,代表一个窗口。它用于容纳场景Scene,场景Scene是一个类似于Swing的JFrame的容器。

Stage stage = new Stage();

Group root = new Group();
root.getChildren().add(box);
Scene scene = new Scene(root, 400, 300);
scene.setFill(Color.BLACK);
stage.setWidth(600);
stage.setHeight(600);   
stage.setTitle("Hello");
stage.setScene(scene);
stage.show();       

jfx支持布局配置化,且支持采用CSS定义。

try {
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("NewWindow.fxml"));
        /* 
         * if "fx:controller" is not set in fxml
         * fxmlLoader.setController(NewWindowController);
         */
        Scene scene = new Scene(fxmlLoader.load(), 600, 400);
        Stage stage = new Stage();
        stage.setTitle("New Window");
        stage.setScene(scene);
        stage.show();
    } catch (IOException e) {
        Logger logger = Logger.getLogger(getClass().getName());
        logger.log(Level.SEVERE, "Failed to create new Window.", e);
    }

3. awt/swing向JavaFX(后续叫jfx)移植中的几点笔记

界面控件的移植

  • JFrame -> Application & Stage: swing中我们在main()函数里创建一个JFrame来启动界面,jfx中是通过Application.launch() 来完成的,主类继承Application。通常我们无需显式创建一个Stage实例,这点和swing不同。
  • JPanel -> Scene & Pane: Scene对应于JPanel, 但实际盛放控件的容器是Pane,有了Pane,再创建一个scene 即可. Pane才是带布局(layout)的容器。
  • JButton -> Button
  • JLabel -> Label:大部分UI组件,将J去掉就成了jfx的对应物。
    In many cases, removing the J will do the trick…
  • JOptionPane -> Dialog
  • ActionListener -> EventHandler< ActionEvent >) : swing需要实现一个listener的全部方法,jfx不需要。Java8开始利用函数式编程,写法更简洁:
scene.setOnMousePressed(event -> onMousePressed(event));
scene.setOnMouseReleased(event -> onMouseReleased(event));

BufferedImage

BufferedImage在swing图形绘制中扮演着重要角色。在JavaFX 2.2中,类似的类为javafx.scene.image.WritableImage, 它是javafx.scene.image.Image的子类。
获得WritableImage对象:

// Obtain a snapshot of the canvas
WritableImage image = canvas.snapshot(null, null);

将BufferedImage转为javafx.scene.image.Image:

BufferedImage image = ...;
javafx.scene.image.Image _image = SwingFXUtils.toFXImage(image, null);

Canvas与图形绘制

在awt/swing中,每个组件都有一个paint(Graphics g)方法。该方法被框架调用,完成组件绘制。但JavaFX中没有这么一个方法。我们可以先创建一个Canvas对象,放到组件里,然后再获得图形设备来完成绘制。

Canvas canvas = new Canvas(bounds.getWidth(), bounds.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREEN);
gc.setStroke(Color.BLUE);
gc.setLineWidth(5);
gc.strokeLine(40, 10, 10, 40);
gc.fillOval(10, 60, 30, 30);
gc.strokeOval(60, 60, 30, 30);
gc.fillRoundRect(110, 60, 30, 30, 10, 10);
gc.strokeRoundRect(160, 60, 30, 30, 10, 10);
gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN);
gc.fillArc(60, 110, 30, 30, 45, 240, ArcType.CHORD);
gc.fillArc(110, 110, 30, 30, 45, 240, ArcType.ROUND);
gc.strokeArc(10, 160, 30, 30, 45, 240, ArcType.OPEN);
gc.strokeArc(60, 160, 30, 30, 45, 240, ArcType.CHORD);
gc.strokeArc(110, 160, 30, 30, 45, 240, ArcType.ROUND);

组件的重绘

在awt/swing框架中,每个组件提供了paintComponent(),能实现组件的界面绘制,该方法无需用户显式调用。重绘在界面移动,大小改变时都会发生。

public class MyPanel extends JPanel {
    private static final long serialVersionUID = -26977787951133480871L;

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.clearRect(0, 0, getWidth(), getHeight());
        g.setColor(java.awt.Color.RED);
        g.fillRect(10, 10, getWidth() - 20, getHeight() - 20);

        // Paint your custom image here:
        g.drawImage(someImage, 0, 0, null);
    }
}

在JavaFX中,可以通过重写layoutChildren()方法实现类似的功能:

public class MyPane extends Pane {
    private final Canvas canvas;

    public MyPane() {
        canvas = new Canvas(getWidth(), getHeight());
        getChildren().add(canvas);
        widthProperty().addListener(e -> canvas.setWidth(getWidth()));
        heightProperty().addListener(e -> canvas.setHeight(getHeight()));
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        GraphicsContext gc = canvas.getGraphicsContext2D();
        gc.clearRect(0, 0, getWidth(), getHeight());
        gc.setFill(Color.RED);
        gc.fillRect(10, 10, getWidth() - 20, getHeight() - 20);

        // Paint your custom image here:
        gc.drawImage(someImage, 0, 0);
    }
}

4. 关于Java GUI编程的一些有趣话题

Java能否胜任2D/3D游戏编程?

我们知道,有了Netty,Java已经成功征服了服务端编程,可以说,服务端才是Java真正的用武之地。那有了JavaFX,加上SWT/JFace,是不是桌面端都能Java开发呢?如果不涉及图像处理,那答案几乎是肯定的。从JBuilder到Netbean/Eclipse到Intellij Idea已经证明了这一点,这些都是重量级桌面软件的代表。
首先,Java开发2D游戏完全没问题。目前已经有 JOGL 和 LWJGL 两个基础库能用于创建 2D 和 3D 游戏,这两个框架在底层构建于OpenGL之上,已经比较好地解决了诸如纹理和利用图形卡渲染的根本问题。对于3D游戏,目前已有jMonkeyEngine游戏引擎,应付中小型游戏应该还是足够的。

有哪些采用Java GUI实现的软件?

JBuilder

上古产品,IDE神器,也是采用Swing制作的,一直不肯开源,终导致被Eclipse灭亡。

IntelliJ IDEA

主窗口为IdeFrameImpl. 窗口里面是pure Swing components. 一些插件会采用JavaFX . 从 2020.2 版本开始采用一个Chromium组件JCEF.

在这里插入图片描述
而且IDEA在对一些Swing组件做了增强,如JFrame和JWindow,使得它们更加精致漂亮。JetBrains团队还有自己的JVM,基于OpenJDK。
在这里插入图片描述

一些UI特性也只能跑在JetBrains自家的JVM上。大部分对话框基于DialogWrapper类,参见com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.MyDialog。

源码位于:https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java

主要的Java图像处理库

  • 强大的图像处理库ImageJ
  • OpenIMAJ
  • JFree: https://github.com/jfree

总结

本文试图给不熟悉Java界面编程的同学一个简要的入门指引,抛砖引玉而已。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/10441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解JavaScript-this关键字

先说结论&#xff1a;谁调用它&#xff0c;this 就指向谁 前言 在讲 Function、作用域 时&#xff0c;我们都讲到了 this&#xff0c;因为 JavaScript 中的作用域是词法作用域&#xff0c;在哪里定义&#xff0c;就在哪里形成作用域。而与词法作用域相对应的还有一个作用域叫…

MP157-0-遇见的问题及解决办法

MP157-0-遇见的问题及解决办法1.Win11运行VMware15虚拟机崩溃死机&#xff0c;蓝屏。1.Win11运行VMware15虚拟机崩溃死机&#xff0c;蓝屏。 时间&#xff1a;2022.11.15 解决办法&#xff1a; Hyper-V方案。 打开控制面板-程序-启用或关闭Windows功能&#xff0c;可能你的电…

【JavaScript高级】03-JavaScript内存管理和闭包

JavaScript内存管理和闭包JavaScript内存管理垃圾回收机制算法常见的GC算法-标记清除闭包闭包的概念理解闭包的形成过程闭包的内存泄露JavaScript内存管理 JavaScript会在定义数据时为我们分配内存&#xff1a; JS对于原始数据类型内存的分配会在执行时&#xff0c;直接在栈空…

Sentinel使用教程

文章目录一、Sentinel简介1.sentinel介绍2.sentinel应用场景3.sentinel与hystrix4.sentinel组件介绍二、Sentinel使用说明1.控制台Dashboard2.Sentinel 流量控制和熔断降级3.常见报错解决一、Sentinel简介 1.sentinel介绍 Sentinel 是由阿里巴巴中间件团队开发的开源项目&…

Java三大特性篇之——继承篇(超详解的好吧!)

&#x1f60d;&#x1f60d;&#x1f60d;欢迎欢迎欢迎欢迎&#xff0c;我的朋友&#xff0c;答应我&#xff0c;看完好吗&#xff1f;&#x1f974; 文章目录前言&#xff1a;何为继承&#xff1f;不谈钱的继承实现&#xff01;嘘&#xff1a;偷偷访问父类的私密成员&#xff…

OkHttp相关知识(二)

okhttp中一次网络请求的大致过程&#xff1a; Call对象对请求的封装 dispatcher对请求的分发 getResponseWithInterceptors()方法 一、OkHttp同步方法总结&#xff1a; 创建OkHttpClient和构建了携带请求信息的Request对象将Request封装成Call对象调用Call的execute()发送…

【11.16】Codeforces 刷题

DP\text{DP}DP &#xff1a;&#xff08;今天做的这两道都没啥 DP 相关来着 D. Match & Catch 题意&#xff1a; 给定两个字符串 1≤∣s1∣,∣s2∣≤50001\leq |s_1|,|s_2|\leq 50001≤∣s1​∣,∣s2​∣≤5000 &#xff0c;求最短的满足各只出现一次的连续公共字串。 思…

实验27:红外遥控三级控速风扇实验

今天介绍一个稍微复杂点的实验,复杂在设计和代码 ——OK,受了抖音西湖大学教授刺激,任何人都可以做研究 ——实验:红外遥控三级风速小电扇 ——每按一下CH-,风速从1-2-3-1-2-3-1循环 ——按下CH+,风扇停止 ——没有背景音乐目的是听风扇声音大小判断风速 OK实验介绍完了…

五个可以永远相信的神仙网站推荐

早八的我们是不是偶尔会处在焦虑中呢&#xff1f;一方面年轻人工作压力大&#xff0c;另一方面我们偶尔会感慨我们的碌碌无为&#xff0c;不知道怎样提升自己。今天为大家推荐五个焦虑时可以随手打开看&#xff0c;不知不觉悄悄提升自己的软件。 1.全历史 全历史是一个把历史以…

《元宇宙2086》亮相金鸡奖中国首部元宇宙概念院线电影启动

2022年中国金鸡百花电影节暨第35届中国电影金鸡奖于11月10日至12日在福建厦门举办&#xff0c;中国动漫集团控股子公司北京中文发文化发展有限公司与《元宇宙2086》作者高泽龙在金鸡奖创投论坛正式签约&#xff0c;宣布将共同启动筹拍中国首部元宇宙概念的院线电影。 当日下午&…

如何在Docker中安装MySQL数据库

1、Docker环境 视频教程&#xff1a;https://www.bilibili.com/video/BV1xv4y1S7kA 2、搜索镜像 https://hub.docker.com/网站搜索MySQL&#xff0c;确定其安装版本&#xff0c;这里安装8.0.31版&#xff1b; 3、拉取镜像 [rootlocalhost ~]# docker pull mysql:8.0.31 8.…

市级专精特新的申报条件

一、基本条件&#xff1a;&#xff08;各市政策不同具体情况也不同&#xff0c;下面为济南市企业的申报条件&#xff09; 1、连续经营3年以上&#xff0c;上年度企业营业收入在800万元以上&#xff1b; 2、近两年营业收入复合增长率不低于8%&#xff08;2021年参照国 家级调…

Nginx 反向代理

title: Nginx 反向代理 date: 2022-11-16 10:24 tags: [Nginx,反向代理,正向代理,代理] 文章目录〇、问题一、前言二、正向代理&反向代理2.1 正向代理2.2 反向代理三、Nginx配置反向代理参考更新〇、问题 什么是正向代理&#xff1f;什么是反向代理&#xff1f;Nginx如何配…

Mysql之视图、索引【第五篇】

大纲&#xff1a; 一、视图 1、什么是视图&#xff1f; 1) MySQL 视图(View)是一种虚拟的表&#xff0c;是从数据库中一个或多个表中导出来的表。视图由列和行构成&#xff0c;行和列的数据来自于定义视图的查询中所使用的表&#xff0c;并且还是在使用视图时动态生成的。 …

【蓝桥杯物联网赛项学习日志】Day3 关于IIC

经过昨天的学习&#xff0c;已经了解和初步学会配置CubeMax进行初始化配置。今天就开始下一章节的学习&#xff0c;关于IIC。 关键词&#xff1a;I2C OLED SSD1306 理论基础 串行通信接口通讯方式分&#xff0c;可以分为两种&#xff0c;分别是同步和异步。按照数据的传输方…

组成目标货币的最少张数

1、题目 arr 是货币数组&#xff0c;其中的值都是正数。再给定一个正数 aim。 每个值都认为是一张货币&#xff0c;返回组成 aim 的最少张数。 注意&#xff1a;因为是求张数&#xff0c;所以每张货币认为是相同或不同就不重要了。 2、思路 假设 arr [3&#xff0c;1&…

GD32F450的时钟笔记

GD32F450 标称 200MHz&#xff0c;但是在手册中又说 它是 240MHz。本文以 手册中的 240MHz 进行举例&#xff0c;我保险起见&#xff0c;产品中使用还是在 200MHz 下使用。 时钟树 手册上的时钟树图如下 GD32F450的 外部时钟源 有2个 LXTAL 外部低速时钟源 32.768 kHzHXTAL …

微信小程序登录获取不到头像和昵称解决办法!

微信小程序登录获取不到头像和昵称主要原因是&#xff1a;小程序wx.getUserProfile接口被收回&#xff01; 大家可以按照文档操作↓ PS&#xff1a; 针对小程序wx.getUserProfile接口将被收回后做出的授权调整 小程序文档中提出的调整说明 对于此次变化&#xff0c;现将小…

怎么使用股票委托下单接口?

都知道&#xff0c;在进行量化交易的时候&#xff0c;交易接口可以100%严格按照定制的计划执行交易&#xff0c;避免了出现下单犹豫和过量交易等问题&#xff0c;100%体现交易策略的收益性&#xff1b;今天主要来聊聊&#xff0c;关于怎么使用股票委托下单接口的问题&#xff1…

使用c#将aj-report桌面化1

说到底,aj-report是个工具,我想大多数人还是想快速使用它来创建一个可以展示的工具。通过之前的章节,你应该可以制作自己的报表页面了,下面我们来看看怎么把aj-report包装成一个桌面能够运行的软件。 当然作为扩展开发,受开源协议限制,我们不能大规模修改aj-report的源代…