C, PHP, VB, .NET

Дневникът на Филип Петров


* Графичен калкулатор с NetBeans

Публикувано на 17 ноември 2015 в раздел УКИ.

Тук ще разгледаме възможността бързо и лесно да създаваме графични приложения чрез средата за програмиране NetBeans. Идеята при използването на такива среди е много лесно и бързо да "подреждаме" графичните контроли и да пишем минимално количество код. Това е особено подходящо за училищни курсове по програмиране, където се цели бързо и лесно достигане до практически значими резултати с минимални усилия.

След като стартирате NetBeans, отидете на File > New Project. Изберете Java Application, след което продължете с Next:

NetBeans - стартиране на проект

На следващият екран се уверете, че НЕ е отбелязано "Create Main Class". Напишете име на проекта - например OutCalculator - и натиснете Finish:

2

С това създадохте празен проект. Неговата файлова структура се изобразява в лявата част на екрана, а в центъра е работното поле, в което ще се отварят файловете. Сега натиснете с десния бутон на мишката върху името на проекта и изберете New > JFrame Form:

Добавяне на JFrame

Задайте име на класа и име на пакета, в който ще се намира, след което натиснете Finish:

Диалогов прозорец за добавяне на JFrame

На следващата картинка са показани 5-те основни области на екрана:

Основен екран на NetBeans

Те са както следва:

  1. Йерархична файлова структура на проекта;
  2. Списък с обекти за бърз достъп;
  3. Основен екран за дизайн на Swing приложението;
  4. Списък с графични контроли, които можем да преместваме в 3;
  5. Стандартен изход (системни съобщения от компилатора и System.out).

Ще забележите, че в 3 може да сменяте режима между Design и Source изглед. Засега ще се фокусираме само върху дизайнера. Започнете да изтегляте контроли от 4 в 3 и ги преоразмерявайте (чрез натискане в левия ъгъл и влачене). Първо ни трябват два JLabel в горния ляв и горния десен ъгъл:

6

Под тях поставете тестово поле (JTextField):

7

Накрая добавете поредица от бутони и ги преоразмерете така, че да получите следния екран (за удобство когато добавите един ред с бутони можете после да ги маркирате, копирате и вмъквате):

8

Сега е добре да променим текста на графичните контроли, за да може да отразява това, което търсим - калкулатор. Натискайте с десния бутон на мишката последователно върху всяка контрола, след което избирайте "Edit text":

9

В крайния си вариант екрана трябва да изглежда по следния начин (JLabel1 и JLabel2 не са изтрити, а просто текста им е сменен с обикновен интервал, с което са станали невидими на екрана):

10

Ще забележите, че при всяко добавяне на нов графичен контрол в поле 2 (списъка с обекти) се появява нов запис. Те приемат имена по подразбиране (JButton1, JButton2, и т.н.). Добре е след като сме направили основния екран да ги променим. Маркирайте обектите един по един и натискайте бутон F2 (rename), след което променяйте имената им с някои по-удобни за вас. Например:

11

 

Това ще са имената на обектите и именно с тези имена ще се обръщаме към тях. Сега ще направим още няколко промени по дизайна. Маркирайте текстовото поле (currentNumber) с десен бутон и изберете Properties. Това са основните свойства на графичния компонент - всеки компонент има свои, като различните компоненти могат да имат различни. Ние искаме текста да е малко по-голям и да е подравнен вдясно. Направете промените както е показано на следващата картинка:

12

Аналогично извикайте Properties на основния JFrame (изберете с десния бутон Properties) от списъка с обекти. Задайте му Title като OurCalc:

13

И по-надолу премахнете опцията Resizable (така прозореца на основната програма ще бъде забранено да се разпъва):

14

Аналогично на currentNumber (текстовото поле) променете Editable да бъде изключено:

15

Дизайнът на нашата програма е готов. Сега трябва да добавим функционалностите на всеки един от контролите ни. С десен бутон натиснете върху бутон 1 и изберете Events > Action > actionPerformed. Това ще създаде метод, който ще бъде извикван при натискане на бутона:

