Netty + JavaFx 实战:仿桌面版微信聊天

专栏亮点

  • 以 DDD 领域驱动设计的分层模型结合 Netty,编写清晰简洁可扩展的框架结构,完成仿微信聊天核心功能项目开发。
  • 使用 JavaFX 做 UI 窗体并且与业务代码分离的方式实现桌面版程序功能。在事件驱动与接口调用的方式中,使代码更加清晰、更加易于扩展。
  • 清晰的设计,明确到位的落地,让理论与实践结合起来。快速上手掌握 Netty、JavaFx、架构、库表的设计与编码。

Netty 是互联网各种框架中以及物联网里都不可缺少的一部分技术栈技能,同时它也是非常重要的一个 Socket 框架。Netty 的应用非常广泛,无论在互联网、大数据以及通信和游戏行业中,都有 Netty 的身影。比如一线大厂阿里的 RPC 框架,Dubbo 协议默认使用 Netty 作为基础通信组件,用于各节点间的内部通信。

淘宝的消息中间件 RocketMQ 的消息生产者与消费者,也是采用 Netty 作为高性能、异步通信组件。除了阿里系、淘宝系,其他很多一线大厂也都在使用 Netty 构建高性能、分布式的网络服务。

rurCDM

前段时间写完 Netty 系列教程,看似系统,但是回顾起来还是零散,看完后一些读者反馈无法真实的上手业务,让我能否写一写关于 Netty 实际业务中的应用。那么为了更好的让自己和大家都能拿一个熟知又有趣的场景,最好的就是模仿 PC 端的微信聊天。之后这样不仅学习到了 Netty,还让 Java 程序员使用自己的语言技术栈开发了一款桌面聊天程序。

5yG6Gt

以上专栏内容请查看本专栏第一章节:专栏学习简述以及全套源码获取

说回来往往一个新知识点的学习到上手分为三个阶段;运行 HelloWord、熟练使用 API、和最终的落地应用。而这最后一步也是最重要的一步,只要将各个知识点与实际要实现的业务功能相结合,才能不断的提升自己的技能。

对一个技能最好的掌握方式就是使用自己熟悉的工具进行实践落地,通过一点点功能的实现和阅读的一堆堆的逻辑中,建设自己对代码的认知提升,对整个框架的深化理解。

为此我开始使用 Netty+JavaFx 以及 SpringBoot 等技术栈,开始搭建仿桌面版微信聊天程序,在这个过程当中显示梳理分析功能结构。这个过程可以使用 xmind 作为你的工具,整理自己的思路。接下来开始对我需要使用的技术栈做案例测试,验证核心功能是否可以满足我的需求。验证完成后开始做架构设计以及业务流程,直到最终的编码实现功能。

专栏介绍

本专栏是作者小傅哥使用 JavaFxNetty4.xSpringBootMysql 等技术栈和偏向于 DDD 领域驱动设计方式,搭建的仿桌面版微信聊天工程实现通信核心功能。

本专栏会以三个大章节内容,逐步进行讲解:

第一部分 - UI 开发:使用 JavaFxMaven 搭建 UI 桌面工程,逐步讲解登录框体、聊天框体、对话框、好友栏等各项 UI 展示及操作事件。从而在这一章节中让 Java 程序员学会开发桌面版应用;

第二部分 - 架构设计:在这一章节中我们会使用 DDD 领域驱动设计的四层模型结构与 Netty 结合使用,架构出合理的分层框架。同时还有相应库表功能的设计。相信这些内容学习后,你一定也可以假设出更好的框架;

第三部分 - 功能实现:这部分我们主要将通信中的各项功能逐步实现,包括;登录、添加好友、对话通知、消息发送、断线重连等各项功能。最终完成整个项目的开发,同时也可以让你从实践中学会技能。

界面展示

功能演示图

登陆页面

登陆页面

聊天页面

聊天页面

添加好友

添加好友

消息提醒

消息提醒

多图解析

ivfXq0

