Kalkulator w C#

Kalkulator w C#

Autor: Marcin Hałaczkiewicz

Opublikowano: 5/10/2006, 12:00 AM

Liczba odsłon: 198830

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:

@STRONA@

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;

Te zmienne i stałe przydadzą nam się w dalszej części programu. Teraz dodajemy dwie funkcje (w klasie frmKalkulator):

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.

Jak wykorzystać Copilot w codziennej pracy? Kurs w przedsprzedaży
Jak wykorzystać Copilot w codziennej pracy? Kurs w przedsprzedaży

Wydarzenia