16

Ще видите, че автоматично ще бъдете прехвърлени в Source изгледа, при това автоматично при метода, който току що сте създали:

17

Сега трябва да направим основното действие на бутон 1. То естествено е да добави цифрата "1" в края на съществуващото число. Да, но ако съществуващото число е 0 (каквото е по подразбиране), би се получило "01", а ние искаме да има само 1. Затова при натискане на бутон бихме изкали първо да се премахнат водещите нули и след това да се добавя числото. Ще направим това чрез следния код, който трябва да добавите на мястото на коментара в новосъздадения метод:

currentNumber.setText(currentNumber.getText().replaceFirst("^0*[^.]", "")+"1");

Да го разгледаме внимателно. На currentNumber извикваме метод "setText" (промени текста). В скобите имаме извикване на текущия текст на същия обект (метод getText()), на резултата от който прилагаме метод "replaceFirst" (замени първите букви). Като първи параметър на replaceFirst сме подали регулярен израз (^ - започва с, 0 - цифрата 0, * - нула или повече пъти), а като втори параметър подаваме празен текст. Към така получения резултат (оригиналния текст с премахнати нули в началото) добавяме текст с цифрата 1. Този краен резултат е и входен параметър на метод SetText.

Нека видим какво сме свършили. От главното меню на NetBeans изберете Run > Rub Project:

18

NetBeans ще се "оплаче", че няма избран Main метод за проекта, т.е. входна точка на програмата. Ще ви предложи следния екран за избор:

19

Натиснете OK и вашия калкулатор ще се появи на екрана:

20

Ще видите, че бутон 1 работи както беше предвидено. Сега загасете програмата и пристъпете към работа с бутони 2, 3, 4, 5, 6, 7, 8, 9 и 0. Тяхното действие е същото като на бутон 1, но само ще трябва да смените цифрата в скобичките:

21

Ето и кода на направеното дотук в текстови вид:

    private void btn1ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"1");
    }                                    

    private void btn2ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"2");
    }                                    

    private void btn3ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"3");
    }                                    

    private void btn4ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"4");
    }                                    

    private void btn5ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"5");
    }                                    

    private void btn6ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"6");
    }                                    

    private void btn7ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"7");
    }                                    

    private void btn8ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"8");
    }                                    

    private void btn9ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"9");
    }                                    

    private void btn0ActionPerformed(java.awt.event.ActionEvent evt) {                                     
        currentNumber.setText(currentNumber.getText().replaceFirst("^0*", "")+"0");
    }

Бутон "." не е толкова тривиален. При него не се притесняваме от водещи 0-ли (можем да имаме число 0.123 например). Тази точка обаче може да присъства максимум веднъж в числото. Затова ще направим следното - първо ще потърсим дали в съществуващия текст има точка и ще конкатенираме нашата точка само ако такава няма. Кодът за това ще бъде следния:

 private void btnDotActionPerformed(java.awt.event.ActionEvent evt) { 
    if(!currentNumber.getText().contains(".")){
       currentNumber.setText(currentNumber.getText()+".");
    }
 }

Удивителната е отрицание. Тоест ако в оператор if текущото число няма "." само тогава ще бъде добавена точка в края му. Пуснете програмата и пробвайте. Първоначално изглежда, че всичко работи добре - ако натискате много пъти върху точката, няма да се появява нова:

22

Ще установите обаче, че има и неприятна грешка (бъг). Ако натиснете 0. и след това кое да е друго число, нулата ще изчезне. Това разбира се е така, защото метода на цифровите бутони изтрива водещите нули. Ще се получи нещо, което не желаем:

23

Начините за справяне с този и други подобни проблеми са най-разнообразни. Ние ще подходим с "груба сила". В методите на бутоните от 0 до 9 ще добавим следния ред код:

 if(currentNumber.getText().startsWith(".")){
    currentNumber.setText("0"+currentNumber.getText());
 }

Със сигурност това не е най-добрия начин за справяне с проблема - първо махаме нулите, после добавяме нула. В случая този начин и бърз и лесен - би бил подходящ за демонстрация пред ученици, които не са се занимавали с програмиране до този момент. Ако изпробваме това, ще видим, че работи:

24

Бутонът "C" е много лесен. Неговата функция е да изтрие всичко и да го върне в първоначалния му вид:

 private void btnCActionPerformed(java.awt.event.ActionEvent evt) { 
    currentNumber.setText("0");
    savedNumber.setText(" ");
    operation.setText(" ");
 }

Бутон +/- също е що годе тривиален. При него правим проверка - ако текущото число започва с "-", трябва да го премахнем. Ако няма "-", трябва да му добавим. За удобство в началото ще си запишем текущото число в локална променлива:

private void btnInvertActionPerformed(java.awt.event.ActionEvent evt) { 
   String cn = currentNumber.getText();
   if(cn.startsWith("-")){
      cn = cn.substring(1, cn.length());
   }
   еlse{
      cn = "-"+cn;
   }
   currentNumber.setText(cn);
}

Сега искаме да извършим действията с бутоните с операциите. При натискане на +,-, * и / искаме текущото число да се съхрани в левия label (savedNumber), а операцията в десния (operation). След това текущото число (currentNumber) трябва да се нулира. Създайте събитие за натискане на бутон + и добавете следния код

 private void btnPlusActionPerformed(java.awt.event.ActionEvent evt) {
    // Ако вече има въведено число, само променяме операцията 
    if(!savedNumber.getText().equals(" ")){
       operation.setText("+");
       return;
    }
    // Във временна променлива съхраняваме текущото число
    String sn = currentNumber.getText();
    // Ако в числото има десетична точка
    if(sn.contains(".")){
       // Премахваме всички нули в края му
       sn = sn.replaceAll("0*$", "");
       // Ако след премахването на нулите завършва на точка
       if(sn.endsWith(".")){ 
          // Премахваме точката
          sn = sn.substring(0,sn.length()-1);
       } 
    } 
    savedNumber.setText(sn); 
    operation.setText("+");
    currentNumber.setText("0");
 }

Виждате, че извършихме в началото няколко допълнителни операции. Ако числото има десетична точка и завършва на една или повече нули, премахваме тези нули (това е така, защото числото 123.45600000 всъщност ще си е 123.456). След като сме премахнали нулите, проверяваме дали случайно числото не завършва на точка (например 123.00000 да се е превърнало в 123.). Ако това е така, премахваме точката.

Аналогично трябва да направим същите операции за бутоните изваждане, умножение и деление - при тях единствено се различава знака:

private void btnMinusActionPerformed(java.awt.event.ActionEvent evt) {                                         
    if(!savedNumber.getText().equals(" ")){
       operation.setText("-");
       return;
    }
    String sn = currentNumber.getText();
    if(sn.contains(".")){
        sn = sn.replaceAll("0*$", "");
        if(sn.endsWith(".")){   
            sn = sn.substring(0,sn.length()-1);
        }   
    }
    savedNumber.setText(sn);
    operation.setText("-");
    currentNumber.setText("0");
}                                        

private void btnMultiplyActionPerformed(java.awt.event.ActionEvent evt) {                                       
    if(!savedNumber.getText().equals(" ")){
       operation.setText("*");
       return;
    }
    String sn = currentNumber.getText();
    if(sn.contains(".")){
        sn = sn.replaceAll("0*$", "");
        if(sn.endsWith(".")){   
            sn = sn.substring(0,sn.length()-1);
        }   
    }
    savedNumber.setText(sn);
    operation.setText("*");
    currentNumber.setText("0");
}                                           

private void btnDivideActionPerformed(java.awt.event.ActionEvent evt) {                                         
    if(!savedNumber.getText().equals(" ")){
       operation.setText("/");
       return;
    }
    String sn = currentNumber.getText();
    if(sn.contains(".")){
        sn = sn.replaceAll("0*$", "");
        if(sn.endsWith(".")){   
            sn = sn.substring(0,sn.length()-1);
        }   
   }
   savedNumber.setText(sn);
   operation.setText("/");
   currentNumber.setText("0");
}