可以获得什么?

  • 一整套可扩展的工程源代码,包括, JavaFx 开发的 UI 工程、Netty 客户端工程、DDD 结构模型的服务端工程以及专栏设计到的案例工程;
  • 学会系统的架构设计能力,了解 DDD 领域驱动设计的优点,可以使用 DDD 的四层架构来在平时的业务开发中进行实际落地;
  • Java 程序员掌握开发桌面版程序的能力,学会使用 JavaFx 开发出漂亮优秀的界面。学习这些内容;可以面试通关、满足工作需要、适合承接私活。

作者介绍

小傅哥,一线互联网后端工程师,CSDN 博客专家,精通 Java、Netty、Spring、SpringBoot 等技术栈,以及擅长中间件开发。

Na1TJZ

适宜人群

  • Netty 爱好者;
  • 处在刚接触 Netty 并希望以后从事此类技术工作的程序员;
  • 需要使用 Netty 开发桌面版网络通信;
  • 提升架构设计能力,写出干净整洁有章可循的代码。

购买须知

  • 本专栏为图文内容,共计 25 篇。每周更新 3 篇,预计 3 月底更新完毕;
  • 付费用户可享受文章永久阅读权限;
  • 本专栏为虚拟产品,一经付费概不退款,敬请谅解;
  • 本专栏可在 GitChat 服务号、App 及网页端 gitbook.cn 上购买,一端购买,多端阅读。

订阅福利

  • 订购本专栏可获得专属海报(在 GitChat 服务号领取),分享专属海报每成功邀请一位好友购买,即可获得 25% 的返现奖励,多邀多得,上不封顶,立即提现。

  • 提现流程:在 GitChat 服务号中点击「我 - 我的邀请 - 提现」。

  • 购买本专栏后,服务号会自动弹出入群二维码和暗号。如果你没有收到那就先关注微信服务号「GitChat」,或者加我们的小助手「GitChatty6」咨询。(入群方式可查看 第 1 篇 文末说明)。

课程内容

登陆框体实现:结构定义、输入框和登陆

一、前言

从本章节开始我们会陆续实现各个框体的 UI 开发,内容会包括;框体拆解、工程结构、代码开发,以及最后编写事件和接口。

在 JavaFx 中,一个框体包含;窗口 (Stage)、场景(Scene)、布局(Pane)、控件(Button 等) 这四方面内容。而开发过程中可以使用 xml 和编码两种方式进行处理,一般一些预定好的会使用 xml 结构,如果是随着我们业务行为触达而产生的会开发到代码中来生成。

那么接下来我们的目标是开发一个登陆框体,样式如下;

二、登陆窗体分析

按照我们的 UI 开发诉求,将整个页面进行拆解,以方便清楚知道我们的各种类型元素放置位置;

序号模块宽 * 高描述
1整体框体540 * 415一个整体的 4px 的圆角面板, 去掉默认的标题和工具栏
2背景图片540 * 158设置的一个背景图
3最小化、退出43 * 32两个同样大小的 Button
4用户 ID 输入框250 * 45明文输入框
5用户密码输入框250 * 45密文输入框
6登录按钮250 * 45登陆按钮 Button,鼠标进入时变换背景色,点击触发登陆
7版本展示400 * 25透明的无背景可以调整,一般展示版本编号如;v1.0
8头像100 * 100圆角头像图片,整个可以使用 Image 等元素开发
9标头200 * 15展示名称,例如;憨憨·语约
  • 以上就是我们整体窗体的一个拆解后的示意图,接下来开始按照整个示意图进行开发。
  • 如果是个人开发的新项目,一般 UI 的设计可以先在草稿设计,最后在使用工具进行具体设计。如果是公司级别会有专门的设计来出所有的图稿。
  • 关于设计中使用的元素可以从工程源码中获取,可以自己从矢量图仓库中寻找自己喜欢的;https://www.iconfont.cn

三、工程结构

