Netty网络聊天室之优雅的JavaFX界面开发

来源:互联网 发布:java bp神经网络 编辑:程序博客网 时间:2024/06/06 10:03

Java在服务端领域能够独占鳌头,然而,在客户端领域一直不温不火。Java的客户端技术,从AWT,SWING,SWT,一直到JAVAFX,即使每一代都有非常大的改善,但仍改变不了它尴尬的地位。依我看来,使用JAVA来开发桌面应用,大概只有我有我们这批javaer了偷笑

不管怎样,javafx的设计理念还是非常优秀的。它借鉴了html开发的特点,将代码,界面,样式三者分离。使用java代码来控制逻辑,使用xml来设计界面,使用css来控制样式。

本文将使用一个简单的例子,来演示javafx的开发案例。

开发工具

e(fx)eclipse是一个带有javafx开发插件的eclipse版本,它自带css,xml代码自动补全。
JavaFX Scene Builder 2.0是一个javafx界面开发的辅助工具,其实就是支持你使用拖曳的方式来设计界面。对于调整界面位置非常有用。

界面样式逻辑三权分立

有了efxeclipse,我们就可以来开发我们的javafx项目啦。
登录界面控件非常少,可以拿来作为入门例子。
在javafx里,Stage表示一个有用户界面的应用程度窗口,在Stage下面,则是由Scene来表示界面容器。Scene包含所有界面组件,例如各种ui控件和ui子容器。
类似于Flex开发,我们的javafx界面设计是写在fxml文件里。如下:
<?xml version="1.0" encoding="UTF-8"?><?import javafx.geometry.*?><?import javafx.scene.paint.*?><?import javafx.scene.text.*?><?import javafx.scene.control.*?><?import javafx.scene.chart.*?><?import javafx.scene.image.*?><?import java.lang.*?><?import javafx.scene.layout.*?><AnchorPane maxHeight="-Infinity" maxWidth="-Infinity"minHeight="-Infinity" minWidth="-Infinity" prefHeight="330.0"prefWidth="430.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"fx:controller="com.kingston.ui.controller.LoginViewController"stylesheets="@../css/login.css"><children><ImageView fitHeight="330.0" fitWidth="430.0" layoutX="91.0"layoutY="90.0" pickOnBounds="true" preserveRatio="true"AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"><image><Image url="@../img/login.png" /></image></ImageView><Button fx:id="login" layoutX="135.0" layoutY="288.0"mnemonicParsing="false" onMouseClicked="#login" onMouseEntered="#login_en"onMouseExited="#login_ex" prefHeight="32.0" prefWidth="194.0" text="%login.safeLogin"textFill="WHITE"><font><Font size="18.0" /></font></Button><ImageView fitHeight="80.0" fitWidth="80.0" layoutX="38.0"layoutY="195.0" pickOnBounds="true" preserveRatio="true"><image><Image url="@../img/head.jpg" /></image></ImageView><TextField fx:id="userId" layoutX="135.0" layoutY="195.0"prefHeight="25.0" prefWidth="194.0" promptText="%login.account" text="1000" /><PasswordField fx:id="password" layoutX="135.0" layoutY="226.0"prefHeight="25.0" prefWidth="194.0" promptText="%login.password" /><CheckBox fx:id="rememberPsw" layoutX="135.0" layoutY="258.0"mnemonicParsing="false" text="%login.rememberPsw" /><CheckBox fx:id="autoLogin" layoutX="260.0" layoutY="258.0"mnemonicParsing="false" text="%login.autoLogin" /><Label /><Label /><ImageView fx:id="closeBtn" fitHeight="30.0" fitWidth="30.0"layoutX="399.0" onMouseClicked="#close" onMouseEntered="#closeEntered"onMouseExited="#closeExited" pickOnBounds="true" preserveRatio="true"AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"><image><Image url="@../img/close.png" /></image><rotationAxis><Point3D /></rotationAxis></ImageView><ImageView fx:id="minBtn" fitHeight="30.0" fitWidth="30.0"layoutX="346.0" onMouseClicked="#min" onMouseEntered="#minEntered"onMouseExited="#minExited" pickOnBounds="true" preserveRatio="true"AnchorPane.rightAnchor="30.0" AnchorPane.topAnchor="0.0"><image><Image url="@../img/min.png" /></image></ImageView><ProgressBar layoutX="132.0" layoutY="289.0" prefHeight="32.0" fx:id="loginProgress"prefWidth="200.0" visible="false" /><Pane fx:id="errorPane" layoutY="36.0" prefHeight="307.0"prefWidth="430.0" style="-fx-background-color: #9AD3EE;" visible="false"AnchorPane.topAnchor="30.0"><children><Label fx:id="errorTips" layoutX="118.0" layoutY="52.0" prefHeight="153.0"prefWidth="194.0" wrapText="true"><font><Font size="27.0" /></font></Label><Button layoutX="118.0" layoutY="223.0" mnemonicParsing="false"onMouseClicked="#backToLogin" prefHeight="32.0" prefWidth="194.0"style="-fx-background-color: #09A3DC;" text="%login.backToLogin" textFill="WHITE"><font><Font size="18.0" /></font></Button></children></Pane><Hyperlink layoutX="339.0" layoutY="195.0" text="%login.registerAccount"onMouseClicked="#gotoRegister"><font><Font size="14.0" /></font></Hyperlink><Hyperlink layoutX="339.0" layoutY="226.0" text="%login.findPsw"><font><Font size="14.0" /></font></Hyperlink></children></AnchorPane>
fxml里面有各种id定义,有了这些id,我们就可以用java代码对其进行控制。在fxml里,有一个fx:controller元素就是用来定义对应的控制器的。
public class LoginViewController implements ControlledStage, Initializable {@FXMLprivate Button login;@FXMLprivate TextField userId;@FXMLprivate PasswordField password;@FXMLprivate CheckBox rememberPsw;@FXMLprivate CheckBox autoLogin;@FXMLprivate ImageView closeBtn;@FXMLprivate void login() throws IOException {final long useId = Long.parseLong(userId.getText());final String psw = password.getText();if (!IoBaseService.INSTANCE.isConnectedSever()) {errorPane.setVisible(true);errorTips.setText(I18n.get("login.failToConnect"));return;}loginProgress.setVisible(true);login.setVisible(false);LoginManager.getInstance().beginToLogin(useId, psw);}}
类似于LoginViewController里,如果我们需要控制xml里面的控件,我们就需要在代码里对控件类型写上@FXML的注解,变量的名字就是fxml里的id了。同时,控件的各种触发事件,例如点击,滑动事件,也需要写上@FXML注解,方法名称也要跟fxml文件里的命名一样,例如 onMouseClicked="#login"。
类似于html的css文件,我们的ui控件样式最好也是写在独立的css文件,然后通过fxml进行指向,例如: stylesheets="@../css/login.css"。
在这里有个需要特别注意的是,如果代码需要更新ui显示,我们必须要在javafx的ui线程上进行
/** * 将任务转移给fxapplication线程延迟执行 * @param task */public void runTaskInFxThread(Runnable task){Platform.runLater(task);}

