当前位置: 首页 > news >正文

使用 Shadcn UI 构建 Java 桌面应用

在这里插入图片描述

许多桌面应用程序,如 Slack、Notion、Microsoft Teams 和 Linear,都采用基于 Web 的用户界面。这已成为现代软件开发中的常见做法,开发者可以借助熟悉的 Web 技术构建应用,从而简化开发流程。

在本篇文章中,我们将向您如何基于 shadcn/ui、React、Tailwind CSS 和 TypeScript,开发一款具有现代网页界面的跨平台 Java 桌面应用程序。

Web vs 原生 UI

传统的 Java 原生 UI 工具包(如 Swing、JavaFX 和 SWT)存在诸多局限:

  • 维护困难:UI 修改复杂,难以构建现代化的界面。
  • 功能缺失:对富样式、动态图形、动画和过渡效果的支持有限。
  • 界面陈旧:默认控件外观笨重,开发者不得不自行定制。

这并不令人意外,因为这些工具包设计于多年前,早已无法满足当今界面开发的需求。

相比之下,Web UI 大幅简化了开发流程。它拥有丰富的现成库和成熟的生态系统,同时还有活跃的社区提供支持。借助这些优势,开发者能够轻松实现高 DPI 显示适配、触摸屏交互支持、针对不同屏幕尺寸的自适应布局,以及跨平台统一的界面效果。

将 Web UI 引入桌面应用,不仅带来了网页开发的诸多优势,还避免了与过时 UI 工具包打交道的繁琐,也无需再费力寻找掌握老旧技术的开发者。

Java 桌面应用中的 Web UI 实践

在本文中,我们将构建一个具有 Web 界面的 Java 桌面应用程序。我们将使用 shadcn/ui —— 这是一个基于 React 的组件库,提供即用型、响应式界面元素。同时,编程语言使用 TypeScript。

该应用将展示一个偏好设置对话框,并将用户的设置保存到本地文件系统中,从而在重启后依然保留设置。以下是其在 macOS 上的实际界面截图:

基于 Web UI 的桌面应用界面截图

基于 Web UI 的桌面应用界面截图

完整源代码可在 GitHub 上获取。

在开发基于 Web UI 的桌面应用时,需要解决以下关键问题:

  1. 可靠的 Web 视图组件:Java 内置组件无法满足现代 Web 标准。
  2. 无服务器资源加载:避免依赖本地或远程服务器,简化部署流程。
  3. Java 与 JavaScript 通信:实现文件系统读写等操作,无需 Web 服务器。

应用窗口与 Web 视图

我们将使用 Swing 的 JFrame 创建一个窗口。JFrame 是 Java 的内置组件,简单易用。接着,我们将从 JxBrowser 添加一个基于 Chromium 的 Web 视图组件,用于在此窗口中显示 Web 内容。

以下是将 Web 视图添加到 Java 窗口的方法:

var engine = Engine.newInstance(HARDWARE_ACCELERATED);
var browser = engine.newBrowser();SwingUtilities.invokeLater(() -> {var view = BrowserView.newInstance(browser);var frame = new JFrame("Application");frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {engine.close();}});frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);frame.add(view, BorderLayout.CENTER);frame.setSize(1280, 900);frame.setLocationRelativeTo(null);frame.setVisible(true);
});

在桌面应用中加载 Web UI

Web 部分是一个标准的 React 应用,我们将其加载到上面创建的浏览器组件中。

加载 Web UI 的方式取决于当前是开发环境还是生产环境。在开发环境中,我们使用典型的 Web 开发流程:启动本地开发服务器,支持热更新和其他常见功能。而在生产环境中,我们需要一个压缩后的、可离线运行的安全版本,避免依赖任何本地或远程服务器。

开发环境

在开发环境中加载 Web 应用非常直接。我们只需启动本地开发服务器并导航到 localhost:

./gradlew startDevServer
if (!AppDetails.isProduction()) {  browser.navigation().loadUrl("http://localhost:[port]");
} else {...
}
生产环境

在生产环境中,我们不能继续使用本地开发服务器的方式,因为此时服务器已不存在。同时,使用远程服务器也不可取。我们的目标是让 Web 应用具备离线运行能力。

此外,无论是本地还是远程服务器,都会带来额外的安全风险:用户可能通过浏览器直接访问 Web 应用的地址,从而查看其源代码,暴露敏感逻辑。显然,这并非我们所希望的。我们希望 Web UI 只能在桌面应用内部访问,并将其源代码完全隐藏在应用包中。

为了实现这一点,我们将 Web 应用文件添加到资源中,并使用 JxBrowser 的 UrlRequest 拦截器 API (Interceptor API)从类路径提供这些文件。

为了允许请求拦截器处理 Web 资源请求,我们将其分配给一个自定义协议:

var options = EngineOptions.newBuilder(HARDWARE_ACCELERATED).addScheme(Scheme.of("jxb"), new UrlRequestInterceptor());
var engine = Engine.newInstance(options.build());

每当浏览器尝试加载 jxb:// URL 时,拦截器就会处理该请求,并从应用的资源目录中返回 index.html。之后,网页请求 CSS、JavaScript 文件等其他资源时,拦截器也会一并处理并返回相关文件。

通过这种方式,所有 Web 资源的加载都发生在应用内部,外部无法拦截,也无法访问这些资源。

同时,使用自定义协议可确保常规 HTTPS/XHR 请求(例如身份验证、API 调用)能够顺利通过。
为了在开发 URL 和生产 URL 之间切换,我们使用以下方法:

if (!AppDetails.isProduction()) {browser.navigation().loadUrl("http://localhost:[port]");
} else {browser.navigation.loadUrl("jxb://my-app.com");
}

Web 与 Java 的通信

就像任何 Web 应用程序与后端通信一样,我们的 Web 应用程序也需要与负责读取偏好设置并将其保存到文件系统的 Java 代码进行通信。

JxBrowser JavaScript-Java Bridge API

最直接的方式是从 JavaScript 直接调用 Java 代码。大多数 Web 视图组件都支持这一功能:有的通过 JSON 对象进行数据交换,有的,如 JxBrowser,甚至允许直接调用 Java 方法、访问 Java 对象。

以下是在 JxBrowser 中设置 JavaScript 和 Java 之间通信的方法:

@JsAccessible
class PrefsService {void setFontSize(int size) {}
}
declare class PrefsService { setFontSize(size: number): void; 
}declare const prefService: PrefsService;
...
prefsService.setFontSize(12);

这种方式适用于小型项目。当通信方法较少时,维护起来尚属轻松,也不容易出错。

但随着项目规模的扩大,这种方式变得越来越难以管理,也更容易出错。由于缺乏编译期检查和自动补全支持,出错的可能性增加,同时难以及时发现它们。对于大型项目,我们需要一种更稳健的通信协议以及自动生成代码的机制,以确保可靠性——否则,我们最终会陷入大量的 bug 之中。

在本节中,我们将提出一个更好的解决方案。

Protobuf + gRPC

我们决定采用 Protobuf 方案。这项技术使我们能够以简单的格式定义 API,并自动生成类型安全的客户端和服务器代码。我们在 .proto 文件中定义消息与服务,随后 Protobuf 会为我们自动生成对应的 Java 和 TypeScript 代码。

service PrefsService {rpc SetFontSize(FontSize) returns (google.protobuf.Empty);
}enum FontSize {SMALL = 0;DEFAULT = 1;LARGE = 2;
}

这种方式建立了一个稳定且类型安全的通信契约,同时还会提供一些有助于加快开发速度的有用功能,例如代码自动补全和类型检查。

我们使用 gRPC 作为可以发送/接收 Protobuf 消息的传输层。通常情况下,Java 端运行 gRPC 服务器,而 Web 端充当客户端。

class PrefsService extends PrefsServiceImplBase {@Overridepublic void setTheme(Theme request, StreamObserver<Empty> responseObserver) {}
}
...
var serverBuilder = Server.builder().http(50051).service(GrpcService.builder()// 注册服务器的实际实现。.addService(new PrefsService()).build());try (var server = serverBuilder.build()) {server.start();server.blockUntilShutdown();
}

当服务器启动后,下一步就是连接 gRPC 客户端。

import {createGrpcWebTransport} from "@connectrpc/connect-web";
import {createClient} from "@connectrpc/connect";
import {PreferencesService} from "@/gen/prefs_pb.ts";const transport = createGrpcWebTransport({baseUrl: `http://localhost:50051`,
});
const prefsClient = createClient(PrefsService, transport);

然后,可以使用 prefsClient 来接收和更新偏好设置:

prefsClient.setFontSize(FontSize.SMALL);

通信示意图

通信示意图

总结

基于 Web UI 的桌面应用开发具有显著优势:

  • 简化开发:丰富的组件库和活跃的社区支持。
  • 现代化界面:轻松实现响应式设计、动画效果等。
  • 跨平台一致性:界面在不同操作系统上表现一致。

在本文中,我们构建了一个简单的 Java 桌面应用程序,其 UI 使用 shadcn/ui、React、Tailwind CSS 和 TypeScript 创建。

在开发过程中,我们解决了构建 Web UI 桌面应用中开发者面临的几项关键难题,包括:

  1. 在 Java 窗口中嵌入 Web 视图以展示 Web UI。
  2. 无需依赖本地或远程服务器加载 Web 资源。
  3. 实现 Java 与 JavaScript 的通信。

带有 Web UI 的桌面 Java 应用程序如下所示:

带有 Web UI 的桌面 Java 应用程序

相关文章:

  • 目标检测 LW-DETR(2024)详细解读
  • 【单片机】如何产生负电压?
  • 深度图转换为点云文件脚本
  • MFC 捕捉桌面存成jpg案例代码
  • DDR中Geardown Mode理解/2N模式理解
  • windows安装python环境
  • 项目执行中缺乏问题记录和总结,如何改进?
  • 【Java高阶面经:数据库篇】12. MySQL锁机制全解:从行锁到死锁优化的深度指南
  • 网络流量分析工具ntopng的安装与基本使用
  • 现代计算机图形学Games101入门笔记(十九)
  • 制造业ERP系统选型与实施避坑探讨
  • OneDrive登录,账号跳转问题
  • leetcode hot100刷题日记——8.合并区间
  • Java泛型详解 —— 出参入参绑定技巧
  • 唯创安全优化纸业车间安全环境:门口盲区预警报警器的应用与成效
  • Mariadb cpu 93% 问题
  • SpringBoot Web 入门
  • 线上问题排查
  • 年度工作计划总结述职报告PPT模版一组分享
  • 第19天-Python自动化生成PPT图文教程(基于python-pptx)
  • 九江网站设计服务机构哪家好/创建网站的流程
  • 怎么做刷网站流量生意/网站seo在线诊断