itstack-naive-chat-ui-02└── src    ├── main    │   ├── java    │   │   └── org.itstack.navice.chat.ui    │   │       ├── view    │   │       │  └── Login.java    │   │       └── Application.java    │   └── resources    │       └── fxml.login    │           ├── css    │           │   └── login.css    │           ├── img    │           │   ├── close_0.png    │           │   ├── close_1.png    │           │   ├── head_default_100.png    │           │   ├── logo.png    │           │   ├── min_0.png    │           │   ├── min_1.png    │           │   └── show.png    │           └── login.fxml    └── test        └── java            └── org.itstack.test                └── ApiTest.java
  • 工程结构上我们先从简单规划;启动层、展示层、资源配置层,三方面。后面在随着开发内容的增多会不断的优化结构
  • fxml 是 JavaFx 开发的资源文件,可以设置界面展示,同时 xml 配置里可以引入 css 文件、设置元素大小等

四、代码讲解

1. login.fxml 配置

在 maven 管理下我们将配置文件放到资源文件夹下;fxml/login/login.fxml

整体外框 xml

<?import javafx.geometry.Insets?><?import javafx.scene.control.*?><?import javafx.scene.layout.Pane?><?import javafx.scene.text.Font?><Pane id="login" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"      prefWidth="540" prefHeight="415" stylesheets="@css/login.css" xmlns="http://javafx.com/javafx/8.0.121"      xmlns:fx="http://javafx.com/fxml/1">    <children>    </children></Pane>
  • 这是一个整体的面板,初步设定了面板的宽高、引入的 css 样式等
  • 接下来在 children 孩子元素集合里添加各种窗体元素

操作栏;最小化、关闭(Pane)

<Pane id="operation" prefWidth="540" prefHeight="158">    <children>        <Label id="login_logo" styleClass="logo" layoutX="10" layoutY="5" prefWidth="200" prefHeight="15" text="憨憨 · 语约" style="-fx-text-fill:#666666;"/>        <Button id="login_min" styleClass="min" layoutX="454" prefWidth="43" prefHeight="32"/>        <Button id="login_close" styleClass="close" layoutX="497" prefWidth="43" prefHeight="32"/>    </children></Pane>
  • 定义一个 Pane,并设置宽高,之后在整个面板里中添加 logo、最小化、关闭操作
  • 后面你会看到更多的 <children>,来装载元素
  • 在孩子元素中有三个元素;
  • login_logo 定义 logo 和文案,这里默认显示文字“憨憨 · 语约”
  • login_min 最小化按钮处理
  • login_close 关闭操作处理

头像(Pane)

<Pane id="headImg" layoutX="80" layoutY="200" prefWidth="100" prefHeight="100"/>
  • 这个是定义一个头像区域,也可以使用其他元素进行定义

用户 ID 输入框(TextField)

<TextField id="userId" layoutX="200" layoutY="200" prefWidth="250" prefHeight="45" promptText="账号">    <padding>        <Insets left="10"/>    </padding></TextField>
  • TextField 是一个单行明文内容输入区域,在这里不仅设置了宽高,还设置了相对位置;layoutX、layoutY
  • padding.Insets,如果你写过 CSS,可能会知道。这是一个设置输入框内,文字输入区域距离左面的空出位置。一般空出的位置可以设置一个背景图片
  • 同时我们设置了提示文字,这个在这里比较简单直接使用;promptText 即可。例如:promptText="账号"

密码输入框(PasswordField)

<PasswordField id="userPassword" layoutX="200" layoutY="255" prefWidth="250" prefHeight="45" promptText="密码">    <padding>        <Insets left="10"/>    </padding></PasswordField>
  • PasswordField 是一个单行密码内容输入区域,同样设置了宽高,以及提示文字
  • 基本的使用方式与 TextField 一致,当然在你后面后去他里面内容的时候,是明文的

登陆按钮(Button)

<Button id="login_button" styleClass="login_button" layoutX="200" layoutY="345" prefWidth="250" prefHeight="45" text="登 陆"/>
  • Button 按钮类的操作都可以使用,在这里设置好宽高,以及文字内容 text="登 陆"
  • 同样在这里我们也设置了相对的展示位置,这个位置是相对的,相遇对当前父元素

