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>
public class LoginViewController implements ControlledStage, Initializable {
@FXML
private Button login;
@FXML
private TextField userId;
@FXML
private PasswordField password;
@FXML
private CheckBox rememberPsw;
@FXML
private CheckBox autoLogin;
@FXML
private ImageView closeBtn;
@FXML
private 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);
}
}
类似于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文件里使用国际化文字的方式。定义一个加载使用国际化文字的工具类(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聊天室客户端