文字资源的国际化支持

客户端开发程序,我们就不可避免地要进行国际化支持,对不同的国际地区显示不同的文字。javafx在这方面也做得不错。
在加载fxml的时候,我们可以指定国际化资源文件,如下 
URL url = Thread.currentThread().getContextClassLoader().getResource(resource);FXMLLoader loader = new FXMLLoader(url);loader.setResources(ResourceBundle.getBundle("i18n/message"));
这样,fxml就可以使用 text="%login.rememberPsw"  的方式引入国际化资源了。
在非fxml文件里使用国际化文字的方式。定义一个加载使用国际化文字的工具类(I18n.java)
/** * 国际化资源池 * @author kingston */public class I18n {    private static ResourceBundle resourcePool;    static {        try {            resourcePool = ResourceBundle.getBundle("i18n/message");        } catch (Exception e) {            e.printStackTrace();        }    }    public static String get(String key, Object... args) {        if (!resourcePool.containsKey(key)) {           return "国际化资源不存在";        }        String message = resourcePool.getString(key);        if (args != null) {            return MessageFormat.format(message, args);        } else {            return message;        }    }}
在代码里,使用 I18n.get("register.operateSucc") 即可。
 

统一管理Stage的加载与切换

在JAVAFX里,经常需要在不同的stage进行切换。为了能高效统一管理stage,我们可以将全部stage缓存起来,根据需要动态显示隐藏窗口。
public class StageController {private Map<String, Stage> stages = new HashMap<>();private Map<String, ControlledStage> controllers = new HashMap<>();public void addStage(String name, Stage stage) {this.stages.put(name, stage);}public Stage getStageBy(String name) {return this.stages.get(name);}public void setPrimaryStage(String name, Stage stage) {this.addStage(name, stage);}public Stage loadStage(String name, String resource, StageStyle... styles) {Stage result = null;try{URL url = Thread.currentThread().getContextClassLoader().getResource(resource);FXMLLoader loader = new FXMLLoader(url);loader.setResources(ResourceBundle.getBundle("i18n/message"));Pane tmpPane = (Pane)loader.load();ControlledStage controlledStage = (ControlledStage)loader.getController();this.controllers.put(name, controlledStage);Scene tmpScene = new Scene(tmpPane);result = new Stage();result.setScene(tmpScene);for (StageStyle style:styles) {result.initStyle(style);}this.addStage(name, result);}catch(Exception e) {e.printStackTrace();}return result;}@SuppressWarnings("unchecked")public <T> T load(String resource, Class<T> clazz) {try{URL url = Thread.currentThread().getContextClassLoader().getResource(resource);FXMLLoader loader = new FXMLLoader(url);return (T)loader.load();}catch(Exception e){e.printStackTrace();}return null;}@SuppressWarnings("unchecked")public <T> T load(String resource, Class<T> clazz, ResourceBundle resources) {try{URL url = Thread.currentThread().getContextClassLoader().getResource(resource);return (T)FXMLLoader.load(url, resources);}catch(Exception e){e.printStackTrace();}return null;}public Stage setStage(String name) {Stage stage = this.getStageBy(name);if (stage == null) {return null;}stage.show();return stage;}public boolean switchStage(String toShow, String toClose) {getStageBy(toClose).close();setStage(toShow);return true;}public void closeStge(String name) {Stage target = getStageBy(name);target.close();}public boolean unloadStage(String name) {return this.stages.remove(name) != null;}public ControlledStage getController(String name) {return this.controllers.get(name);}}


全部代码已在github上托管

服务端代码请移步 --> netty聊天室服务器

客户端代码请移步 --> netty聊天室客户端