版本(Label)

<Label id="slogan" layoutX="5" layoutY="398" prefWidth="400" prefHeight="15"       text="v1.0 小傅哥 | https://bugstack.cn">    <font>        <Font size="12"/>    </font></Label>
  • Label 一般可以设置文字、图片等展示内容。是一个轻量级元素
  • 在这里我们设置了版本编号,和自己一些内容信息
  • 同时我们还设置了字体的大小,关于字体后面我们还会使用到 css(这里的 css 是 javafx 的 css,除名称不同外基本一致)

2. login.css 设计

#login{    -fx-background-radius: 4px;    -fx-border-width: 1px;    -fx-border-radius: 4px;    -fx-border-color: rgb(180,180,180);    -fx-background-color: white;}#operation{-fx-border-color: rgb(180,180,180);   -fx-border-width: 1px 1px 0 1px;   -fx-border-radius: 4px 4px 0 0;   -fx-background-image: url("/fxml/login/img/system/show.png");}.close,.close:pressed{    -fx-background-radius: 2px;    -fx-background-position: center center;    -fx-background-repeat: no-repeat;    -fx-background-size: 43px 34px;    -fx-background-color: transparent;    -fx-background-image: url("/fxml/login/img/system/close_0.png");    -fx-cursor: hand;    -fx-border-width: 0;}.close:hover{    -fx-background-color: #f45454;    -fx-background-image: url("/fxml/login/img/system/close_1.png");    -fx-border-width: 1px 1px 0 0;    -fx-border-color: rgb(180,180,180);    -fx-border-radius: 2px;}...
  • 从上面可以看到基本和我们认识的 css 代码一致,在这里我们就不全部展示这部分内容了
  • 一些基本的 javafx 中的 css 常用语法我们在第一篇中已经介绍,可以参考。很常用的包括;背景色、线条色、宽高、图片、圆角、悬停手势等。

3. 代码模块

Login.java & 登陆页面初始化

public class Login extends Stage {    private static final String RESOURCE_NAME = "/fxml/login/login.fxml";    private Parent root;    public Login() {        try {root = FXMLLoader.load(getClass().getResource(RESOURCE_NAME));        } catch (IOException e) {e.printStackTrace();        }        Scene scene = new Scene(root);        scene.setFill(Color.TRANSPARENT);        setScene(scene);        initStyle(StageStyle.TRANSPARENT);        setResizable(false);        this.getIcons().add(new Image("/fxml/login/img/system/logo.png"));    }}
  • 单个窗体的需要继承 Stage,也就是继承了窗口类,并需要在里面创建场景,才可以运行展示

  • 在这里我们加载配置元素 login.fxml,初始化窗体的基本信息

  • 在布局中我们设置了填充为透明色,以及初始化样式 StageStyle.TRANSPARENT

  • 最后我们设置了状态栏的图标样式,这里我们设置了模仿微信的样式,颜色略有差异

    this.getIcons().add(new Image("/fxml/login/img/system/logo.png"));

Application.java & 启动类

public class Application extends javafx.application.Application{    @Override    public void start(Stage primaryStage) throws Exception {Login login = new Login();        login.show();}    public static void main(String[] args) {launch(args);    }}
  • 这里的 Application 继承了 JavaFx 的 Application,并实现 start 启动

  • 在这里我们初始化登陆窗体,并通过 login.show() 调用窗体的展现

  • 上面这个结构是一个固定的模板代码,也是配置到 maven 中的启动类路径;

    <plugin>  <groupId>com.zenjava</groupId>  <artifactId>javafx-maven-plugin</artifactId>  <version>8.8.3</version>  <configuration>      <mainClass>org.itstack.navice.chat.ui.Application</mainClass>  </configuration></plugin>

五、效果演示

  1. 在类org.itstack.navice.chat.ui.Application,右键运行

  2. 不出意外效果如下;

