W tym artykule zajmiemy się pisaniem prostego kalkulatora w języku C#. Programik będzie wyposażony jedynie w podstawowe opcje, gdyż celem jest pokazanie jak pisać aplikację tego typu, a nie wyposażyć ją we wszelkie matematyczne funkcje.
Chcielibyśmy, aby nasze liczydło umiało dodawać, odejmować, mnożyć i dzielić. Interfejs oczywiście ma być graficzny. Okno programu będzie posiadało przyciski odpowiadające cyfrom, działaniom, zmianie znaku, kropce dziesiętnej, znakowi równa się oraz kontrolkę Anuluj. Tak więc odpalamy Visual C# Express Edition, z menu File wybieramy New Project, klikamy na Windows Application i w polu Name wpisujemy nazwę programu, czyli Kalkulator.
Przyciski kalkulatora nie będą standardowymi przyciskami z okna Toolbox,
ponieważ nie chcemy, aby po kliknięciu były one wybrane, czyli metoda Focus()
ma zawsze zwracać wartość false.
Funkcja Focus() mówi nam czy kontrolka w danym momencie jest aktywna. Na
przykład pole TextBox jest wybrane, gdy w jego wnętrzu miga kursor. Gdy aktywny
przycisk jest podświetlony, naciśnięcie klawisza enter spowoduje to samo co kliknięcie
na kontrolkę. Właśnie tego musimy uniknąć - dlaczego - o tym później.
Tak więc chcemy mieć własny typ przycisków. Wybierzmy z menu Project opcję
AddClass i w polu Name podajmy jej nazwę - Przycisk.
Przechodzimy teraz do pliku z nowo utworzoną klasą (otworzy się automatycznie).
U góry dopiszmy using System.Windows.Forms;
Wyprowadzamy naszą klasę z klasy Button. Następnie tworzymy konstruktor naszej klasy
i wpisujemy w nim SetStyle(ControlStyles.Selectable, false);
Ta linijka kodu spowoduje, że przycisków nie będzie można wybrać, czyli otrzymamy
to o co nam chodzi. Tak powinien w całości wyglądać kod nowej klasy:
using
System.Windows.Forms;
namespace Kalkulator
{
class
Przycisk : Button
{
public
Przycisk()
{
SetStyle(ControlStyles.Selectable,
false);
}
}
}
Po zapisaniu pliku możemy go zamknąć, nie będziemy go już modyfikować. Przechodzimy
do widoku formy (Form1.cs) i zaglądamy do zakładki ToolBox. Jeśli nie jest
widoczna nigdzie z boku ekranu to wybieramy menu View a następnie ToolBox.
Pod nagłówkiem Kalkulator Components znajdziemy stworzoną przez nas kontrolkę.
Tworzymy 18 przycisków. 10 z nich nazywamy cmd0, cmd1, cmd2...cmd9, a w polu
Text wpisujemy odpowiednią cyfrę. Wszystkie te właściwości znajdziemy w oknie
Properties. Kolejnym przyciskom nadajemy nazwy cmdSuma, cmdRoznica, cmdIloczyn,
cmdIloraz cmdKropka, cmdZnak, cmdWynik, cmdAnuluj. Wyświetlamy na nich odpowiedni
symbol. Dla cmdZnak może to być +/-, a dla cmdAnuluj po prostu Anuluj. Dodatkowo
dla przycisków działań oraz cmdWynik i cmdZnak ustawiamy właściwość Enabled
na false. Robimy to po to, by zaraz po uruchomieniu programu były one nieaktywne.
Uaktywnimy je dopiero po wybraniu jakiejś cyfry. Wszystko po to, aby program nie
pozwalał użytkownikowi na wciskanie przycisków nie po kolei. Najpierw ma być naciśnięta
cyfra/y, później działanie, znowu cyfra/y i kolejne działanie lub =.
Teraz dodajemy kontrolkę TextBox, nadajemy jej nazwę txtWynik, kasujemy zawartość
pola Text i zmieniamy wartość Enabled na false, dodatkowo ustawiamy
BackColor na White oraz RightToLeft na Yes. W ten szybki
sposób otrzymaliśmy gotowe pole do wyświetlania wyniku. Ma być ono nieaktywne (właściwość
Enabled), aby nie można było nic do niego wpisać ani go wybrać. Jednak nieaktywność
tej kontrolki powoduje, że jej tło staje się szare. Dlatego właśnie wymuszamy biały
kolor po prostu ustawiając go we właściwości BackColor. Jeśli chodzi o przestawianie
RightToLeft na Yes - powoduje to wyświetlanie tekstu "od prawej do lewej". Zmieniliśmy
to tylko ze względów estetycznych.
Chcielibyśmy jeszcze, aby na pasku tytułu okna z aplikacją była wyświetlana jej
nazwa. W tym celu klikamy w puste miejsce formy i w jej właściwościach ustawiamy
pole Text na Kalkulator. Teraz układamy elementy naszego programu
w sensowny sposób. Może to wyglądać np. tak:
Czas zająć się właściwym pisaniem programu. W oknie Solution Explorer klikamy prawym przyciskiem na Form1.cs i wybieramy View Code. W klasie frmKalkulator dopisujemy następujące linijki:
const
int
PLUS = 1;
const int
MINUS = 2;
const int
RAZY = 3;
const int
PODZIELIC = 4;
double a = 0;
double b = 0;
int dzialanie = 0;
bool kropka = false;
// kropka dziesietna
int ilepoprzecinku = 0;
// ilosc cyfr po przecinku gdy kropka jest wcisnieta
int znak = PLUS; // plus
lub minus
bool czykasowacekran =
false;
public
void
pokazznaki()
{
cmdSuma.Enabled = true;
cmdRoznica.Enabled = true;
cmdIloczyn.Enabled = true;
cmdIloraz.Enabled = true;
if (dzialanie != 0) cmdWynik.Enabled
= true;
cmdZnak.Enabled = true;
}
public void
ukryjznaki()
{
cmdSuma.Enabled = false;
cmdRoznica.Enabled = false;
cmdIloczyn.Enabled = false;
cmdIloraz.Enabled = false;
cmdWynik.Enabled = false;
cmdZnak.Enabled = false;
}
Powyższe linijki kodu mają za zadanie aktywowanie lub dezaktywowanie przycisków
działań, przycisku = oraz +/-. Jest to nam potrzebne do uniknięcia błędów wynikających
z naciskania klawiszy nie po kolei.
Potrzebna jest nam jeszcze funkcja, którą będziemy wywoływać po naciśnięciu klawisza
z cyfrą. Znak ten przekażemy do funkcji w argumencie.
public
void
cyfry(int i)
{
pokazznaki();
if (dzialanie != 0)
// czyli wcisnelismy cyfre po raz pierwszy po uruchomieniu
programu
{
if (!kropka)
a = 10 * a + i; // np. 10 * 15 + 3 = 150 + 3 = 153
else
{
if (i != 0) a = a + i / (Math.Pow(10, ilepoprzecinku++));
// Math.Pow - potega
else ++ilepoprzecinku;
}
}
else
{
if (!kropka)
b = 10 * b + i;
else
{
if (i != 0) b = b + i / (Math.Pow(10, ilepoprzecinku++));
// np. 0.6 + 3 / 10^2 = 0.6 + 0.03 = 0.63
else ++ilepoprzecinku;
}
}
if (czykasowacekran)
{
txtWynik.Text =
"";
czykasowacekran =
false;
znak = PLUS;
}
txtWynik.Text += i.ToString();
}
Omawiany tutaj kod będzie odpowiednio zwiększał zmienną, w której przechowywana
jest aktualnie wpisywana liczba. Przy wprowadzaniu pierwszej wartości będzie modyfikowana
zmienna b, przy każdej kolejnej liczbie - zmienna a. Funkcja ta również wyświetla
aktualną wartość w polu txtWynik. Dba ona również o kosmetykę i niezawodność programu.
Po wciśnięciu cyfry uaktywnią nam się znaki działań oraz = i +/- (wpisaliśmy liczbę,
więc możemy już liczyć), a także pole wyświetlania zostanie wyczyszczone jeśli zaistnieje
taka potrzeba (tzn. gdy po wpisaniu pewnej liczby nacisnęliśmy np. + i zaczynamy
wpisywać kolejną liczbę, wtedy ta poprzednia znika). Przyda nam się również taka
funkcja:
public
double
dzialaj(double x, double
y, int dz)
{
ukryjznaki();
double wynik = 0;
if (dzialanie != 0)
{
switch
(dzialanie)
{
case PLUS: wynik = y + x;
break;
case MINUS: wynik = y - x;
break;
case RAZY: wynik = y * x;
break;
case PODZIELIC: wynik = y / x;
break;
}
txtWynik.Text = wynik.ToString();
a = 0;
}
else wynik = y;
// jak wcisnelismy znak np. + po raz pierwszy
dzialanie = dz;
czykasowacekran = true;
cmdKropka.Enabled = true;
ilepoprzecinku = 0;
return wynik;
}
Funkcja ta będzie używana przy naciskaniu przycisków działań. Najpierw wykonuje ona bieżące działanie (jeśli jakieś jest) na argumentach x i y, a następnie ustawia działanie na takie, jakie przesłano w argumencie dz. Oczywiście podobnie jak wszystkie funkcje dba również o estetykę i niezawodność programu.
@STRONA@Teraz dodamy obsługę zdarzenia kliknięcia w przycisk z cyfrą. W tym celu przechodzimy do zakładki z widokiem graficznym formy. Wybieramy przycisk 1. W oknie Properties klikamy żółtą ikonkę w kształcie błyskawicy. Zobaczymy zakładkę Events, w której znajdują się wszystkie możliwe zdarzenia dla danej kontrolki. Znajdujemy pole Click i dwa razy na nim klikamy (to samo można uzyskać klikając dwa razy na przycisku, gdyż zdarzenie Click jest akcją domyślną).
Otworzy nam się edytor z już utworzoną funkcją. W jej wnętrzu wpisujemy cyfry(1);. Podobnie postępujemy z pozostałymi przyciskami cyfr zmieniając odpowiednio argument funkcji cyfry(...). Właśnie po to napisaliśmy wyżej omówiony kod, aby nie powielać tego samego ciągu znaków. Zmieniamy jedynie argument, by poinformować funkcję, którą cyfrę wciśnięto. Teraz oprogramujemy kliknięcie kropki dziesiętnej. Klikamy dwa razy na odpowiadający jej przycisk i wpisujemy kod:
if
(czykasowacekran)
{
txtWynik.Text = "";
czykasowacekran = false;
znak = PLUS;
}
if (txtWynik.Text ==
"") txtWynik.Text = "0";
txtWynik.Text += ",";
cmdKropka.Enabled = false;
kropka = true;
ilepoprzecinku = 1;
Kod ten po naciśnięciu kontrolki ustawia wartość kropka na
true (informuje nas ona o tym, że kropka jest wciśnięta i że wpisujemy liczby po
przecinku) i ustawia ilość miejsc po przecinku na jeden. Zmienna ilepoprzecinku
mówi nam która cyfra po przecinku będzie aktualnie wpisywana.
Teraz zajmiemy się działaniami. Klikamy dwa razy na znaku + i wpisujemy kod:
b = dzialaj(a, b, PLUS);. Spowoduje on wykonanie
działania podanego w ostatnim argumencie na liczbach a i b (b dzialanie a), jego
wynik zostanie podstawiony pod b. Podobnie postępujemy z pozostałymi działaniami
zmieniając odpowiednio ostatni argument funkcji dzialaj(...) na MINUS, RAZY, PODZIELIC.
Odnosimy się w ten sposób do funkcji napisanej poprzednio. Teraz podwójne kliknijmy
na przycisk +/- i wpiszmy:
if
(znak == PLUS) znak = MINUS;
else znak = PLUS;
if (dzialanie != 0) { a = -a; txtWynik.Text = a.ToString();
}
else { b = -b; txtWynik.Text
= b.ToString(); }
Kod ten ustawia odpowiednio zmienną znak. Gdy jest ona równa
true to wpisywana liczba jest dodatnia, gdy false - ujemna. Oprócz tego wyświetla
zaktualizowaną liczbę w kontrolce TextBox.
Pozostały nam jedynie przyciski = i Anuluj. Najpierw =. W znany sposób tworzymy
obsługę zdarzenia kliknięcia na przycisku i dodajemy w nim kod:
b = dzialaj(a, b, dzialanie);
txtWynik.Text = b.ToString();
cmdKropka.Enabled = true;
pokazznaki();
cmdWynik.Enabled = false;
cmdKropka.Enabled = false;
dzialanie = PLUS;
a = 0;
Spowoduje on wykonanie działanie na liczbach a i b, podstawienie
wyniku do b oraz wyświetlenie go. Standardowo - również estetyka i niezawodność
(ustawienia Enabled). Po wszystkim zmienna a jest ustawiana na 0, a działanie na
PLUS. Teraz gdy naciśniemy przycisk jakiegoś działania to uruchomi się funkcja
działaj, która wykona następujące działanie: aktualny wynik + 0, czyli nic się nie
zmieni, a o to nam chodzi. Gdybyśmy tego nie zrobili i wciskali po kolei np. 50
+ 10 =, a następnie kliknęli jakieś działanie, to wyszłyby nam "kosmosy".
Teraz kod przycisku Anuluj. Funkcja obsługująca kliknięcie będzie wyglądać następująco:
a = b = 0;
dzialanie = 0;
kropka = false;
ilepoprzecinku = 0;
txtWynik.Text = "";
znak = PLUS;
czykasowacekran = false;
ukryjznaki();
Zeruje ona wszystkie flagi naszego programu do wartości początkowych
oraz ukrywa przyciski, których naciśnięcie teraz nie byłoby zgodne z wymaganą kolejnością.
Nasz program jest już prawie gotowy. Prawie, bo chcielibyśmy jeszcze by można było
obsługiwać go za pomocą klawiatury. Zrobimy to tworząc obsługę zdarzenia KeyPressed
dla jednej formy naszej aplikacji. Właśnie po to na początku tego tekstu tworzyliśmy
specjalną klasę przycisków. Gdyby były one wybieralne to musielibyśmy obsłużyć zdarzenie
KeyPressed dla każdego z nich. A tak wystarczy, że zrobimy to jedynie dla
formy, bo dzięki jednej linijce kodu w konstruktorze klasy Przycisk, żaden
przycisk nie może być aktywny, więc cały czas forma jest wybrana i może obsługiwać
swoje zdarzenia. Po utworzeniu funkcji dla KeyPressed wpisujemy w niej:
switch
(e.KeyChar)
{
case
'+': if (cmdSuma.Enabled)
cmdSuma_Click(sender, e); return;
case
'-': if (cmdRoznica.Enabled)
cmdRoznica_Click(sender, e); return;
case
'*': if (cmdIloczyn.Enabled)
cmdIloczyn_Click(sender, e); return;
case
'/': if (cmdIloraz.Enabled)
cmdIloraz_Click(sender, e); return;
case (char)13:
if (cmdWynik.Enabled) cmdWynik_Click(sender, e);
return; // enter
case (char)27:
cmdAnuluj_Click(sender, e); return;
// escape
case
'.':
case
',': if (cmdKropka.Enabled)
cmdKropka_Click(sender, e); return;
}
int n = (int)e.KeyChar;
if (n < 48 || n > 57)
return; // jak nie nacisnelismy cyfry to wychodzimy
z funkcji
n = int.Parse( e.KeyChar.ToString() );
cyfry(n);
return;
Najpierw sprawdzamy czy został wciśnięty jakiś znak działania
lub enter (odpowiadający =), escape (Anuluj), kropka / przecinek. Jeśli żaden z
tych klawiszy nie został wciśnięty, sprawdzamy czy nie była to cyfra. Robimy to
sprawdzając kod ASCII poprzez rzutowanie typu char na int. Cyfry znajdują się w
przedziale 48...57. Jeśli to również nie cyfra to wychodzimy z funkcji i nic się
nie dzieje, w przeciwnym wypadku wywołujemy funkcję cyfry(...) z argumentem zależnym
od wciśniętej cyfry (tak samo jakbyśmy kliknęli odpowiedni przycisk).
W tej chwili nasz program jest ukończony, w pełni funkcjonalny i uodporniony na
błędy (dzięki znikającym przyciskom). Możemy go obsługiwać zarówno za pomocą myszki
jak i klawiatury. Tak więc oddajmy się liczeniu ;)
Stworzony tutaj program wraz z plikami źródłowymi możemy pobrać stąd.