先看看效果吧:css
很炫酷吧?git
想不想要?github
想要吧.bash
固然做者知道大家确定想.app
否则也不会点进来对不对.less
好.进入正题.ide
这个是仿照win10自带的计算器制做的简化版本.是用Qt作的,直接把整个表达式输入而后得出计算结果. 主要分为三部分.界面部分,事件处理部分与表达式处理部分.函数
选择Widgets Application.工具
起名字.oop
通常只需MinGW.
这里默认便可,名字能够随便改
按键的话,基本上按着改就能够了.改布局,改颜色,改字体,主要就是这三个. 首先先打开.ui文件:
这里给出做者的参考style:
border:1px groove rgb(220,220,220); background-color:rgb(243,243,243);
字体:
这里按我的喜爱调整便可.
这里不只把里面的字符改变,还要把相应的对象名也改变.
再细调每个按键,包括大小,字体与颜色,使整体效果更好.
数字要注意有"加粗"效果,符号的话尽可能"精细"一点.
调整好间隔.注意细节.
下面是win10自带的计算器:
看到间隔了没?
做者要的就是这种效果.
能够先运行看看.
两边的间隔的话一会配合widget的大小调整便可.
输出框很简单,就是一个QLineEdit.
做者的qss:
border:0px groove rgb(243,243,243); background-color:rgb(245,245,245);
标题栏其实也很简单,一个QBoxLayout
QLabel输入标题,两个QPushButton表示最小化与关闭,同时加入两个Spacer,让标题与左边空出一些距离.
其实就是模仿win10的标题栏的效果
这里就不作最大化了.由于涉及到按钮的从新排布问题,这个能够本身选择实现.
把上一步作的标题栏移到合适的位置,同时删除自带的QMenuBar,QToolBar,QStatusBar.
调整好后大概就那样,透明度这里选择了0.9.
真是完美啊!
首先把原本那个标题栏去掉.
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
再在protected中加入鼠标监听函数:
void mousePressEvent(QMouseEvent *); void mouseMoveEvent(QMouseEvent *);
私有成员中加入两个QPoint.分别表示当前窗口坐标与光标的坐标.
QPoint mousePoint; QPoint windowPoint;
第一个函数是鼠标按下时触发的,根据event->button()判断是不是左键,是的话获取mouse坐标,在设置window坐标.
当触发第二个函数时,即先判断是否按住左键不放,使用MainWindow的move方法移动窗口.
event->globalPos()获取坐标后减去原来光标的坐标获得window坐标的变化量,再用原坐标加上这个变化量.
void MainWindow::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { mousePoint = event->globalPos(); windowPoint = frameGeometry().topLeft(); } } void MainWindow::mouseMoveEvent(QMouseEvent *event) { if(event->buttons() & Qt::LeftButton) { move(windowPoint + event->globalPos() - mousePoint); } }
这里以最小化为例,关闭也同样的,改一下函数调用便可. 在最小化按钮中右键选择Go to slot:
选择clicked()
添加一个最小化函数:
下面是关闭的函数:
按键的鼠标事件包括两个:
这里的实现方式是经过事件过滤器实现的.增长一个eventFilter()函数
bool eventFilter(QObject *,QEvent *);
首先经过event->type()判断事件类型,若是是光标悬停,再判断对应的各个对象增长阴影效果.
addNumButtonEffet():
void MainWindow::addNumButtonEffect(QPushButton *button,QGraphicsDropShadowEffect *shadow) { shadow->setEnabled(true); button->setStyleSheet( "border:1px groove rgb(220,220,220);" "background-color:rgb(193,193,193);" ); }
这里QGraphicsDropShadowEffect *shadow事先初始化好了.
而后在添加事件过滤器:
这里能够对比一下有没有阴影的效果:
没有阴影:
加上阴影:
呃....这里多是截图工具的问题,看不来多大的效果,可是直接在机器上看是有比较大的区别的,建议仍是加上阴影.
单击事件就是单击了某个按键而后用户能够在输出框中看到对应的反应.
依次选择按键,右键Go to slot:
选择clicked()
而后添加处理函数,做者这里本身实现了一个添加文本与清除焦点的函数,添加文本就是对应按键被光标单击后添加到输出框,至于为何要清除焦点....
由于...
由于空格.
由于做者的"良好"习惯,习惯在运算符先后加上空格
单击后会把焦点保留在这个按钮上,键盘上敲空格默认会帮你"按一次"这个按钮,所以若是不清除焦点的话,在光标单击了某个按钮,好比7,按空格就会在输出框上输出7,光标单击了8后,按空格就会在输出框上输出8.
这里添加文本时还要注意默认的起提示做用的0.
void MainWindow::appendText(const QString &s) { if(ui->box->text() == "0") ui->box->setText(s); else ui->box->setText(ui->box->text()+s); } void MainWindow::appendTextAndClearFocus(QPushButton *button, const QString &s) { appendText(s); button->clearFocus(); }
键盘事件就是主要处理各个按键按下时的阴影与输出框添加输出. 键盘事件经过如下两个函数处理:
void keyPressEvent(QKeyEvent *); void keyReleaseEvent(QKeyEvent *);
第一个是按键按下时触发的,第二个是松开按键触发的.
在按键按下时添加上阴影与颜色加深效果.
经过event->key()依次判断各个键.
而后添加在keyRealeseEvent()中把对应的阴影去掉:
void MainWindow::keyReleaseEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: case Qt::Key_Plus: case Qt::Key_Minus: case Qt::Key_Asterisk: case Qt::Key_Slash: case Qt::Key_AsciiCircum: case Qt::Key_Percent: case Qt::Key_ParenLeft: case Qt::Key_ParenRight: case Qt::Key_BraceLeft: case Qt::Key_BraceRight: case Qt::Key_BracketLeft: case Qt::Key_BracketRight: case Qt::Key_Backspace: case Qt::Key_Space: case Qt::Key_Period: case Qt::Key_Escape: case Qt::Key_Equal: case Qt::Key_Return: removeNumButtonEffect(ui->num0,num0_shadow); removeNumButtonEffect(ui->num1,num1_shadow); removeNumButtonEffect(ui->num2,num2_shadow); removeNumButtonEffect(ui->num3,num3_shadow); removeNumButtonEffect(ui->num4,num4_shadow); removeNumButtonEffect(ui->num5,num5_shadow); removeNumButtonEffect(ui->num6,num6_shadow); removeNumButtonEffect(ui->num7,num7_shadow); removeNumButtonEffect(ui->num8,num8_shadow); removeNumButtonEffect(ui->num9,num9_shadow); removeSignButtonEffect(ui->plus,plus_shadow); removeSignButtonEffect(ui->minus,minus_shadow); removeSignButtonEffect(ui->mutiply,mutiply_shadow); removeSignButtonEffect(ui->divide,divide_shadow); removeSignButtonEffect(ui->pow,pow_shadow); removeSignButtonEffect(ui->percent,percent_shadow); removeSignButtonEffect(ui->parentheses,parentheses_shadow); removeSignButtonEffect(ui->parentheses,parentheses_shadow); removeSignButtonEffect(ui->brace,brace_shadow); removeSignButtonEffect(ui->brace,brace_shadow); removeSignButtonEffect(ui->bracket,bracket_shadow); removeSignButtonEffect(ui->bracket,bracket_shadow); removeSignButtonEffect(ui->backspace,backspace_shadow); removeSignButtonEffect(ui->blank,space_shadow); removeSignButtonEffect(ui->dot,dot_shadow); removeSignButtonEffect(ui->C,c_shadow); removeSignButtonEffect(ui->equal,equal_shadow); break; } }
这里之因此没有一个个按键去判断是由于有可能同时多个按键按下,而后同时松开后发现某个按键还存在阴影,所以统一当其中一个按键释放时去除全部按键的阴影.
在输出框中添加输出,调用一个函数便可:
看看效果:
这里实际使用了Qt的动画,针对透明度改变的动画.
void MainWindow::fadeIn(void) { QPropertyAnimation * changeOpacity = new QPropertyAnimation(this,"windowOpacity"); changeOpacity->setStartValue(0); changeOpacity->setEndValue(0.91); changeOpacity->setDuration(2500); changeOpacity->start(); }
第一行表示改变的是透明度,第二三行设置起始值与结束值,接下来设置动画时间(单位ms),而后开始动画.
这里能够不设置最大尺寸,但必定要设置最小尺寸.
设置这个实际上禁止了拖拽去改变大小.
淡出效果与淡入效果相似.
不一样的时须要添加计时处理,不能直接在exit(0)前调用fadeOut()函数,由于动画会在另外一个线程启动,因此须要在主线程休眠指定秒数,等待淡出效果完成后,主线程再调用exit(0);
void MainWindow::fadeOut(void) { QPropertyAnimation * changeOpacity = new QPropertyAnimation(this,"windowOpacity"); changeOpacity->setStartValue(0.9); changeOpacity->setEndValue(0); changeOpacity->setDuration(2500); changeOpacity->start(); QTime start = QTime::currentTime().addMSecs(2500); while(QTime::currentTime() < start) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); }
其中addMSecs()表示要延迟的秒数,while循环体中表示处理本线程的事件,其中100表示处理事件最多100ms就返回本语句.
这里就不放淡出效果的图片了.
因为这是整个字符串做为表达式进行输入,须要先进行判断再计算.因此分为判断与计算两部分.
这里使用了一个新开的控制台工程,后面会把这个整合起来.
使用了一个check类判断,因为只有10个数字按键,加减乘除,小数点,求余,求次幂,大中小括号,空格,因此能够分红这几类进行判断.
void removeAllBlank(void) { size_t i = 0; while((i = s.find(' ',i)) != string::npos) s.erase(i,1); }
首先把全部空格去除,避免以后的判断.
把表达式中的全部字符分红5类:
而后就是针对每一个类型判断它的下一个字符是不是容许的类型,不是的话返回false.
好比碰上了一个 ( 或 [ 或 {
则它的下一个不能是运算符号或者小数点,固然容许-与+,由于有 (-7) (+234)
这种状况.
而后把这个符号保存下来判断后面是不是对应的右括号.
if(isLeftBrace(i)) { if(isSignOrDot(i+1)) { if(s[i+1] != '-' && s[i+1] != '+') return false; } braces.push(s[i]); }
整个判断函数以下:
bool valid(void) { if(isSignOrDot(0) || isRightBrace(0)) return false; len = s.size(); stack<char> braces; for(size_t i=0;i<len;++i) { if(isLeftBrace(i)) { if(isSignOrDot(i+1)) { if(s[i+1] != '-' && s[i+1] != '+') return false; } if(isRightBrace(i+1)) return false; braces.push(s[i]); } else if(isRightBrace(i)) { if(isDot(i+1) || isDigit(i+1) || isLeftBrace(i+1)) return false; if(isRightBrace(i+1)) { stack<char> braces_copy(braces); if(braces_copy.empty()) return false; braces_copy.pop(); if(braces_copy.empty()) return false; } if(braces.empty()) return false; char brace = braces.top(); if((brace == '(' && s[i] != ')') || (brace == '[' && s[i] != ']') || (brace == '{' && s[i] != '}')) return false; braces.pop(); } else if(isSign(i)) { if(isSign(i+1) || isDot(i+1) || isRightBrace(i+1)) return false; } else if(isDot(i)) { if(isSignOrDot(i+1) || isBrace(i+1)) return false; } else if(isDigit(i)) { if(isRightBrace(i+1)) { if(braces.empty()) return false; char brace = braces.top(); if((brace == '(' && s[i+1] != ')') || (brace == '[' && s[i+1] != ']') || (brace == '{' && s[i+1] != '}')) return false; } } } return braces.empty(); }
特别要注意下的就是碰到右括号的状况,除了要判断是不是单独存在的右括号,还有判断是否与前一个左括号匹配.
这是针对单目运算符-的状况,好比(-7),而后把它转化为(0-7):
string getResult(void) { size_t len = s.size(); for(size_t i = 0;i<len;++i) { if(s[i] == '(' && (s[i+1] == '-' || s[i+1] == '+')) s.insert(i+1,"0"); } return s; }
在左小括号后判断是不是-或+,是的话对应位置插入0.
calc辅助类中使用了两个栈,运算符栈与操做数栈.
private: stack<char> operators; stack<double> operands;
其中有两个重要的方法:
bool canCalculate(char sign); void calculate(void);
第一个方法将下一个准备进入的符号做为参数,判断是否能够计算操做数栈的前两个数,若是能够的话,使用第二个函数进行计算.
calculate()会将出栈两个操做数与一个运算符,得出结果后在将其压回操做数栈.
void calculate(void) { double post = popAndGetNum(); char sign = popAndGetSign(); double pre = popAndGetNum(); double result = 0.0; switch (sign) { case '+': result = pre+post; break; case '-': result = pre-post; break; case '*': result = pre*post; break; case '/': if(fabs(post) < 1e-6) { cout<<"Error.Divisor is 0."; exit(EXIT_FAILURE); } else result = pre / post; break; case '^': result = pow(pre,post); break; case '%': result = static_cast<int>(pre) % static_cast<int>(post); break; } push(result); } bool canCalculate(char sign) { if(sign == '(' || sign == '[' || sign == '{' || operators.empty()) return false; char t = getSign(); if(t == '^') return true; switch (t) { case '+': case '-': return sign == '+' || sign == '-'; case '*': case '/': case '%': return sign == '+' || sign == '-' || sign == '*' || sign == '/' || sign == '%'; } return false; }
下面是calc类:
class calc { private: stack<char> operators; stack<double> operands; char getSign(void) { return operators.top(); } double getNum(void) { return operands.top(); } void popSign(void) { operators.pop(); } void popNum(void) { operands.pop(); } double popAndGetNum(void) { double num = getNum(); popNum(); return num; } char popAndGetSign(void) { char sign = getSign(); popSign(); return sign; } public: void push(double num) { operands.push(num); } void push(char sign) { operators.push(sign); } char get(void) { return getSign(); } void pop(void) { popSign(); } double result(void) { return getNum(); } void calculate(void) { double post = popAndGetNum(); char sign = popAndGetSign(); double pre = popAndGetNum(); double result = 0.0; switch (sign) { case '+': result = pre+post; break; case '-': result = pre-post; break; case '*': result = pre*post; break; case '/': if(fabs(post) < 1e-6) { cout<<"Error.Divisor is 0."; exit(EXIT_FAILURE); } else result = pre / post; break; case '^': result = pow(pre,post); break; case '%': result = static_cast<int>(pre) % static_cast<int>(post); break; } push(result); } bool canCalculate(char sign) { if(sign == '(' || sign == '[' || sign == '{' || operators.empty()) return false; char t = getSign(); if(t == '^') return true; switch (t) { case '+': case '-': return sign == '+' || sign == '-'; case '*': case '/': case '%': return sign == '+' || sign == '-' || sign == '*' || sign == '/' || sign == '%'; } return false; } bool empty(void) { return operators.empty(); } };
private封装了一些简单的对两个栈进行操做的工具方法,公有的pop()与get()是对运算符栈进行的操做.由于外部不须要对操做数栈进行操做,由calculate()进行操做,公有的push重载了,能够push到操做数栈或运算符栈.
计算部分在这里直接放在了main中:
int main(void) { check chk; while(!chk.inputAndCheck()) cout<<"Error!Please input again.\n"; string s = chk.getResult(); size_t len = s.size(); calc c; for(size_t i=0;i<len;++i) { if(isdigit(s[i])) { double num; size_t i1 = i+1; while(i1 < len && (isdigit(s[i1]) || s[i1] == '.')) ++i1; istringstream input(s.substr(i,i1)); input>>num; i = i1-1; c.push(num); } else if(s[i] == '}' || s[i] == ']' || s[i] == ')') { char sign; char start = (s[i] == '}' ? '{' : ( s[i] == ']' ? '[' : '(')); while((sign = c.get()) != start) c.calculate(); c.pop(); } else //s[i] is [ ( { + - * / ^ % { while(c.canCalculate(s[i])) c.calculate(); c.push(s[i]); } } while(!c.empty()) c.calculate(); cout<<"result is "<<c.result()<<endl; return 0; }
对表达式的每一个字符逐个处理,如果数字,提取出来并压栈. 如果右括号类,不断从运算符栈中提取直到把这段括号内的表达式计算完成.
不然如果左括号或者是运算符,当能够计算的时候一直计算,提取两个操做数运算并压栈,再把新的运算符压栈.
最后使用result()获取结果.
这里就显示几个很长的例子算了
固然做者测试了不少的例子
6.6/{2.3+34.3*2.22-5%2+22%4*[2+3.4/5-(4.3+3.2*33.3)]+34.3} + 7.8*{2.4-6/6+0-0*[23.4-3.4/6+4*(2.2+3)]}+0 - 0 + 0.0 = 10.8569 3.4 - (+3.34) + 34.3 * (-2) / 3.34 + {[(-3.4)^2/3.4+3.4/3]-3.32+[3*(-3)]} = -28.2656 9^5-34.4^2.3+5%6-34+66%78-78%4 + (-3)*3.4 / {3*(-7)+[3*(-8)+3*(3.4+4.34)/9.3-3.2 + 0.0 - 0]+0.0 - 0}+3.4^4/6.888 = 55683.2
不信的话能够手工计算一下.
这部分把界面部分与表达式处理部分整合起来.
计算表达式的程序叫MyCalc.exe,注意把它放在对应的工程文件夹下面,而后使用QProcess调用.
使用execute执行,表达式先去除全部的空格,而后做为命令行参数传递给计算程序,而后计算程序把计算结果写入到result.txt文件,Qt读取这个文件,若是读到#表示表达式输入错误,不然,则是正确的计算结果.
对于结果由于在计算程序中设置了fixed格式,所以对于
1+2
也会返回
3.000000
这步把多余的0去掉,还要注意小数点的状况.
修改setText的内容,把结果传递过去.
设置fixed格式,不然的话显示的是科学计数法,对小数位数有要求的话能够设置setprecision.
这里出现错误时,输出"#",而后主程序读取到就会提示"表达式错误,请从新输入."
还有除数为0的错误提示,这个要注意一下:
好比输入了一个点,不能继续输入点,输入了一个乘号或者除号不能再继续输入另外一个符号:
把Qt文件夹下的如图所示的bin添加到Path环境变量,
使用windeployqt打包,首先把程序调成release模式,运行一次,生成release的exe,而后把exe复制到一个单独的文件夹,再用命令行进入到这个文件夹,运行
windelpoyqt xxx.exe
这个命令把须要的dll复制到当前所在文件夹.
打开Enigma Virtual Box,选择
第一个选择release的exe,第二个选择打包以后的文件夹,而后选择添加文件,选择递归添加,添加上一步生成的全部文件(夹).
这里选择压缩文件. 而后选择压缩等待完成便可.
点击运行.
大功告成!!
1.github(里面包含完整可执行的单个exe) 注:因为使用了lfs上传大文件,因此clone的时候请使用
git lfs clone
2.码云
1.Qt淡入
2.Qt按键
3.Qt标题栏
4.事件过滤器
5.Qt鼠标事件
6.Qt延时处理
7.Qt文件读写
8.Qt打包成单文件
这个简单的计算器有很大的改进空间,好比能够添加各类"数": 正弦函数,余弦函数,正切函数,反正弦函数,指数函数,对数函数,高阶导数,抽象函数,复合函数.内心没数
等等.另外还能够改进矩形的按钮,能够改为圆角矩形或者椭圆形. 另外,对于阴影的处理能够添加淡入淡出效果.
最后就是磨砂.由于win10的是有磨砂效果的,这个做者还不会.... 最后再上几个图,看看效果(因为动图大小的限制只是简单的表达式...):
但愿大家也有一个属于本身的计算器!