六、总结

  • 在这一篇中我们显示拆解分析了整个窗体需要实现的功能,之后逐步使用 JavaFx 去把 UI 搭建处理。在这里使用到了代码、XML、CSS,三个技术的配合工作。
  • 通过上面的工程和代码可以初步的了解到一个简单的窗体的搭建和使用,并且设计了我们的工程结构,一个好的开始就从现在开始了
  • 同时如果你细心可能会发现我们的工程其实是在打一个 jar 包,将来共客户端使用,最后也就达到了 UI 与业务分离,这部分会随着后续章节逐步展现
登陆框体事件与接口

一、前言

  • 在上一章节中我们把登陆窗体开发完成了,并进行了效果演示。那么接下来我们就需要在这个窗体里面添加行为事件和接口,待完成内容如下;

    序号类型描述
    1事件鼠标拖拽窗体移动
    2事件最小化到快捷栏
    3事件退出当前窗体
    4事件使用用户 ID 和密码登陆
    5接口登陆成功,执行跳转操作
    6接口登陆失败,执行提示操作
  • 在桌面版程序开发中不同于 web。桌面版开发需要有界面的事件的发起,例如 Button 按钮点击,当接收外部条件变化后要有接口承载,例如登陆成功后的页面跳转。但是在 web 中大部分时候只需要一个 http 请求同步响应即可。

  • 另外也可能有一部分桌面开发程序中是类似同步请求和反馈的,那么在一个事件的发起后,就直接影响事件内容的变化,来改变窗体或者填充数据行为。

  • 以下的章节我们会先去非常直接简单的添加事件和接口,以更清晰的直观的了解这部分内容的开发。之后我们会进行一次小的 重构,以此来适应更好的拓展。

二、工程结构 (重构前)

itstack-naive-chat-ui-03└── src    ├── main    │   ├── java    │   │   └── org.itstack.navice.chat.ui    │   │       ├── view    │   │       │  └── Login.java    │   │       └── Application.java    │   └── resources    │       └── fxml.login    │           ├── css    │           ├── img    │           └── login.fxml    └── test        └── java            └── org.itstack.test                └── ApiTest.java
  • 在目前的工程结构下,直接在里面开发事件和接口。

三、事件内容讲解 (重构前)

接下来我们会在现有代码中,org.itstack.navice.chat.ui.view.Login.java,进行编写事件和接口。

1. 鼠标移动窗体事件

 private double xOffset; private double yOffset;private void move() {    root.setOnMousePressed(event -> {xOffset = getX() - event.getScreenX();        yOffset = getY()- event.getScreenY();        root.setCursor(Cursor.CLOSED_HAND);    });    root.setOnMouseDragged(event -> {setX(event.getScreenX() + xOffset);        setY(event.getScreenY() + yOffset);});    root.setOnMouseReleased(event -> {root.setCursor(Cursor.DEFAULT);    });}
  • 这里的 root 是我们整个登陆窗体的元素面板,可以通过它来设置一些行为设置或者还可以通过 ID 查找根里面还有的元素 root.lookup
  • 在我们所有使用的桌面程序里,都是可以通过鼠标拖拽来移动位置的。那么这里我们是定义三个鼠标事件,来实现窗体的移动,如下;
  • setOnMousePressed;鼠标按下事件,这个时候记录窗体位置
  • setOnMouseDragged;鼠标拖动事件,这个设置 setX、setY
  • setOnMouseReleased;鼠标释放事件,这个时候恢复默认鼠标样式,最终完成了整个窗口的拖拽过程

2. 最小化窗体事件

private void min() {Button login_min = $("login_min", Button.class);    login_min.setOnAction(event -> {System.out.println("最小化窗体");        setIconified(true);    });}
  • 这个事件比较简单,只需要设置按钮点击事件后执行;setIconified(true) 即可,如果有些快捷键操作,弹出窗体可以动态设置为 false

3. 退出窗体事件

