Java桌面应用程序设计:SWT简介

Java桌面应用程序设计:SWT简介,第1张

Java语言的声望和它在桌面应用程序(GUI程序)所取得的成就显然极不相符 至今仍然很少能看到非常成功Java桌面程序 虽然有JBuilder Netbean JProbe等大型软件作为代表 但这仍不能证明Java的GUI程序是成功的 它们的外观总是和同一 *** 作系统平台下的其它软件显得格格不入 对机器配置的需求也似乎永无止境 这使得它们只能被一些总是拥有当前最高性能PC的程序员们所容忍 或是那些不在乎金钱和时间的专业用户所接受 对绝大多数计算机使用者来说 AWT或SWING代表着怪异的界面和无法接受的速度 Standard Widget Toolkit(SWT)或许是Java这一噩梦的终结者 广大Java程序员终于可以开发出高效率的GUI程序 它们拥有标准的外观 几乎没有人能看出你的程序是用Java写出来的 更为重要的是 这些程序是跨平台的

SWT本身仅仅是Eclipse组织为了开发Eclipse IDE环境所编写的一组底层图形界面 API 或许是无心插柳 或是有意为之 至今为止 SWT无论是在性能和外观上 都超越了SUN公司提供的AWT和SWING 目前Eclipse IDE已经开发到了 版本 SWT已经十分稳定 这里指的稳定应该包含两层意思

一是指性能上的稳定 其中的关键是源于SWT的设计理念 SWT最大化了 *** 作系统的图形构件API 就是说只要 *** 作系统提供了相应图形的构件 那么SWT只是简单应用JNI技术调用它们 只有那些 *** 作系统中不提供的构件 SWT才自己去做一个模拟的实现 可以看出SWT的性能上的稳定大多时候取决于相应 *** 作系统图形构件的稳定性

另一个稳定是指SWT API包中的类 方法的名称和结构已经少有改变 程序员不用担心由于Eclipse组织开发进度很快(Eclipse IDE每天都会有一个Nightly版本的发布) 而导致自己的程序代码变化过大 从一个版本的SWT更新至另一版本 通常只需要简单将SWT包换掉就可以了

第一个SWT程序

下面让我们开始一个SWT程序 (注意 以下的例子和说明主要针对Windows平台 其它的 *** 作系统应该大同小异) 首先要在Eclipse安装文件中找到SWT包 Eclipse组织并不提供单独的SWT包下载 必须下载完整的Eclipse开发环境才能得到SWT包 SWT是作为Eclipse开发环境的一个插件形式存在 可以在${你的eclipse安装路径}\plugins路径下的众多子目录下去搜索SWT JAR文件 在找到的JAR文件中包含了SWT全部的Java类文件 因为SWT应用了JNI技术 因此同时也要找到相对应的JNI本地化库文件 由于版本和 *** 作平台的不同 本地化库文件的名称会有些差别 比如SWT WIN DLL是Window平台下Eclipse Build 的动态库 而在Unix平台相应版本的库文件的扩展名应该是 so 等等 注意的是 Eclipse是一个开放源代码的项目 因此你也可以在这些目录中找到SWT的源代码 相信这会对开发很有帮助 下面是一段打开空窗口的代码(只有main方法)

