UWAGA! Promocja dla firm - MICROSOFT OFFICE 365  na 12 miesiecy ZA DARMO! Tylko na CentrumXP.pl!
Wielka promocja Office 365 na CentrumXP.pl!
Do góry Skomentuj

Kalkulator w C#

Kalkulator w C#

Autor: Marcin Hałaczkiewicz Opublikowano: 10 maja 2006 Odsłon: 130 932

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;

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.

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.

Zobacz również

Komentarze

Szczerze mówiąć, to chyba najgorszy tutek z tutejszych. Po pierwsze - mam wrażenie, że to można zrobić znacznie prościej i szybciej (program straaasznie "muli" u mnie), autor dziwnie kręci, prawie nie komentuje kodu, stosuje jakieś praktyki i nie wyjaśnia ich zasadności, itp. p.s. konia z wozem temu kto mi znajdzie kontrolkę EditBox w VC#2005EE :> Ja to zrobiłem na TextBox'ie.
m010ch, 16 czerwca 2006, 23:24
Szczerze, to mi też się ten artykuł nie podoba. Ale był to pierwszy program jaki kiedykolwiek napisalłem w C# i w .NET, więc proszę o wyrozumiałość. btw oczywiście, że chodzi o TextBox a nie EditBox
Marcin Hałaczkiewicz, 17 czerwca 2006, 12:30
C# dopiero poznaje i miedzy innymi z kursu tutaj zamieszczonego ale ten przyklad nie jest jakis super... Uruchamiajac cmdWynik_Click powodujemy cmdKropka.Enabled = false; jednak pozniej juz tego nie zmieniamy wiec powtorne urzycie cmdKropka nie jest mozliwe. Nalezaloby dopisac do pokazznaki() linijke: cmdKropka.Enabled = true; Autor poswiecil sporo estetyce i niezawodnosci ale pomimo ze tylko kilka razy uruchomilem pisana aplikacje to wylapalem cos takiego: po wprowadzeniu liczby, wybraniu dzialania, wprowadzeniu liczby i wybraniu "=" mozemy ponownie wprowadzic liczbe jednak nie mozemy wybrac dzialania tzn teoretycznie tak ale aplikacja wykona "=" z dzialaniem zapamietanym przy pierwszym dzialaniu. Ja widze przynajmniej dwa sposoby rozwiazania tego malego niedopatrzenia.
Adam, 27 sierpnia 2006, 23:20
Mam jeszcze jeden ciekawy blad: wybieramy cyfre/liczbe i zmieniamy znak na minus, dopisujemy kolejna cyfre i znowu zmieniamy znak, tym razem na plus i tutaj nagle wyswietlana liczba w txtWynik zmniejsza sie o (wybrana cyfra/liczba w pierwszej fazie * 2) a przeciez zmieniamy tylko znak. I do tego na razie nie doszedlem "dlaczego tak" ale widac ze zaczynam zabawe z C# :)
Adam, 27 sierpnia 2006, 23:38
na dziendobry: metoda Focus() nie mówi o tym czy kontrolka jest wybrana tylko ustawia focus na kontrolkę - do tego pierwszego służy property TextBox.Focused. po drugie zamiast wydziwiać z textboxem (enabled=false, backcolor=white) proponuje używać locked=true...
vandalizmo, 30 sierpnia 2006, 16:24
a mi by sie nie chcialo 10x klikac na kazdym przycisku i wpisywac w funkcji cyfry(1), cyfry(2) ..., skoro mozna zaznaczyc wszystkie 10 guzikow, wybrac im jedna metode a w niej napisac tylko jedna linijke: cyfry( (sender as przycisk).tag); i w kazdym guziku w zmiennej tag poustawiac odpowiednie wartosci, czyli 1,2,3 itd- o wiele elastyczniejsze rozwiazanie
bct, 23 listopada 2007, 12:58
Nie umiem wrobić tego kalkulatora niestety głupio mi jest nie dawajcie tego do pobrania proszę ?
Kalkulator, 3 maja 2008, 14:28
C# dopiero poznaje i miedzy innymi z kursu tutaj zamieszczonego ale ten przyklad nie jest jakis super... Uruchamiajac cmdWynik_Click powodujemy cmdKropka.Enabled = false; jednak pozniej juz tego nie zmieniamy wiec powtorne urzycie cmdKropka nie jest mozliwe. Nalezaloby dopisac do pokazznaki() linijke: cmdKropka.Enabled = true; Autor poswiecil sporo estetyce i niezawodnosci ale pomimo ze tylko kilka razy uruchomilem pisana aplikacje to wylapalem cos takiego: po wprowadzeniu liczby, wybraniu dzialania, wprowadzeniu liczby i wybraniu "=" mozemy ponownie wprowadzic liczbe jednak nie mozemy wybrac dzialania tzn teoretycznie tak ale aplikacja wykona "=" z dzialaniem zapamietanym przy pierwszym dzialaniu. Ja widze przynajmniej dwa sposoby rozwiazania tego malego niedopatrzenia
H, 3 maja 2008, 15:12
Zamiast używania const int PLUS = 1; itd. wystarczy użyć enum"a...
Acca, 23 kwietnia 2011, 11:16

Dodaj swój komentarz

Zasady publikacji komentarzyZasady publikacji komentarzy

Redakcja CentrumXP.pl nie odpowiada za treść komentarzy publikowanych na stronach Portalu
i zastrzega sobie prawo do usuwania wypowiedzi, które:

  • zawierają słowa wulgarne, obraźliwe, prowokujące i inne naruszające dobre obyczaje;
  • są jedynie próbami reklamowania stron internetowych (spamowanie poprzez umieszczanie linków);
  • przyczyniają się do złamania prawa bądź warunków licencyjnych oprogramowania (cracki, seriale, torrenty itp.);
  • zawierają dane osobowe, teleadresowe, adresy mailowe lub numery GG;
  • merytorycznie nie wnoszą nic do dyskusji lub nie mają związku z tematem komentowanego newsa, artykułu bądź pliku.

Autor:

Komentarz:

Dodaj komentarz