private void quit() {Button login_close = $("login_close", Button.class);    login_close.setOnAction(event -> {System.out.println("退出窗体");        close();        System.exit(0);    });}
  • 最小化与退出是一对的事件,退出事件同样需要绑定按钮,并执行 close() 操作以退出窗体,并执行程序退出 System.exit(0)
  • 另外在 socket 通信的时候,退出还需要断开服务端连接,记录个人状态等操作。例如;某某地方登陆、某某地方退出、时间、设备等信息,后续在通信开发中我们会陆续完善部分功能

4. 登陆操作事件

private void login() {TextField userId = $("userId", TextField.class);    PasswordField userPassword = $("userPassword", PasswordField.class);    $("login_button", Button.class).setOnAction(event -> {System.out.println("登陆操作");        System.out.println("用户 ID:" + userId.getText());        System.out.println("用户密码:" + userPassword.getText());    });}
  • 登陆操作是我们在点击按钮时,获取输入框内的‘用户 ID’与‘用户密码’,发送给服务端进行验证。当然这里的真实业务场景下,不会直接明文传输,会进行非对称加密,并在服务端解析后做登陆验证。
  • 登陆验证完成后,开始收到异步消息。如果验证成功则可以进行页面跳转,否则进行提示。
  • 那么现在你可能会想到这部分怎么和通信交互,以及事件的触发,这部分内容我们会逐步讲解。

四、工程结构 (重构后)

itstack-naive-chat-ui-04└── src    ├── main    │   ├── java    │   │   └── org.itstack.navice.chat.ui    │   │       ├── view    │   │       │  └── login    │   │       │  │    ├── ILoginEvent.java    │   │       │  │    ├── ILoginMethod.java    │   │       │  │    ├── LoginController.java    │   │       │  │    ├── LoginEventDefine.java    │   │       │  │    ├── LoginInit.java    │   │       │  │    └── LoginView.java    │   │       │  └── UIObject.java    │   │       └── Application.java    │   └── resources    │       └── fxml.login    │           ├── css    │           ├── img    │           └── login.fxml    └── test        └── java            └── org.itstack.test                └── ApiTest.java
  • 从上面新的结构可以看到,原来我们仅是一个类被抽取出这么多类,而同时每一个都被赋予了不同的功能,以整洁我们的代码;

五、代码讲解 (重构后)

抽象后的类功能结构,如下;

序号描述
1ILoginEvent事件接口类,具体实现交给调用方。例如我们在点击登陆后将属于窗体的功能处理完毕后,实际的验证交给外部
2ILoginMethod方法接口类,在上面我们说过桌面程序的开发基本都是事件触达和等待回调,那么我们给外部提供接口主要用于类似登陆处理完毕后,来执行相应方法进行窗体切换或者数据填充
3LoginController窗体的控制管理类,也是一个窗体的管家;因为它会继承窗体的装载、实现接口方法、初始化界面、初始化事件定义
4LoginEventDefine窗体事件定义,例如将登陆、最小化、退出等在这里完成定义
5LoginInit窗体的初始化操作,可以创建一些待填充的元素
6LoginView窗体的展示,主要用于扩展一些随着用户操作新展示的元素,例如后续在聊天窗体新增的消息提醒等
7UIObjectUI 父类定义,这是一个抽象类,提供了基础的初始化内容和接口,以及定义抽象方法

1. 窗体事件接口

ILoginEvent.java & 窗体事件接口

public interface ILoginEvent {    /**     * 登陆验证     * @param userId        用户 ID     * @param userPassword  用户密码     */    void doLoginCheck(String userId, String userPassword);}
  • 事件方法里提供了登陆所需的参数,用户 ID、用户密码,如果是实际业务开发还会需要传递;IP 地址、设备信息、请求时间等信息,用于判断是否正常登陆

2. 窗体方法接口

ILoginMethod.java & 窗体方法接口

