JavaGUI:多功能计算器(五)–Swing实现双语数据包+菜单切换(完整源码)
【背景提示】
“软件国际化”就是实现多种语言显示切换。
1.在前端网页上,可通过探测本地化语言环境实现自动切换;
2.在操作系统如Win10中,有各种显示语言包,可以下载/安装/卸载;
3.在应用程序App中,有专门的语言数据包,如中文/日文语言包等。
一、简单实现语言包:
在java的SwingGUI中,可以方便地实现双语悬停提示,但要实现语言包功能,难点就是要设计好语言包的格式。
本文的方法是:
- 使用Java特有的二维可变长数组(见程序前端的LANG[ ][ ]);
- 将GUI界面需显示的字符串进行分组,如按键组“pkeys”和菜单组“menu”;
- 字串分组里再按 “组信息+英文组+中文组”的顺序排列;
- 各分组顺序可任意排列,保持编辑修改的灵活性;
实现的功能为:
- 保留前文v0.40版本的鼠标悬停双语互提示功能(由iLang判别);
- 菜单切换中英文界面;切换后可同时看到两个窗口,但焦点在最后启动的窗口;关闭焦点窗口会同时关闭前导窗口,而关闭前导窗口不会关闭焦点窗口;
- 程序后面的命令执行部分仍然使用英文命令,中文命令由cmdIndex()方法实现自动转换;(详情参见后面完整源码)
二、完整源码:
(本源码版本为v0.41;后面的按键和菜单命令执行部分与前文v0.34基本相同)
//TODO v0.41;20230101w0;JavaSwing实现多语言菜单切换@多功能计算器;(同时保留双语悬停提示)
//文章标签: java gui awt swing eclipse
//版本说明:
//v0.41;20230101w0;JavaSwing实现多语言数据包@多功能计算器;双语菜单切换+悬停提示,创新版;
//v0.40;20221230;JavaSwing实现双语悬停提示@多功能计算器;(AWT2Swing直接升级版)
package calculator;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Calculate
{
//v0.34;20221005;总体布局;完整版3位版本号;简易版仅需1位版本号;
//v0.40;20221230(AWT2Swing直接升级版);
//计算变量:
private double num1,num2,mem; //操作数1/2,中间记忆mem,均为double;
private boolean isNum; //数字拼接标志位,转存后失效;
private String Oper; //二元计算操作符,依赖"="计算;
//显示变量:
private JTextArea tRecord; //操作记录;多行可编辑复制;用CR按键清空;
private JTextField tResult; //计算结果;单行可退格,由C按键复制后清空;
//主框架:
private JFrame frame;
private int wFrame;
private int hFrame;
private int iLang=0; //语言代码:默认0英文,,1中文;2俄文,3韩文,4日文;
//v0.41;20230101;定义窗口语言数据包;
private final String[][] LANG = {
{"language","1","0"},
{"English","Chinese","Russian"}, //iLang=0,1,2;
{"英语","汉语","俄语"},
{"about","1","100","70"},
{"About","Auther: yy-2020."},
{"关于DbY","作者: yy-2020."},
{"frame","1","590","270","res/yy-2020.png"}, //主框架frame,大小及图标文件;
{"Calculator V0.41"},
{"计算器DbY v0.41"},
{"textshow","1","5","20"}, //文本显示,默认5行25列;
{"RecordArea\n","ResultField"},
{"操作记录区\n","计算结果"},
{"pkeys","7","8"}, //按键板,默认7行8列;
{"temp" ,"f(x)" ,"n!" ,"(" ,"d" ,"e" ,"f" ,"x^y"},
{"tone" ,"log10","cbrt" ,")" ,"a" ,"b" ,"c" ,"%"},
{"tune" ,"tan" ,"sqrt" ,"MC" ,"7" ,"8" ,"9" ,"/"},
{"oct" ,"cos" ,"x^3" ,"M" ,"4" ,"5" ,"6" ,"*"},
{"sus" ,"sin" ,"x^2" ,"E" ,"1" ,"2" ,"3" ,"-"},
{"chrd" ,"rand" ,"1/x" ,"PI" ,"0" ,"." ,"-/+" ,"+"},
{"midi" ,"time" ,"dec" ,"hex" ,"CR" ,"C" ,"BS" ,"="},
{"速度" ,"函数" ,"阶乘" ,"(" ,"d" ,"e" ,"f" ,"乘方"},
{"音色" ,"对数10" ,"立方根" ,")" ,"a" ,"b" ,"c" ,"取余"},
{"变调" ,"正切" ,"平方根" ,"清存储" ,"7" ,"8" ,"9" ,"除"},
{"八度" ,"余弦" ,"立方" ,"存储" ,"4" ,"5" ,"6" ,"乘"},
{"延音" ,"正弦" ,"平方" ,"欧拉数" ,"1" ,"2" ,"3" ,"减"},
{"和弦" ,"随机数" ,"倒数" ,"圆周率" ,"0" ,"." ,"取负" ,"加"},
{"编曲" ,"时间" ,"10进制" ,"16进制" ,"清记录" ,"清结果" ,"退格" ,"="},
{"menu","5","0"}, //菜单定义;
{"File","Open","Save","saveTo","Exit"},
{"Option","Typical","Personal","Multifunc","Default","Set"},
{"Help","tTip","tShow","About","Example","Constant","Formula"},
{"eXtends","Music","Calendar","Weather","Health"},
{"Language","English","Chinese"},
{"文件","打开","保存","另存","退出"},
{"选项","典型","个性","多功能","默认","多选"},
{"帮助","提示","显示","关于","示例","常数","公式"},
{"扩展","音乐","日历","天气","健康"},
{"语言","英文","中文"},
};
//v0.41;20230103;语言数据包LANG[][]串组分类索引方法;类串cStr;返回类串索引号i;
private int classIndex(String cStr,String[][] Lang) {
for(int i=0;i<Lang.length;i++) {
if(Lang[i][0]==cStr) return i; //返回类串索引号i;
}return -1; //未找到匹配项,返回异常;
}
//v0.41;20230108;英语命令回归索引方法;为新增的双语串组LANG[][]专门改写的版本;
private String cmdIndex(String str,String cStr,String[][] Lang) {
int s = classIndex(cStr,Lang); //本类起始行号s@Lang[][];
int nRow = Integer.parseInt(Lang[s+0][1]); //本类行数nRow@Lang[][];
for(int i=nRow*2;i>0;i--) {
for(int j=0;j<Lang[s+i].length;j++) {
if(Lang[s+i][j]==str)
return Lang[s+i-(i<nRow?0:nRow)][j];//返回对应英语命令串;
}
}return "xxx"; //未找到匹配项,返回错误串;
}
//v0.34;20221005;将原init()中创建键盘按钮的程序段独立为方法,便于实现中英双语界面;
//v0.40;20221230;AWT2Swing直接升级版;并增加中文悬停提示;
//v0.41;20230102;为新增的双语串组LANG[][]专门改写的版本;
private void CreateButtons(JPanel owner,String[][] Lang,int s) {
int nRow=Integer.parseInt(Lang[s+0][1]); //键组行数@[s+0][1];
JButton [][]keys = new JButton[nRow][]; //创建按钮组;
for (int i = 0; i < nRow; i++){
keys[i] = new JButton[Lang[s+1+i].length];
for (int j = 0; j < Lang[s+1+i].length; j++){
keys[i][j] = new JButton(Lang[s+1+i+(iLang==0?0:nRow)][j]); //创建单个按钮,按钮名@kLang[][];
keys[i][j].setToolTipText(Lang[s+1+i+(iLang==0?nRow:0)][j]); //光标悬停中文提示;
keys[i][j].addActionListener(new MyKeysListen());//监听注册;动作响应@MyKeysListen();
owner.add(keys[i][j]); //放置按钮@物主owner;
}
}
}
//v0.34;20221005;将原init()中创建菜单的程序段独立为方法,便于实现中英双语界面;
//v0.40;20221230;AWT2Swing直接升级版,并增加中文悬停提示;
//v0.41;20230102;为新增的双语串组LANG[][]专门改写的版本;
private void CreateMenu(JMenuBar owner,String[][] Lang,int s) {
int nRow=Integer.parseInt(Lang[s+0][1]); //菜单length;
JMenu []menu = new JMenu[nRow];
JMenuItem [][]mItem = new JMenuItem[nRow][];
//自定义菜单监听执行动作类MyMenuListen(),事件统一响应;【核心类】
MyMenuListen mbl = new MyMenuListen(); //创建菜单监听类实例;
//菜单分类创建/注册/放置;命名@MENU[][];分隔线fileMenu.addSeparator();
for (int i = 0; i < nRow; i++){
menu[i] = new JMenu(Lang[s+1+i+(iLang==0?0:nRow)][0]); //创建菜单;
menu[i].setToolTipText(Lang[s+1+i+(iLang==0?nRow:0)][0]);//光标悬停中文提示;
menu[i].addActionListener(mbl); //监听注册;动作响应@MyMenuListen();
owner.add(menu[i]); //放置菜单@物主owner;
mItem[i] = new JMenuItem[Lang[s+1+i].length-1];
for (int j = 0; j < Lang[s+1+i].length-1; j++){
mItem[i][j] = new JMenuItem(Lang[s+1+i+(iLang==0?0:nRow)][j+1]); //创建菜单项;
mItem[i][j].setToolTipText(Lang[s+1+i+(iLang==0?nRow:0)][j+1]); //光标悬停中文提示;
mItem[i][j].addActionListener(mbl); //监听注册;动作响应@MyMenuListen();
menu[i].add(mItem[i][j]); //放置菜单项;
}
}
}
//------------------------------------
//v0.34;初始化"关于"对话框about;v0.33新增;
//v0.41;20230107;为新增的双语串组LANG[][]专门改写的版本;
private void aboutInit(JFrame owner,String[][] Lang)
{
int s = classIndex("about",Lang);
int nRow = Integer.parseInt(Lang[s+0][1]);
int wAbout = Integer.parseInt(Lang[s+0][2]);
int hAbout = Integer.parseInt(Lang[s+0][3]);
JDialog about = new JDialog(owner,Lang[s+1+(iLang==0?0:nRow)][0],true);
JLabel info = new JLabel(Lang[s+1+(iLang==0?0:nRow)][1],JLabel.CENTER);
info.setToolTipText(Lang[s+1+(iLang==0?nRow:0)][1]); //光标悬停中文提示;
about.setSize(wAbout,hAbout);
about.add(info,"Center"); //,BorderLayout.CENTER;
about.setLocationRelativeTo(owner); //位置跟随;关联frame;
about.setResizable(false); //默认true;
about.addWindowListener(new MyWinListen()); //注册窗口监听器;MyWinListen;
about.setVisible(true);
}
//v0.41;20230102w1;创新的主框架初始化;为新增的双语串组LANG[][]专门改写的版本;
private void frameInit(String[][] Lang)
{
int nRow; //本类行数@Lang[s+0][1];
for (int i=0;i < Lang.length; i++) {
switch(Lang[i][0]) {
case "language":
//iLang = (Lang[i][2]=="0"?0:1); //默认定制;菜单切换时不需要;
break;
case "frame":
nRow = Integer.parseInt(Lang[i+0][1]);
wFrame = Integer.parseInt(Lang[i+0][2]);
hFrame = Integer.parseInt(Lang[i+0][3]);
Image im = (new ImageIcon(Lang[i+0][4])).getImage();
frame = new JFrame(Lang[i+1+(iLang==0?0:nRow)][0]); //窗口标题title;
frame.setIconImage(im); //主窗口图标;
frame.setSize(wFrame+(iLang==0?0:43),hFrame); //主窗口大小;
frame.setBackground(Color.lightGray); //自定义深蓝Color(0,64,96);推荐Color.lightGray;
frame.setLocationRelativeTo(null); //将窗口居中;若无该方法,窗口将位于屏幕左上角;
//frame.setResizable(false); //禁止调整窗口大小。默认允许调整窗口尺寸;
break;
case "textshow":
nRow = Integer.parseInt(Lang[i+0][1]);
int rRow = Integer.parseInt(Lang[i+0][2]);
int rCol = Integer.parseInt(Lang[i+0][3]);
tRecord = new JTextArea(Lang[i+1+(iLang==0?0:nRow)][0],rRow,rCol);//创建文本区,操作记录显示区;
tRecord.setToolTipText(Lang[i+1+(iLang==0?nRow:0)][0]); //光标悬停中文提示;
frame.add(tRecord,"West");
tResult = new JTextField(Lang[i+1+(iLang==0?0:nRow)][1],rCol);//创建单个文本行,文本名@tLang[1][];
tResult.setToolTipText(Lang[i+1+(iLang==0?nRow:0)][1]); //光标悬停中文提示;
frame.add(tResult,"North");
break;
case "pkeys":
JPanel pkeys = new JPanel(); //按键板;默认7行8列;
int kRow = Integer.parseInt(Lang[i][1]);//键组行数;
int kCol = Integer.parseInt(Lang[i][2]);//键组列数;
pkeys.setLayout(new GridLayout(kRow,kCol,1,1)); //设置布局;网格间距行列(1,1);
CreateButtons(pkeys,Lang,i); //创建按键板;简单DIY;
frame.add(pkeys,"East"); //顺序在前,上层优先;
break;
case "menu":
JMenuBar mbar = new JMenuBar();
CreateMenu(mbar,Lang,i); //创建菜单;简单DIY;
frame.setJMenuBar(mbar);
}
}
frame.addWindowListener(new MyWinListen());
frame.setVisible(true);
}
//构造方法:
private Calculate() {
frameInit(LANG);
}
//------------------------------------
//v0.34;20221001;自定义菜单监听事件响应类;(监听事件响应;计算器控制主体)【核心类】
class MyMenuListen implements ActionListener //监听接口实现类;
{
//菜单项menuItem动作响应;--接口实现方法;
private void ActMenu(String act) { //菜单动作;菜单项mi;【核心方法】
switch(act){
case "Open" : break;
case "Save" : break;
case "Reset": frame.setSize(wFrame,hFrame);break;
case "tTip" : tResult.setText(iLang==0?"计算结果":"tResult");
tRecord.setText(iLang==0?"操作记录区:\n":"tRecord\n");break;
case "tShow": tRecord.append("\n"+"mem="+mem+"\n");
tRecord.append("num1+Oper+num2=tResult\n");
tRecord.append(num1+Oper+num2+"\n="+tResult.getText()+"\n");
break;
case "Exit" : System.exit(0);break;
case "Typical": frame.setSize(wFrame-(iLang==0?132:104),hFrame);break;
case "Personal": frame.setSize(wFrame-(iLang==0?66:30),hFrame);break;
case "Multifunc": frame.setSize(wFrame+(iLang==0?132:192),hFrame+49);break;
case "Default": frame.setSize(wFrame+(iLang==0?0:43),hFrame);break;
case "About": aboutInit(frame,LANG);break;
case "English": iLang=0;frameInit(LANG);break;
case "Chinese": iLang=1;frameInit(LANG);break;
}
}
//继承关系:Component.AbstractButton.MenuItem.Menu;
public void actionPerformed(ActionEvent e){ //执行动作(动作事件e);按钮响应;
String mcmd = e.getActionCommand(); //获取动作命令;菜单命令串名@MENU[];
//预置动作;
tRecord.append(mcmd); //追加菜单操作记录;
if(iLang!=0)mcmd=cmdIndex(mcmd,"menu",LANG);//将非英语字串转换为英文字串;
//标准动作;
ActMenu(mcmd); //执行菜单命令串;
}
}
//------------------------------------
//v0.34;20221005;自定义按键监听事件响应类;(监听事件响应;计算器运行主体;)【核心类】
class MyKeysListen implements ActionListener { //监听接口实现类;
//按键分类;--按键动作分类预处理:其它按钮无需列出;
private final String []opNumf = {
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f",".",}; //浮点数&hex按钮;
private final String []opNum1 = {
"x^2","x^3","sqrt","cbrt","1/x","n!","-/+","hex",
"rand","sin","cos","tan","log1",}; //一元操作符;
private final String []opNum2 = {"+","-","*","/","%","x^y",}; //二元操作符;
//按键动作分类方法;
private boolean opClass(String []array, String op){ //操作动作的分类方法;串组元素确认;
for(String element:array)if(element==op) return true; //--JDK5.0以后新增foreach语句;
return false;
}
//转换整数int到16进制串;
private String opHexStr(String []array, double num){ //标准名Int2HexStr();
String hexStr="";int d=(int)num; //十进制d;16进制串hexStr;
do{hexStr=opNumf[d%16]+hexStr;d/=16;}while(d!=0);
return hexStr;
}
//预处理动作@1~4;
private void ActInput(String act) { //输入动作;分类响应的预处理方法;
if(opClass(opNumf,act)==true) { //1.数字拼接@tfResult;
if(isNum==true) {
String ts=tResult.getText(); //继续拼接;临时串ts;
tResult.setText(ts+act); //追加动作串act;
}else tResult.setText(act); //重新拼接;
isNum=true;return; //首位数字拼接@ks;
}
if(opClass(opNum2,act)==true) { //2.双元操作符@Oper;
num1=Double.parseDouble(tResult.getText());
isNum=false;Oper=act;return; //双元操作符@"="时延迟生效;
}
if(act=="=") { //3.等号计算;双元计算num2;
num2=Double.parseDouble(tResult.getText());
isNum=false;return;
}
if(opClass(opNum1,act)==true) { //4.一元操作符;
num1=Double.parseDouble(tResult.getText()); //转化为double;
tRecord.append("=");
isNum=false;return;
}
}
//计算动作@5~7;
private void ActCalc(String act) { //计算动作;核心计算方法;
switch(act){
//预处理动作@1~4;
//5.主要计算动作;
case "hex" :
tResult.setText("0x"+opHexStr(opNumf,num1));break;//Int2HexStr;
case "n!":
int n=1;int t=(int)num1;do{n*=t--;}while(t!=1);
tResult.setText(n+"");break; //整数n的阶乘;
case "cbrt" :tResult.setText(Math.cbrt(num1)+"");break;//CubeRoot;
case "sqrt" :tResult.setText(Math.sqrt(num1)+"");break;//SquareRoot;
case "x^3" :tResult.setText(Math.pow(num1,3)+"");break;//Math.pow(a,b)取a的b次幂;
case "x^2" :tResult.setText(Math.pow(num1,2)+"");break;//
case "1/x" :tResult.setText(1/num1+"");break; //除0显示Infinity错误;
case "-/+" :tResult.setText(0-num1+"");break; //正负转换;
case "=" :
switch(Oper) { //等号操作;二元计算;
case "+" :tResult.setText(num1+num2+"");break;
case "-" :tResult.setText(num1-num2+"");break;
case "*" :tResult.setText(num1*num2+"");break;
case "/" :tResult.setText(num1/num2+"");break;
case "%" :tResult.setText(num1%num2+"");break;
case "x^y" :tResult.setText(Math.pow(num1,num2)+"");break;//Math.pow(x,y);
}break;
//6.辅助计算动作;
case "M":
if(mem==0) {
tRecord.append("MS"); //记忆保存MS;
mem=Double.parseDouble(tResult.getText());
}else {
tRecord.append("MR"); //记忆读取MR;
tResult.setText(mem+"");isNum=true;
}tRecord.append("\nmem="+mem+"\n");break;
case "MC":tRecord.append("MC");mem=0; //记忆清空MC;
tRecord.append("\nmem="+mem+"\n");break;
case "PI":tResult.setText(Math.PI+"");isNum=true;break; //圆周率;PI=3.141592653589793-2384626;
case "E" :tResult.setText(Math.E+"");isNum=true;break; //Euler;E=2.718281828459045-2354;
//7.特殊动作;
case "BS": //退格BackSpace;
tResult.setText(tResult.getText().substring(0,tResult.getText().length()-1));
break;
case "C": //Copy&Clear;
tRecord.append("\n="+tResult.getText()+"\n");//拷贝结果至记录区并换行;
tResult.setText("0");break;
case "CR" :
num1=num2=mem=0;
tRecord.setText("num1=num2=mem=0\n");break; //清空记录区;
}
}
//按钮动作响应;--接口方法;
public void actionPerformed(ActionEvent e){ //执行动作(动作事件e);按钮响应;
String key = e.getActionCommand(); //获取按键动作命令;即按键串名key@KEYS[];
//预置动作;
if(""==tResult.getText())tResult.setText("0");//避免空串赋值;
tRecord.append(key); //操作动作记录;将键名追加到记录区文本;
if(iLang!=0)key=cmdIndex(key,"pkeys",LANG); //将非英语字串转换为英文命令字串;
//标准动作;
ActInput(key); //输入动作;
ActCalc(key); //计算动作;
}
}
//------------------------------------
//v0.33;20221001;退窗口事件响应类;
//exit.addActionListener(e->System.exit(0));
class MyWinListen extends WindowAdapter{
public void windowClosing(WindowEvent e){
e.getWindow().setVisible(false); //关闭窗口;
e.getWindow().dispose(); //释放资源;
if(e.getSource()==frame)
System.exit(0); //完全退出;
}
}
//------------------------------------
//v0.33;20221001;主方法;
//v0.41;20230103;创新版,使用多语言数据包初始化;
public static void main(String[] args)
{
new Calculate(); //用构造方法创建实例;主框架初始化;
}
}
//----------------------------------------------------------------------------*/
三、运行效果:(截图)
中英文界面对比:(对话窗口自动跟随主窗口)
中文窗口的“语言”菜单:(多功能界面)
中文窗口的“帮助”菜单:(双语提示效果)
英文窗口中的“选项–>典型”计算器界面:(简易型界面)
网页在中英文浏览器环境下的自适应显示效果:
Win10的显示语言可以下载/安装/卸载:
IE浏览器切换使用语言@工具→Internet选项: