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

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文件里。如下:












	
		
			
				
			
		
		
		
			
				
			
		
		
		
		
		
		

fxml里面有各种id定义,有了这些id,我们就可以用java代码对其进行控制。在fxml里,有一个fx:controller元素就是用来定义对应的控制器的。
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);
	}
}
类似于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 stages = new HashMap<>();

	private Map 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 load(String resource, Class 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 load(String resource, Class 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聊天室客户端





你可能感兴趣的:(java,基于Netty的仿QQ聊天室)