public interface ILoginMethod {    /**     * 打开登陆窗口     */    void doShow();    /**     * 登陆失败     */    void doLoginError();    /**     * 登陆成功;跳转聊天窗口 [关闭登陆窗口,打开新窗口]     */    void doLoginSuccess();}
  • 这里面定义登陆操作的基本方法,按需可以提供一些入参。
  • doShow(); 是调用展示登陆窗体的接口,因为在我们重构定义后,调用方并不能直接使用 show() 来展示页面
  • doLoginError(); 登陆失败的操作
  • doLoginSuccess(); 登陆成功的操作,主要包含关闭现有页面,打开新的页面。这里新的页面,也就是我们后续的聊天主窗体

3. 窗体的控制管理类

LoginController.java & 窗体的控制管理类

public class LoginController extends LoginInit implements ILoginMethod {    private LoginView loginView;    private LoginEventDefine loginEventDefine;    public LoginController(ILoginEvent loginEvent) {super(loginEvent);    }    @Override    public void initView() {loginView = new LoginView(this, loginEvent);    }    @Override    public void initEventDefine() {loginEventDefine = new LoginEventDefine(this, loginEvent, this);    }    @Override    public void doShow() {super.show();    }    @Override    public void doLoginError() {System.out.println("登陆失败,执行提示操作");    }    @Override    public void doLoginSuccess() {System.out.println("登陆成功,执行跳转操作");        // 关闭原窗口        close();}}
  • 首先可以看到这里继承了窗体的初始化,实现了窗体的接口定义。这样的方式可以更加方便外部的调用,同时内部的逻辑也会更加清晰。
  • initView(),初始化了窗体页面,如果随着后续的窗体内容的增加,这部分初始化的内容也会有所增加。
  • initEventDefine(),等窗体初始化完成后,我们就可以初始化我们的事件定义。
  • 接下来我们后面看到的代码就是关于接口的具体的实现,供外部调用的。

4. 窗体事件定义

LoginEventDefine.java & 窗体事件定义

public class LoginEventDefine {    private LoginInit loginInit;    private ILoginEvent loginEvent;    private ILoginMethod loginMethod;    public LoginEventDefine(LoginInit loginInit, ILoginEvent loginEvent, ILoginMethod loginMethod) {        this.loginInit = loginInit;        this.loginEvent = loginEvent;        this.loginMethod = loginMethod;        loginInit.move();        min();        quit();        doEventLogin();}    // 事件;最小化    private void min() {        loginInit.login_min.setOnAction(event -> {loginInit.setIconified(true);        });}    // 事件;退出    private void quit() {        loginInit.login_close.setOnAction(event -> {loginInit.close();            System.exit(0);        });}    // 事件;登陆    private void doEventLogin() {        loginInit.login_button.setOnAction(event -> {loginEvent.doLoginCheck(loginInit.userId.getText(), loginInit.userPassword.getText());});    }}
  • 这里的事件定义基本和前面一样,唯一不同的是我们不需要在这里通过 id 获取元素
  • 事件定义完成后,都会交给构造方法进行初始化。当然,如果后面这部分内容较多,还可以抽象为配置

5. 窗体的初始化操作

LoginInit.java & 窗体的初始化操作

public abstract class LoginInit extends UIObject {    private static final String RESOURCE_NAME = "/fxml/login/login.fxml";    protected ILoginEvent loginEvent;    public Button login_min;          // 登陆窗口最小化    public Button login_close;        // 登陆窗口退出    public Button login_button;       // 登陆按钮    public TextField userId;          // 用户账户窗口    public PasswordField userPassword;// 用户密码窗口    LoginInit(ILoginEvent loginEvent) {        this.loginEvent = loginEvent;        try {root = FXMLLoader.load(getClass().getResource(RESOURCE_NAME));        } catch (IOException e) {e.printStackTrace();        }        Scene scene = new Scene(root);        scene.setFill(Color.TRANSPARENT);        setScene(scene);        initStyle(StageStyle.TRANSPARENT);        setResizable(false);        this.getIcons().add(new Image("/fxml/login/img/system/logo.png"));        obtain();        initView();        initEventDefine();}    private void obtain() {login_min = $("login_min", Button.class);        login_close = $("login_close", Button.class);        login_button = $("login_button", Button.class);        userId = $("userId", TextField.class);        userPassword = $("userPassword", PasswordField.class);    }}
  • 这个类是一个抽象类,同时继承了 UI 父类方法。
  • 并且在构造函数中执行了初始化操作;initView()initEventDefine()
  • obtain() 方法中可以看到,我们这里就已经初始化获取了基本需要的元素,这样也方面我们后续的使用,不需要重复获取。

6. 窗体的展示

LoginView.java & 窗体的展示

public class LoginView {    private LoginInit loginInit;    private ILoginEvent loginEvent;    public LoginView(LoginInit loginInit, ILoginEvent loginEvent) {        this.loginInit = loginInit;        this.loginEvent = loginEvent;    }}
  • 这部分内容当前类主要承担着窗体初始化和窗体的事件的一个构造函数,对于后续其他复杂页面初始化更多的预定义元素才会更有用。

7. UI 父类定义

UIObject.java & UI 父类定义

public abstract class UIObject extends Stage {    protected Parent root;    private double xOffset;    private double yOffset;    public  <T> T $(String id, Class<T> clazz) {return (T) root.lookup("#" + id);    }    public void clearViewListSelectedAll(ListView<Pane>... listViews) {for (ListView<Pane> listView : listViews) {listView.getSelectionModel().clearSelection();}    }    public void move() {        root.setOnMousePressed(event -> {xOffset = getX() - event.getScreenX();            yOffset = getY()- event.getScreenY();            root.setCursor(Cursor.CLOSED_HAND);        });        root.setOnMouseDragged(event -> {setX(event.getScreenX() + xOffset);            setY(event.getScreenY() + yOffset);});        root.setOnMouseReleased(event -> {root.setCursor(Cursor.DEFAULT);        });}    // 初始化页面    public abstract void initView();    // 初始化事件定义    public abstract void initEventDefine();}
  • 这里我们将一些公用的方法和事件操作抽象为父类,共所有的框体使用
  • 同时我们预定了两个抽象类函数,initView()initEventDefine(),这样主要为了方便统一名称下的初始化操作。尤其在团队编码中,更加重要

六、效果演示