import e one examplepublic class OpenShell{ public static void main(String [] args) {Display display = new Display()Shell shell = new Shell(display)shell open()// 开始事件处理循环 直到用户关闭窗口while (!shell isDisposed()) { if (!display readAndDispatch())display sleep()}display dispose() }}

确信在CLASSPATH中包括了SWT JAR文件 先用Javac编译例子程序 编译无错后可运行java Djava library path=${你的SWT本地库文件所在路径} e one example OpenShell 比如SWT WIN DLL件所在的路径是C:\swtlib 运行的命令应该是java Djava library path=c:\swtlib e one example OpenShell 成功运行后 系统会打开了一个空的窗口

剖析SWT API

下面再让我们进一步分析SWT API的组成 所有的SWT类都用 eclipse swt做为包的前缀 下面为了简化说明 我们用*号代表前缀 eclipse swt 比如* widgets包 代表的是 eclipse swt widgets包

我们最常用的图形构件基本都被包括在* widgets包中 比如Button Combo Text Label Sash Table等等 其中两个最重要的构件当数Shell和Composite Shell相当于应用程序的主窗口框架 上面的例子代码中就是应用Shell构件打开一个空窗口 Composite相当于SWING中的Panel对象 充当着构件容器的角色 当我们想在一个窗口中加入一些构件时 最好到使用Composite作为其它构件的容器 然后再去* layout包找出一种合适的布局方式 SWT对构件的布局也采用了SWING或AWT中Layout和Layout Data结合的方式 在* layout包中可以找到四种Layout和与它们相对应的布局结构对象(Layout Data) 在* custom包中 包含了对一些基本图形构件的扩展 比如其中的CLabel 就是对标准Label构件的扩展 上面可以同时加入文字和图片 也可以加边框 StyledText是Text构件的扩展 它提供了丰富的文本功能 比如对某段文字的背景色 前景色或字体的设置 在* custom包中也可找到一个新的StackLayout布局方式

SWT对用户 *** 作的响应 比如鼠标或键盘事件 也是采用了AWT和SWING中的Observer模式 在* event包中可以找到事件监听的Listener接口和相应的事件对象 例如常用的鼠标事件监听接口MouseListener MouseMoveListener和MouseTrackListener 及对应的事件对象MouseEvent

* graphics包中可以找到针对图片 光标 字体或绘图的API 比如可通过Image类调用系统中不同类型的图片文件 通过GC类实现对图片 构件或显示器的绘图功能

对不同平台 Eclipse还开发了一些富有针对性的API 例如 在Windows平台 可以通过* ole win 包很容易的调用ole控件 这使Java程序内嵌IE浏览器或Word Excel等程序成为可能!

更复杂的程序

下面让我们展示一个比上面例子更加复杂一些的程序 这个程序拥有一个文本框和一个按键 当用户点击按键的时候 文本框显示一句欢迎信息

为了文本框和按键有比较合理的大小和布局 这里采用了GridLayout布局方式 这种布局是SWT中最常用也是最强大的布局方式 几乎所有的格式都可能通过GridLayout去达到 下面的程序也涉及到了如何应用系统资源(Color) 以及如何释放系统资源

private void initShell(Shell shell) { //为Shell设置布局对象 GridLayout gShellLay = new GridLayout() shell setLayout(gShellLay) //构造一个Composite构件作为文本框和按键的容器 Composite panel = new Composite(shell SWT NONE) //为Panel指定一个布局结构对象 这里让Panel尽可能的占满Shell 也就是全部应用程序窗口的空间  GridData gPanelData = new GridData(GridData GRAB_HORIZONTAL| GridData GRAB_VERTICAL|GridData FILL_BOTH) panel setLayoutData(gPanelData) //为Panel也设置一个布局对象 文本框和按键将按这个布局对象来显示  GridLayout gPanelLay = new GridLayout() panel setLayout(gPanelLay) //为Panel生成一个背景色 final Color bkColor = new Color(Display getCurrent() ) panel setBackground(bkColor) //生成文本框 final Text text = new Text(panel SWT MULTI|SWT WRAP) //为文本框指定一个布局结构对象 这里让文本框尽可能的占满Panel的空间  GridData gTextData = new GridData (GridData GRAB_HORIZONTAL| GridData GRAB_VERTICAL|GridData FILL_BOTH) text setLayoutData(gTextData) //生成按键 Button butt = new Button(panel SWT PUSH) butt setText( Push ) //为按键指定鼠标事件 butt addMouseListener(new MouseAdapter(){public void mouseDown(MouseEvent e){ //当用户点击按键的时候 显示信息 text setText( Hello SWT )} } //当主窗口关闭时 会触发DisposeListener 这里用来释放Panel的背景色  shell addDisposeListener(new DisposeListener(){public void widgetDisposed(DisposeEvent e) { bkColor dispose()} }}

把这段代码中的方法initShell()加入到第一个打开空窗口的例子中 得到的是一段能成功运行的完整GUI应用程序 运行方法可参考第一个例子

系统资源的管理

在一个图形化的 *** 作系统中开发程序 都要调用系统中的资源 如图片 字体 颜色等 通常这些资源都是有限的 程序员务必非常小心的使用这些资源 当不再使用它们时 就请尽快释放 不然 *** 作系统迟早会油尽灯枯 不得不重新启动 更严重的会导致系统崩溃 SWT是用Java开发的 Java语言本身的一大优势就是JVM的 垃圾回收机制 程序员通常不用理会变量的释放 内存的回收等问题 那么对SWT而言 系统资源的 *** 作是不是也是如此?答案是一个坏消息 一个好消息

坏消息是SWT并没采用JVM的垃圾回收机制去处理 *** 作系统的资源回收问题 一个关键的因素是因为JVM的垃圾回收机制是不可控的 也就是说程序员不能知道 也不可能做到在某一时刻让JVM回收资源!这对系统资源的处理是致命的 试想你的程序希望在一个循环语句中去查看数万张图片 常规的处理方式是每次调入一张 查看 然后就立即释放该图片资源 而后在循环调入下一张图片 这对 *** 作系统而言 任何时刻程序占用的仅仅是一张图片的资源 但如果这个过程完全交给JVM去处理 也许会是在循环语句结束后 JVM才会去释放图片资源 其结果可能是你的程序还没有运行结束 *** 作系统已经宕掉

但下面的好消息也许会让这个坏消息变得无关紧要 对于SWT 只需了解两条简单的 黄金 法则就可以放心的使用系统资源!之所以称为黄金法则 一是因为少 只有两条 二是因为它们出奇的简单 第一条是 谁占用 谁释放 第二条是 父构件被销毁 子构件也同时被销毁 第一条原则是一个无任何例外的原则 只要程序调用了系统资源类的构造函数 程序就应该关心在某一时刻要释放这个系统资源 比如调用了

Font font = new Font (display Courier SWT NORMAL)

那么就应该在不在需要这个Font的时候调用

font dispose()

对于第二个原则 是指如果程序调用某一构件的dispose()方法 那么所有这个构件的子构件也会被自动调用dispose()方法而销毁 通常这里指的子构件与父构件的关系是在调用构件的构造函数时形成的 比如

Shell shell = new Shell()Composite parent = new Composite(shell SWT NULL)Composite child = new Composite(parent SWT NULL)

其中parent的父构件是shell 而shell则是程序的主窗口 所以没有相应的父构件 同时parent又包括了child子构件 如果调用shell dispose()方法 应用第二条法则 那么parent和child构件的dispose()方法也会被SWT API自动调用 它们也随之销毁

   线程问题

在任何 *** 作平台的GUI系统中 对构件或一些图形API的访问 *** 作都要被严格同步并串行化 例如 在一个图形界面中的按键构件可被设成可用状态(enable)或禁用状态(disable) 正常的处理方式是 用户对按键状态设置 *** 作都要被放入到GUI系统的事件处理队列中(这意味着访问 *** 作被串行化) 然后依次处理(这意味着访问 *** 作被同步) 想象当按键可用状态的设置函数还没有执行结束的时候 程序就希望再设置该按键为禁用状态 势必会引起冲突 实际上 这种 *** 作在任何GUI系统都会触发异常

Java语言本身就提供了多线程机制 这种机制对GUI编程来说是不利的 它不能保证图形构件 *** 作的同步与串行化 SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要求 在SWT中 通常存在一个被称为 用户线程 的唯一线程 只有在这个线程中才能调用对构件或某些图形API的访问 *** 作 如果在非用户线程中程序直接调用这些访问 *** 作 那么SWTExcepiton异常会被抛出 但是SWT也在* widget Display类中提供了两个方法可以间接的在非用户线程的进行图形构件的访问 *** 作 这是通过syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现 例如

//此时程序运行在一个非用户线程中 并且希望在构件panel上加入一个按键

Display getCurrent() asyncExec(new Runnable() { public void run() {Button butt = new Button(panel SWT PUSH)butt setText( Push ) }})

方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回 而后者则无论指定的线程是否执行都会立即返回到当前线程

SWT的扩展 JFace

JFace与SWT的关系好比Microsoft的MFC与SDK的关系 JFace是基于SWT开发 其API比SWT更加易于使用 但功能却没SWT来的直接 比如下面的代码应用JFace中的MessageDialog打开一个警告对话框

MessageDialog openWarning(parent Warning Warning message )

如果只用SWT完成以上功能 语句不会少于 行!

JFace原本是为更加方便的使用SWT而编写的一组API 其主要目的是为了开发Eclipse IDE环境 而不是为了应用到其它的独立应用程序 因此在Eclipse 版本之前 很难将JFace API完整的从Eclipse的内核API中剥离出来 总是要多多少少导入一些非JFace以外的Eclipse核心代码类或接口才能得到一个没有任何编译错误的JFace开发包 但目前Eclipse组织似乎已经逐渐意识到了JFace在开发独立应用程序起到的重要作用 在开发的 版本中 JFace也开始变成了和SWT一样的完整独立的开发包 只是这个开发包还在变动中(笔者写本文时 应用的Eclipse M 版本) JFace开发包的包前缀是以 eclipse jface开头 JAR包的源代码也和SWT一样 也在${你的eclipse安装路径}\plugins路径下去找

对开发人员来说 在开发一个图形构件的时候 比较好的方式是先到JFace包去找一找 看是不是有更简洁的实现方法 如果没有再用SWT包去自己实现 比如JFace为对话框提供了很好的支持 除了各种类型的对话框(比如上面用的MessageDialog 或是带有Title栏的对话框) 如要实现一个自定义的对话框也最好从JFace中的Dialog类继承 而不是从SWT中的* widget Dialog继承

应用JFace中的Preference包中的类很容易为自己的软件做出一个很专业的配置对话框 对于Tree Table等图形构件 它们在显示的同时也要和数据关联 例如Table中显示的数据 在JFace 中的View包中为此类构件提供了Model View方式的编程方法 这种方法使显示与数据分开 更加利于开发与维护 JFace中提供最多的功能就是对文本内容的处理 可以在 eclipse jface text * 包中找到数十个与文本处理相关类

与应用程序更近一步

Java程序通常是以class文件的方式发布的 运行class需要JRE或JDK的支持 这又是Java GUI程序的另一个致命的弱点 想象对一个面向广大用户的应用程序来说 无论你的程序功能有多简单 或是你的代码十分的精简 你都不得不让用户去下载一个 M的JRE 那是多么令人沮丧的一件事 而且对程序员来说 Class通常意味着源代码的暴露 反编译的工具让那些居心叵测的人轻易得到你的源代码 虽然有很多对Class的加密方法 但那总是以牺牲性能为代价的 好在我们还有其它的方式可用 把Class编译成exe文件!

lishixinzhi/Article/program/Java/gj/201311/27737

Java对于服务器 个人电脑和移动设备来说是一项伟大的技术 由于需要java的跨平台的特性 因此java在服务器和移动设备方面的应用是非常成功的 但java在个人电脑应用方面的情况和在服务器及移动设备方面的应用有所不同 但是这很快就会有所改变 至少比你想象得要快 在这篇文章中 我会分析一下java在桌面环境中的应用将怎样得到提升 然后具体说一下java GUI(用户图形接口)的三个主要的工具:AWT Swing 和SWT 在下文中 我将会开发一个完整的java桌面应用程序 Java与桌面端 现在 流行的桌面平台要数Windows Mac and Linux了 但它们不是十全十美的 Windows主宰著桌面 *** 作系统的市场 其上有巨大的应用和开发群体 但它昂贵且有许多安全漏洞 Linux有着稳固的基础 它是开源的软件 比Windows更可靠 Macs非常容易 *** 作且不是黑客的目标 但与Windows和Linux比起来 Mac的硬件和软件可选的余地非常的有限 公司和个人选择他们的 *** 作系统基于许多因素 花费少且安全性高是首选的因素 这导致一些组织从Windows 系统转而选择Linux 对许多用户来说 可用性及对原有应用程序的支持是非常重要的因素 这意味着Windows 将继续享有巨大的市场 Mac有其自己忠诚的用户 这使得苹果机仍然可以存活 Linux 在桌面的流行及Mac的成功创造了多样性 这种多样性正是Java需要的 这种多样性使得Java在桌面有举足轻重的地位 跨平台的支持 Java 运行于所有相关的 *** 作系统 包括Windows Mac和Linux 对于任何组织 他想把现有的应用从一个 *** 作系统移植到另一个 *** 作系统而不用做太多的改动 那么Java正是他们首选的桌面开发平台 或许用微软的可视化工具很容易构建 NET应用 但是这将使你被绑定在了Windows平台上了 很多人也许想立刻用Linux 代替Windows 从而避免由微软件 *** 作系统的漏洞带来的问题 用户不能容忍的问题之一是当从Windows移植到Linux带来的巨大的费用 如果你的应用程序用Java构建 你就没有了这些问题 Java的图形用户界面看上去会跟你用的 *** 作系统一样 而并不需要做什么改动 假如有一天又有一种桌面 *** 作系统出现的话 java 是个安全的赌注 因为它现在能够运行在Windows和Linux 上 那么可以推测它也可以运行在将来可能出现的 *** 作系统上 这些 *** 作系统可能或迟或早地由微软 或是开源社区 或是其它的人开发出来 丰富的特征 最初 Java只有非常有限的一些特征去构建图形用户界面 思想就是用平台无关的Java应用程序接口打包不同的 *** 作系统的本地图形用户界面 称之为抽象的窗口工具 仅有普通的部件如文件域 文本区 选择框 单选按钮 列表框和按钮被AWT支持 图形和图像的特性支持非常有限 也就是说 只足够构建简单的applet程序 认识到需要更高级的图形用户界面组件和图形能力 Sun公司开发了Swing Java D Java D 图像的输入/输出 Java高级图像(JAI)和很多其它的 这些中的一些窗体组件现在已经是Java 标准版(J SE)里的一部分 并且其它的一些扩展必须和你的应用程序打包在一起 例如Swing Java D 图像的输入/输出都是Java的核心API 随着Java开发工具包(JDK)和Java运行环境一起提供 让我们不要忘了J EE平台 如果你开发服务器端的应用程序并且需要丰富的图形用户界面 那么你毫无疑问应该选择Java 这允许你把服务器端的一些代码移到客户端 反之亦然 例如 一个项目可能开始是基于WEB和图形界面 在某些时候 用户可能要求图形元素不能在HTML中实现 如果你选择java做客户端应用 那么你可以重用这些当初用来做服务器端的代码 如果你用远程调用 一些类会真正地实现服务器和客户端的共享 通过页面服务器 Java桌面应用也能够和其它的Java 或非Java应用程序通信 如CORBA TCP/IP 或是 HTTP Java图形界面工具 Java有三个主要的图形界面工具 AWT Swing和SWT Swing 是构建java图形界面标准的API(应用程序接口) 一些AWT类由Swing基础而来 SWT是一个非常有前途的新的窗体工具 由IBM资助 但是事实上 这三者相互补充 他们满足不同的需求 AWT 抽象窗口工具集为简单的applet程序设计 它不适宜用来构建丰富的桌面图形界面 但是从开始被介绍 它至少有一个好的思想就是布局管理 它负责为组件找到一个放置的位置 这种机制是必需的 因为GUI组件在不同的 *** 作系统中有不同的尺寸 现在 AWT扩展了组件模型和事件处理机制(由JavaBeans说明定义) 新的图形API(称为Java D) 支持剪贴板和拖拉 *** 作 打印 准入 和新的GUI工具Swing 所有这些都归到Java基础类中(JFC) Swing Swing是曾经开发的最复杂的GUI之一 它有一套完全的组件从按钮到文件域到表格 树型和文件编辑器 这些组件不依赖于 *** 作系统本地的部件 而是用原始的图形像直线 矩形 文字画出 这种画代表感观插件 它能够模仿本地的感观 Swing也有平台无关的外观称为 Metal Swing的结构由MVC模式得到启发 这里在屏幕上的视觉GUI组件和支持数据的模型对象之间有一个明显的分隔 在GUI和数据层之间的通讯基于事件 在最初的Swing版本中有许多错误并且有执行问题 这减慢了接受它的速度 Swing最大的问题是被从事于并且许多人相信它是为开发桌面应用而准备的 今天 有许多基于Swing开发的商业产品 包括大多数的Java集成开发工具 我所喜欢的集成开发工具是Jbuilder 它的速度相当的快 SWT SWT是IBM为它的Eclipse集成开发环境而开发的图形用户界面工具 SWT可以在Eclipse环境外使用 而且提供对 *** 作系统本地图形用户界面的直接访问 因此 基于SWT的Java应用程序拥有本地的图形用户界面并且可以和本地别的应用程序和部件集成在一起 假如你的桌面应用程序产生HTML报表 你想把它显示给用户看 你可以使用Swing去浏览简单的HTML文档 但这不是一个理想的的解决方案 最好是在你的应用程序里提供IE或者Mozilla浏览器引擎 SWT社区现在正在设计浏览器API 这些API可以让你产生基于IE或者Mozilla的HTML窗口 SWT现在可以在AIX HPUX Linux QNX Solaris and Windows下面运行 Mac OS X is 也在进行之中 误解与Bug 对于java/Swing一直有着误解 诸如 Java/Swing太慢了 或者是Java/Swing需要更多的内存 Swing也许在老式的奔腾的cpu而且只有 m内存运行JDK 运行起来却是很慢 但是如果在PIII级别的CPU有着 mb的内存 运行JDK 环境是足够快的 对于一个应用程序来说鼠标在 毫秒和在 毫秒的反映的区别 对于使用者来说看起来是 没什么区别的 Java在企业级的数百人 上千人同时在线的服务器表现的很好 Java在对于有限资源的移动设备上的表现也是很出色的 那为什么Java不能成为很好的桌面应用程序呢?以我的观点看 Swing的bug比其运行速度慢这问题还严重 例如 如果你用的是JDK 你将不能在表格(称为JTable)中输入%&($#!q 等这些字符 这八个字符和箭头键及Home End Pgup and Pgdn这几个键的键值是相同的 其中一个由JTable由到的类调用了KeyEvent getCharCode()方法代替KeyEvent getKeyCode() 这个bug这JDK 已经得到了纠正 你大概已经放弃过Swing 如果你是从用JDK 的Swing 你可能因为你不能在表格里输入q而恼怒 可能不幸的是你正需要用Jtable开发一个Swing应用 你将花费许多时间从sun的bug数据库中查找解决的办法 但没有发现你需要的(记住在那时Swing还是个新事物) 你将花费更多的时间去看Swing的源代码和发展中的工作区 经过了这个的经历之后 很少有人很在另一个项目里再用Swing了 你的工作区会像下面这样子 import java awt *import java awt event *import javax swing *import javax swing table *public class WorkingTable extends JTable { public static final boolean JDK = System getProperty( java version ) startsWith( )public void processKeyEvent(KeyEvent e) { if (JDK ) { char ch = e getKeyChar()if (e getID() == KeyEvent KEY_TYPED &&(( <= ch &&ch <= 40) || ch == 'q')) { int anchorRow = getSelectionModel().getAnchorSelectionIndex()int anchorColumn = getColumnModel() .getSelectionModel().getAnchorSelectionIndex()if (anchorRow != -1 &&anchorColumn != -1) { if (!isEditing()) editCellAt(anchorRow, anchorColumn)Component editorComp = getEditorComponent()if (isEditing() &&editorComp instanceof JTextField) { JTextField textField = (JTextField) editorComptextField.setText(textField.getText() + ch)return} } } } super.processKeyEvent(e)} } 不幸的是,Swing有许多像上面描述的那样的问题,一些问题很难解决,需要做大量的工作。Tw.WInGwiT.例如,Swing的打开文件和保存文件的对话框是基于称为JfileChooser的组件,它部分的执行了JDK 1.2和JDK 1.3(一些特性总是不能用的,要创建一个新的目录对大多数用户来是一个挑战)。我不知道为什么Sun需要几年的时间直到jdk1.4中才完成JfileChooser。在JDK 1.4之前,你有两种选择:用这种破烂的JfileChooser或是创建你自己的文件选择框,Borland公司在他们的JBuilder 4中做一个很好的文件打开对话框。然而,大多数的开发者用的是标准的JfileChooser,给他们的用户带来许多问题。有一件重要的事情需要注意:可以像上面描述的那个去创建工作环境,因为Swing的源代码是可以得到的。学习java源代码也能够让你成为更好的程序员并且让你理解工作在Java API的内部机制。当你开发你自己习惯的GUI组件,这点是有用 lishixinzhi/Article/program/Java/hx/201311/26851


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7900536.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-11
下一篇 2023-04-11

发表评论

登录后才能评论

评论列表(0条)

保存