目录
- 第一章、前言
- 1.1、效果展示
- 1.2、原理分析
- 第二章、代码分开讲解
- 2.1、代码结构
- 2.2、初始化方法init()
- 2.3、收尾方法display()
- 2.4、核心方法button_add()
- 2.4.1、标签及文本框的添加
- 2.4.2、退格及清除按钮及其事件的添加
- 2.4.3、数字按钮及其事件的添加
- 2.4.4、加减乘除点的添加要用到的方法m1()(请忽略我这辣眼睛的起名(苦笑))
- 2.4.5、加减乘除点及其事件的添加
- 2.4.6、=事件的添加需要的方法clc()
- 2.4.7、=及其事件的添加
- 第三章、完整代码
- 第四章、结语
第一章、前言
本文我们利用JFrame容器实现简单计算器功能。
其实如果有java的一些基础是可以直接跳到完整代码那一部分去看的。我的注释基本算是全面的。
有些部分没有注释到的我也会在本文章中为你一一道来。
1.1、效果展示
讲代码之前还是先看看效果吧!
1.2、原理分析
首先要实现这个页面很简单 我们可以利用JFrame的网格布局方式然后逐步添加按钮标签等元素就行了,当然过程中肯定要添加事件喽。
或者我们可以利用swt插件直接利用鼠标拖动来进行排版。
但是我认为前者将控制权交个了电脑,后者充满了随意性都不是很好。所以我没有给布局,直接设置的null,这样就可以手动设置其大小和位置了。
第二章、代码分开讲解
2.1、代码结构
我将函数主体删掉,这样你们很容易看清这个代码的结构。
如上图大家可以看到
- 第10行以前都是一些导包操作,这些都是一顿 alt+/ 搞定 不用多说。
- 第11行开始定义类继承自JFrame类。
- 第13行定义了一个成员变量se 后面将字符型的计算式转化为结果候要用到,讲到,这里不做赘述。
- 第15行大家不陌生吧,一开始学,如果不是用的集成开发工具,那么大多情况下要手动输入这段,他就是函数的主入口。
- 第17到21行是构造方法,为了结构清晰,我在类中又定义了三个方法,然后在构造方法中调用这三个方法。
- 第23行的init就是被构造方法调用的第一个方法,具体作用是设置窗口大小位置等类似初始化的一些功能。
- 第25行button_add是被构造方法调用的第二个方法,也是这个程序的
核心方法
,功能就是给init中初始化出来的窗口中添加按钮标签和相关事件的。 - 第33行display是被构造方法调用的第二个方法,也是收尾方法,功能主要设置窗体的隐藏显示,和设置关闭窗体后默认执行的操作。
- 第28和第30行是为核心方法button_add服务的,也就是被它所调用,我们后面讲。
2.2、初始化方法init()
private void init() {
setBounds(300, 300, 500, 700);
getContentPane().setLayout(null);
setTitle("P&p calculator v5.0");
}
这里就三行代码。
第一行是设置第一次显示的时候,窗体的位置和大小。官方API是这样写的。
public void setBounds(int x , int y , int width , int height)。其中x y 是以屏幕左上角为原点建立坐标系,向右为x正方向,向下为y正方向。如下图为上面的参数的示意图。(尺寸不一定成比例)
第二行中getContentPane().setLayout(null);得到正在操作的窗体的contentPane 对象(类似于句柄)并将其布局方式设置为null。你可以理解为将所有元素都设置为相对于窗体的绝对位置,如果你设置一个布局方式之后,后续就不能通过上面的setBounds方法设置其位置了。
上面讲原理的时候已经讲过了,这样将布局方式设置为null之后你就可以用setBounds随心所欲的设置元素的大小和位置了。
这里要注意:如果是窗体,那么相对位置的原点是屏幕的左上角,而如果是窗体中的元素,那么相对的原点就是窗体的左上角(如果有标题栏那么就是相对于标题栏的左下角,正文的左上角,如下图)
这里还要注意的一点是:从上面可以看出,原点在窗口的左上角偏下的位置,我们这边设置的高为700,但是实际绘图区域的高可能只有650左右(减去了标题栏的高),这一点需要注意,(如下图)。还有一个宽也存在同样的问题,实际绘图区域比给的少,这个情况我目前还不知道是什么原因,估计最可能是我电脑屏幕小了分辨率高了,我就将缩放与布局设置成了125%,这样就导致了宽度与设置的大小不符合吧。
第三行不必多说,见名之意,就是设置标题,一般是软件名字及版本号,我把自己的别名也加上了。
2.3、收尾方法display()
因为中间的方法是核心方法,且比较多,我们放到最后讲,
我们先说收尾方法。就二行比较简单。
private void display() {
setVisible(1 < 2);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
第一行的方法为继承自java.awt.Window 的方法。
官方API是这样提供的
public void setVisible(boolean b)。
见名之意,设置此窗体对象的可见性 这里我用了一个表达式 (我是能偷懒绝对会偷懒的,少一个字符(1<2三个字符 true四个字符,以前设计excel公式时候养成的习惯))
第二行的方法官方API解释如下
public void setDefaultCloseOperation(int operation)
就是用户关闭窗体时候默认执行的操作。
本代码中执行的是用户关闭窗体后关闭程序,如果不设置,则会出现有趣的效果,各位可以试试。
2.4、核心方法button_add()
2.4.1、标签及文本框的添加
如上图先添加的当然是”显示屏幕”。
这两组(四种元素)的添加基本一致,就是位置不一样,我们拿一组出来说即可。
// 计算式标签
JLabel jisuan = new JLabel("计算式");
jisuan.setFont(new Font("华文楷体", Font.CENTER_BASELINE, 25));
jisuan.setBounds(0, 0, 100, 80);
getContentPane().add(jisuan);
// 计算式显示框
JTextField jisuan_text = new JTextField();
jisuan_text.setBounds(100, 15, 360, 50);
jisuan_text.setHorizontalAlignment(JTextField.LEFT);
jisuan_text.setFont(new Font("华文楷体", Font.PLAIN, 20));
getContentPane().add(jisuan_text);
首先是是计算式这三个字、
因为不需要改变,也不用添加任何事件,我们就用JLabel。
- 第1到2行简单,就是创建文本为“计算式”三个字的JLabe对象,并对其字体进行设置,比如我个人比较喜欢华文楷体,其实我更喜欢华文行楷,但是那个字体比一般字体小一号,不利于排版。
- 第3行就是他的位置大小,这个你们可以改变其值,然后运行一遍,不满意在调整,然后循环,直到你满意为止。
- 第4行就是将这个元素添加到当前窗体。
然后是计算式右边的计算式显示框
- 这个有四行和上面基本一致,(位置那一行你们通过推算设置即可)
- 有一行不一样的就是设置文本水平对齐方式 显而易见的我们设置的左对齐。(…JTextField.LEFT)
还有一组显示结果的标签及文本框的,基本同上,就是位置不一样,这里不在累述。
2.4.2、退格及清除按钮及其事件的添加
退格按钮
// 退格按钮
final JButton celan = new JButton();
celan.addActionListener(e -> jisuan_text.setText(jisuan_text.getText().length() != 0
? jisuan_text.getText().substring(0, jisuan_text.getText().length() - 1) : ""));
celan.setFont(new Font("华文楷体", Font.PLAIN, 42));
celan.setText("退 格");
celan.setBounds(50, 150, 180, 80);
getContentPane().add(celan);
这里就是普通的添加按钮操作然后设置字体、设置按钮文字、大小位置等最后添加到当前窗体。
唯独需要注意的是事件,也就是第二行,这里面我加了个点击事件,并利用Lambda表达式实现效果,如果不懂的请自行学习。这里我讲下Lambda表达式内部代码,这个就是一个三目表达式加上几个函数的嵌套。
首先,我们通过上面定义的计算式显示框的对象得到计算式的文本,然后判断其长度是否为0,也就说其是否为空。
是的话就直接返回空文本,然后结合外面的setText将计算式的值设置为空。
否的话就通过getText得到文本然后利用substring截取去掉最后一个字符,并将新字符串传递给setText参数进而实现退格效果。
清除按钮
// 清除按钮
final JButton celan_all = new JButton();
celan_all.addActionListener(e -> jisuan_text.setText(""));
celan_all.setFont(new Font("华文楷体", Font.PLAIN, 42));
celan_all.setText("清 除");
celan_all.setBounds(250, 150, 180, 80);
getContentPane().add(celan_all);
和退格如出一辙,就是最后的lambda表达式有所区别,这里直接设置为空值,不用那么多判断。
2.4.3、数字按钮及其事件的添加
这一组数字的按钮的添加最简单,不需要那么多判断,就是利用getText得到目前的字符串,然后追加一个上对应的数字,组成的新字符串作为setText的参数即可。比如数字1的代码如下。
// 数字1按钮
final JButton num_1 = new JButton();
num_1.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 1));
num_1.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_1.setText("1");
num_1.setBounds(50, 250, 80, 80);
getContentPane().add(num_1);
2.4.4、加减乘除点的添加要用到的方法m1()(请忽略我这辣眼睛的起名(苦笑))
因为加减乘除事件的添加中要用到这个方法,所以我就先讲这个
/**
* 当按下运算符相关按钮时候 <br>
* 检测计算式是否以运算符等相关文本结尾 <br>
* 是则返回空值 否则返回对应运算符的文本
* @param jisuan_text 显示计算式的文本框对象
* @param string 要追加的运算符 (/*-+.)
* @return 返回要追加的字符串 等待调用相关函数追加到计算式的末尾
*/
private String m1(JTextField jisuan_text, String string) {
String Str = jisuan_text.getText() + ((jisuan_text.getText().endsWith("+")
|| (jisuan_text.getText().endsWith("-")) || (jisuan_text.getText().endsWith("*"))
|| (jisuan_text.getText().endsWith("/")) || (jisuan_text.getText().endsWith("."))) ? "" : string);
return Str;
}
这个方法应该很好懂,就是参数中传入计算式的对象,然后通过getText方法得到其文本,然后判断他是不是以加减乘除点任意一个符号结尾的,如果是,则返回空否则返回你要添加的符号(加减乘除点按钮点哪个加哪个)将这个作为一个字符创追加到getText的后面,然后将这个新字符串返回出去。
2.4.5、加减乘除点及其事件的添加
我们以加号为例作以说明吧。
// 加号按钮
final JButton clc_add = new JButton();
clc_add.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, "+")));
clc_add.setFont(new Font("华文楷体", Font.PLAIN, 42));
clc_add.setText("+");
clc_add.setBounds(350, 250, 80, 80);
getContentPane().add(clc_add);
其中大部分和前面类似的代码,就是事件稍有不同,我们这里加以说明。
就是调用m1()方法,判断他是不是不是以数字结尾的,如果是则返回当前计算式加上加号组成一个新字符串,并以其为参数调用setText将其设置为新计算式即可。其他的符号的事件的添加就是一个字符串之差而已,这里就不用重复说了。
2.4.6、=事件的添加需要的方法clc()
/**
* 获取字符串计算式转化为结果
*
* @param se2
* ScriptEngine 对象
* @param text
* 要转化的字符串
* @return 返回转化后的结果
* @see 具体请参看这篇文章
*/
private String clc(ScriptEngine se2, String text) {
Object obj = null;
try {
obj = se.eval(text);
} catch (ScriptException e) {
e.printStackTrace();
}
return obj.toString();
}
以上方法实际上是参考如下博文,感谢(憨笑)。
姑苏城下大哥的这篇 《java 字符串表达式计算》
具体作用就是将由纯计算式组成的字符串转化成计算结果。
这里的两个参数第一个貌似是具体的引擎对象,就是我们类的唯一一个成员变量。第二个参数就是待计算的文本。由于文本可能是逻辑不正确的计算式(比如1+++2,这个明显不正确。)所以需要捕捉异常,这里使用的是try catch代码块。
try catch中我们调用eval方法得到一个对象,return的时候 ,我们调用其tostring方法获得文本并将其返回即可。
2.4.7、=及其事件的添加
// 等于按钮
final JButton num_equ = new JButton();
num_equ.addActionListener(e -> answer_text.setText(clc(this.se, jisuan_text.getText())));
num_equ.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_equ.setText("=");
num_equ.setBounds(250, 550, 80, 80);
getContentPane().add(num_equ);
同样的,这里其他部分不用多说,就是把添加事件部分拎出来说下。
answer_text.setText(clc(this.se, jisuan_text.getText()))
就是将计算式文本取出来和当前对象的唯一成员变量se对象一起传入clc。这样就得到当前计算式的结果,然后调用结果显示框的setText方法将其设置为结果即可。
第三章、完整代码
//我的包名 原本准备去除的 后来想了还是留着吧
//各位如果想直接用本代码 最好把这个删除了
package com.company.windows;
//这三个是实现文本型计算式到计算结果的三个包的导入
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.awt.Font;
//按钮
import javax.swing.JButton;
//顶层容器
import javax.swing.JFrame;
//标签 单纯的文本显示用
import javax.swing.JLabel;
//输入框 用以显示计算式和结果
import javax.swing.JTextField;
/**
* @author P&p
* @category 计算器 可实现基本计算功能
* @version 5.0
* @date 2020年9月6日
*/
// 新建类继承自JFrame类
public class Calculator4 extends JFrame {
// 创建一个 ScriptEngine对象 用于将字符串计算式转化为结果
ScriptEngine se = new ScriptEngineManager().getEngineByName("JavaScript");
/**
* 函数的主入口
*/
public static void main(String[] args) {
new Calculator4();
}
/**
* 构造函数 用三个过度函数<br>
* 进行初始化,元素添加及最终的显示界面和收尾
*/
public Calculator4() {
// 初始化相关操作,创建窗口,设置背景颜色标题等
init();
// 添加按钮和标签
button_add();
// 界面的显示及收尾工作
display();
}
private void init() {
setBounds(300, 300, 500, 700);
getContentPane().setLayout(null);
setTitle("P&p calculator v5.0");
}
private void button_add() {
// 计算式标签
JLabel jisuan = new JLabel("计算式");
jisuan.setFont(new Font("华文楷体", Font.CENTER_BASELINE, 25));
jisuan.setBounds(0, 0, 100, 80);
getContentPane().add(jisuan);
// 计算式显示框
JTextField jisuan_text = new JTextField();
jisuan_text.setBounds(100, 15, 360, 50);
jisuan_text.setHorizontalAlignment(JTextField.LEFT);
jisuan_text.setFont(new Font("华文楷体", Font.PLAIN, 20));
getContentPane().add(jisuan_text);
// 结果标签
JLabel answer = new JLabel("结 果");
answer.setFont(new Font("华文楷体", Font.CENTER_BASELINE, 25));
answer.setBounds(0, 55, 100, 80);
getContentPane().add(answer);
// 结果显示框
JTextField answer_text = new JTextField();
answer_text.setBounds(100, 70, 360, 50);
answer_text.setHorizontalAlignment(JTextField.LEFT);
answer_text.setFont(new Font("华文楷体", Font.PLAIN, 20));
getContentPane().add(answer_text);
// 退格按钮
final JButton celan = new JButton();
celan.addActionListener(e -> jisuan_text.setText(jisuan_text.getText().length() != 0
? jisuan_text.getText().substring(0, jisuan_text.getText().length() - 1) : ""));
celan.setFont(new Font("华文楷体", Font.PLAIN, 42));
celan.setText("退 格");
celan.setBounds(50, 150, 180, 80);
getContentPane().add(celan);
// 清除按钮
final JButton celan_all = new JButton();
celan_all.addActionListener(e -> jisuan_text.setText(""));
celan_all.setFont(new Font("华文楷体", Font.PLAIN, 42));
celan_all.setText("清 除");
celan_all.setBounds(250, 150, 180, 80);
getContentPane().add(celan_all);
// 数字1按钮
final JButton num_1 = new JButton();
num_1.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 1));
num_1.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_1.setText("1");
num_1.setBounds(50, 250, 80, 80);
getContentPane().add(num_1);
// 数字2按钮
final JButton num_2 = new JButton();
num_2.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 2));
num_2.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_2.setText("2");
num_2.setBounds(150, 250, 80, 80);
getContentPane().add(num_2);
// 数字3按钮
final JButton num_3 = new JButton();
num_3.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 3));
num_3.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_3.setText("3");
num_3.setBounds(250, 250, 80, 80);
getContentPane().add(num_3);
// 加号按钮
final JButton clc_add = new JButton();
clc_add.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, "+")));
clc_add.setFont(new Font("华文楷体", Font.PLAIN, 42));
clc_add.setText("+");
clc_add.setBounds(350, 250, 80, 80);
getContentPane().add(clc_add);
// 数字4按钮
final JButton num_4 = new JButton();
num_4.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 4));
num_4.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_4.setText("4");
num_4.setBounds(50, 350, 80, 80);
getContentPane().add(num_4);
// 数字5按钮
final JButton num_5 = new JButton();
num_5.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 5));
num_5.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_5.setText("5");
num_5.setBounds(150, 350, 80, 80);
getContentPane().add(num_5);
// 数字6按钮
final JButton num_6 = new JButton();
num_6.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 6));
num_6.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_6.setText("6");
num_6.setBounds(250, 350, 80, 80);
getContentPane().add(num_6);
// 减号按钮
final JButton clc_sub = new JButton();
clc_sub.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, "-")));
clc_sub.setFont(new Font("华文楷体", Font.PLAIN, 42));
clc_sub.setText("-");
clc_sub.setBounds(350, 350, 80, 80);
getContentPane().add(clc_sub);
// 数字7按钮
final JButton num_7 = new JButton();
num_7.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 7));
num_7.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_7.setText("7");
num_7.setBounds(50, 450, 80, 80);
getContentPane().add(num_7);
// 数字8按钮
final JButton num_8 = new JButton();
num_8.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 8));
num_8.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_8.setText("8");
num_8.setBounds(150, 450, 80, 80);
getContentPane().add(num_8);
// 数字9按钮
final JButton num_9 = new JButton();
num_9.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 9));
num_9.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_9.setText("9");
num_9.setBounds(250, 450, 80, 80);
getContentPane().add(num_9);
// 乘号按钮
final JButton clc_mun = new JButton();
clc_mun.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, "*")));
clc_mun.setFont(new Font("华文楷体", Font.PLAIN, 42));
clc_mun.setText("*");
clc_mun.setBounds(350, 450, 80, 80);
getContentPane().add(clc_mun);
// 小数点按钮
final JButton num_point = new JButton();
num_point.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, ".")));
num_point.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_point.setText(".");
num_point.setBounds(50, 550, 80, 80);
getContentPane().add(num_point);
// 数字0按钮
final JButton num_0 = new JButton();
num_0.addActionListener(e -> jisuan_text.setText(jisuan_text.getText() + 0));
num_0.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_0.setText("0");
num_0.setBounds(150, 550, 80, 80);
getContentPane().add(num_0);
// 等于按钮
final JButton num_equ = new JButton();
num_equ.addActionListener(e -> answer_text.setText(clc(this.se, jisuan_text.getText())));
num_equ.setFont(new Font("华文楷体", Font.PLAIN, 42));
num_equ.setText("=");
num_equ.setBounds(250, 550, 80, 80);
getContentPane().add(num_equ);
// 除以按钮
final JButton clc_div = new JButton();
clc_div.addActionListener(e -> jisuan_text.setText(m1(jisuan_text, "/")));
clc_div.setFont(new Font("华文楷体", Font.PLAIN, 42));
clc_div.setText("/");
clc_div.setBounds(350, 550, 80, 80);
getContentPane().add(clc_div);
}
/**
* 获取字符串计算式转化为结果
*
* @param se2
* ScriptEngine 对象
* @param text
* 要转化的字符串
* @return 返回转化后的结果
* @see 具体请参看这篇文章
*/
private String clc(ScriptEngine se2, String text) {
Object obj = null;
try {
obj = se.eval(text);
} catch (ScriptException e) {
e.printStackTrace();
}
return obj.toString();
}
/**
* 当按下运算符相关按钮时候 <br>
* 检测计算式是否以运算符等相关文本结尾 <br>
* 是则返回空值 否则返回对应运算符的文本
* @param jisuan_text 显示计算式的文本框对象
* @param string 要追加的运算符 (/*-+.)
* @return 返回要追加的字符串 等待调用相关函数追加到计算式的末尾
*/
private String m1(JTextField jisuan_text, String string) {
String Str = jisuan_text.getText() + ((jisuan_text.getText().endsWith("+")
|| (jisuan_text.getText().endsWith("-")) || (jisuan_text.getText().endsWith("*"))
|| (jisuan_text.getText().endsWith("/")) || (jisuan_text.getText().endsWith("."))) ? "" : string);
return Str;
}
/**
* 收尾工作 设置显示 和 当关闭了窗口时候关闭该程序。<br>
* 注意:如果不设置则会在后台运行浪费内存资源。
*/
private void display() {
setVisible(1 < 2);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
第四章、结语
终于完成了。下面又到了总结时间了。
- 关于如何将计算式转化成对应结果这一块,我是完全参考一篇博客的(详见2.4.6),至于其原理,一直没弄懂,后续我会尝试弄懂他,各位如果有懂的能不能教教我(委屈)。
- 既然基本的运算功能能实现,那么那些科学计算器是不是也能够实现呢,各位可以在这个基础上加以改进。
- 这个计算器不知各位看出来没,有个小瑕疵,就是如果计算式是以符号结尾的按等号没有用,你们可以尝试改进下。
- 部分方法的起名比较随意,比如核心方法button_add按照命名规则其实正确命名规范为buttonAdd。并且这个命名也和方法实现的功能不匹配,实际添加的不只有button 还有JLabel,JTextField 。
- 还有这十数字键的添加太过繁琐,应该是有简单的方法,比如将数字组成一个字符串1234567890,然后用循环添加按钮,并用charAt方法取得对应位置的数字。当然你会说设置位置时候怎么办,可以将位置坐标值推算好,然后按顺序添加到数组中,然后循环时候用下标取就可以了。
- 同样的减加乘除点也是一样 位置一个数组存储,对应符号一个数字存储,循环添加,这样能大大减少代码量,这个就留给你们去优化了。
- 还有就是我的事件和方法中存在大量的getText 和setText ,这个其实可以适当的做临时变量,这样也能大大减少代码量。
好了就啰嗦这么多,还是那句话,有任何问题,欢迎私信,评论。可能我第一时间没看到(报了个java班为期五个月,已经49天啦,手机上课时候上交了),我看到了,在我能力范围的,肯定会帮你解决的。
Title:the 9th blog
By:P&p
Time:2020-09-06 (学JAVA的第49天)