  • 首先我们在 org.itstack.naive.chat.ui.Application 中,添加我们的窗体启动代码,同时我们还实现了事件并传给构造函数;

    public class Application extends javafx.application.Application {  @Override  public void start(Stage primaryStage) throws Exception {ILoginMethod login = new LoginController((userId, userPassword) -> {System.out.println("登陆 userId:" + userId + "userPassword:" + userPassword);      });
      login.doShow();}
    public static void main(String[] args) {launch(args); }}
  • 点击运行,效果如下;

七、总结

  • 在这章节中我们定义了事件和实现了接口,并且我们将原有代码进行重构,以更加适合扩展的方式进行编码。
  • 关于最初的代码和重构的结构,需要好好理解下,也许你心里还有更佳的方案,那么也是可以尝试的,找到自己最合适的。
  • 等到我们开始实现具体业务流程开发的时候,就会体会到这么拆解架构的原因。因为我们的目标是让 UI 里不要写逻辑,保持 前后端分离
专栏学习简述以及全套源码获取
PC 端微信页面拆分及 JavaFx 使用
聊天框体实现:整体结构定义、侧边栏
聊天框体实现:对话框
聊天框体实现:对话聊天框
聊天框体实现:好友栏
聊天框体实现:好友填充框
聊天框体事件定义
练习篇:聊天表情框体实现
解答篇:聊天表情框体实现
服务端架构设计
通信协议包定义
客户端架构设计
数据库表结构设计
登录功能实现
搜索和添加好友
对话通知与应答
用户与好友通信
用户与群组通信
断线重连恢复通信
服务端控制台搭建
练习篇:聊天表情发送功能实现
解答篇:聊天表情发送功能实现

阅读全文: http://gitbook.cn/gitchat/column/5e5d29ac3fbd2d3f5d05e05f

  • 4
    点赞
  • 0
    评论
  • 11
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值