Остава последния и най-труден бутон - извършване на операцията (=). При него логиката ще е сложна.

  1. Първо трябва да проверим дали има записано число. Ако няма, не трябва да правим нищо - Край. Ако има, продължаваме в 2;
  2. Превръщаме съхраненото число и текущото число от текст в числов тип double. Преминаваме на 3;
  3. Проверяваме каква е операцията и я извършваме. Продължаваме на 4;
  4. Записваме получения резултат в текущото число, а старото съхранено число и операцията ги премахваме. Край.

Освен това ще се погрижим ако крайния резултат е точен, т.е. число завършващо на .0, да премахнем окончанието .0.

private void btnCalculateActionPerformed(java.awt.event.ActionEvent evt) {                         
    if(savedNumber.getText().equals(" ")){
        return;
    }
    double sn = Double.parseDouble(savedNumber.getText());
    double cn = Double.parseDouble(currentNumber.getText());
    double result = 0.0;
    switch(operation.getText()){
        case "+":
           result = sn+cn;
           break;
        case "-":
           result = sn-cn;
           break;
        case "*":
           result = sn*cn;
           break;
        case "/":
           result = sn/cn;
           break;
    }
    savedNumber.setText(" ");
    operation.setText(" ");
    currentNumber.setText(""+result);
    if(currentNumber.getText().endsWith(".0")){
       String tmp = currentNumber.getText();
       currentNumber.setText(tmp.substring(0, tmp.length()-2));
    }
}

Дотук направения калкулатор е почти готов. Има още няколко неща, за довършване, едното от които обаче е много съществено - какво правим при деление на нула? Проверете, ще се получи следното:

25

В Java e прието число разделено на нула да се приема за безкрайност. При това текстът Infinity превърнат в число е валидна конструкция. По същият начин ако разделите 0/0 ще получите резултат NaN (not a number - не е число). Всяка операция с NaN ще дава NaN. Тоест бихме могли да оставим нещата и по този начин.

По лошото в случая е, че в нашата програма можем да продължим да работим и да добавяме разни числа в края на този текст:

26

Това вече категорично не е валидно число и ако се опитате да извършите операция с него, програмата ще се счупи. Опитайте.

За да се справим с този проблем трябва да забраним на цифрите да се добавят ако currentNumber е със стойност Infinity или NaN. Добавете следния код в началото на метода на всеки един от бутоните от 0 до 9:

if(    currentNumber.getText().endsWith("Infinity") 
    || currentNumber.getText().endsWith("NaN")){
   return;
}

Защо endsWith, а не equals? Причината е, че можем спокойно с бутон +/- да правим -Infinity или -NaN. Така си спестяваме още две проверки.

С това нашият калкулатор е готов и е относително стабилен. Вече можем да го разпространим. Натиснете Clean and Build бутона:

27

След това отидете с Windows Explorer в директорията, в която сте записали проекта. В моя случай това е C:\Users\Philip\Documents\NetBeansProjects\OurCalculator. В поддиректория "dist" ще видите файл OurCalculator.jar. Именно това е крайния вариант на вашата програма. Може да го копирате и разпространявате. Ако го отворите на компютър с инсталиран JRE, програмата ще се стартира.

Допълнителна задача 1: Създаденият има проблем с прекалено дългите числа - ако започнете да въвеждате число с 20 или повечи цифри, то ще излезе от екрана, а при операция етикетите на savedNumber и operation ще се припокрият.

Допълнителна задача 2: Отново при смятане с прекалено дълги числа, те ще се преобразуват като експоненти - например може да започнат да се появяват числа като 4.99E14 и други подобни. Програмата ни няма да се чупи, но ще е възможно да продължим да пишем с цифри в края на тези числа. Опитайте се да направите така, че да не може.

Допълнителна задача 3: Потърсете други пропуски в създадената програма и предложете решения.

 

 



Добави коментар

Адресът на електронната поща няма да се публикува


*