WPF Programátorská kalkulačka - Dokončenie
V prvom diele som začal C# časť kódu programátorské kalkulačku v C# .NET WPF. Dnes ju dokončíme.
Aj keď by išiel priebežne sa zadávanými hodnotami zobrazovať aj výsledok, nepripadá mi to príliš dobré riešenie a preto sa výsledok ukáže až po stlačení Enter na klávesnici. Tiež je potrebné kalkulačku zresetovat. K tomu rovnako ako u iných aplikácií využijeme kláves esc
Každý stlačenie klávesy, rovnako ako pohyb myši, alebo zmenu stavu tlačidlá na nej, vyvolá príslušnú udalosť. My budeme obsluhovať udalosť "KeyDown". V properties hlavného okna si v eventoch dvojklikom na "KeyDown" definujeme metódu pre obsluhu stlačili klávesy:
private void Window_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { try { vypocty.vypocet(); showResult(); } catch(Exception ex) { MessageBox.Show(ex.Message); } } else if (e.Key == Key.Escape) { clearAll(); } }
Myslím, že z kódu je jasné, ako zistíme ktorá klávesa bola stisla a čo sa ďalej vykoná. Pri nevydarenom výpočte zachytíme výnimku a aktivujeme MessageBox s príslušnou hláškou.
Ďalej sa dostávame k ovládanie samotného okna:
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { while (Mouse.LeftButton == MouseButtonState.Pressed) { DragMove(); } } //obsluha buttonu private void buttonMinimize_Click(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } private void buttonClose_Click(object sender, RoutedEventArgs e) { this.Close(); } private void buttonHelp_Click(object sender, RoutedEventArgs e) { WindowHelp windowHelp = new WindowHelp(); windowHelp.Show(); }
Z kódu je opäť nad slnko jasné čo metódy udalostí "Click" jednotlivých button ov robia. Snáď len k presunu okna dodám, že je realizovaný metódou DragMove ();, Teda pri stlačení a držaní ľavého tlačidla myši.
Nakoniec ostáva výber typu a operátora z ComboBox. Metódy si opäť pripravíme v properties dvojklikom na event SelectionChanged. Na výbere typu nie je nič zvláštneho, len sa tu prvýkrát stretneme so slovníkom:
private void ComboBoxTyp_SelectionChanged(object sender, SelectionChangedEventArgs e) { prevody.Typ = ComboBoxTyp.SelectedItem.ToString(); vypocty.PocetBytu = prevody.dateTypeDictionary[prevody.Typ]; }
Výber operátora je trochu zložitejšia, pretože sa niektoré logické operácie vykonávajú len na jednom operandu a tak je nutné zakázať / povoliť prístup k tomu druhému.
Triedy pre spracovanie dát
Trieda Vypocet
Je veľmi jednoduchá. Má päť vlastností a jedinú metódu, ktorá vykonáva samotné aritmetické a logické operácie:
class Vypocet { public dynamic A_val { get; set; } public dynamic B_val { get; set; } public dynamic Result { get; set; } public string Operator; public int PocetBytu { get; set;} public Vypocet() { A_val = 0; B_val = 0; Result = 0; } internal void vypocet() { switch (Operator) { case "+": Result = A_val + B_val; break; case "-": Result = A_val - B_val; break; case "x": Result = A_val * B_val; break; case "/": Result = A_val / B_val; break; case "%": Result = A_val % B_val; break; case "&": Result = A_val & B_val; break; case "|": Result = A_val | B_val; break; case "^": Result = A_val ^ B_val; break; case "~": Result = ~A_val; break; case "<<": Result = A_val << B_val; break; case ">>": Result = A_val >> B_val; break; } double x=Math.Pow(2,(double)(PocetBytu*8))-1; if(Result> x) { throw new Exception("při operaci došlo k přetečení"); } } }
Čo je na nej však zaujímavého je použitie dátového typu dynamic. Je to taký univerzálny typ, ktorý môže zastúpiť ktorýkoľvek, v našom prípade hodnotový typ. Dynamic sa navyše muža za behu programu meniť, čo nie je u bežných typov možné.
Musel som sa k nemu uchýliť z dôvodu splnenia poslednej podmienky z úvodného článku - výber typu, na ktorom budú operácie vykonané. Avšak ako vždy je jeho univerzálnosť je vykúpená určitým nepohodlím, nefunguje na ňom intelisense, takže čo si s ním môžete dovoliť je potreba hľadať v dokumentácii a navyše na ňom nejdú použiť operátory ako sizeof, alebo kontrola pretečeniu za pomoci checked, čo by sa tu zrovna hodilo. Preto som musel pristúpiť k pomerne krkolomnému zistenie max. Hodnoty aktuálneho typu (dynamic.MaxValue celkom logicky nefunguje) a porovnania s výsledkom operácie s prípadným vyhodením výnimky pri pretečeniu:
double x=Math.Pow(2,(double)(PocetBytu*8))-1; if(Result> x) { throw new Exception("při operaci došlo k přetečení"); }
Trieda Prevod
Je už trochu zložitejšie a to hlavne kvôli požiadavke delenie hex a bin hodnôt na jednotlivé bajty pomocou medzier v texte. Najskôr sa musí typ rozsekať na jednotlivé bajty tým, že definujeme pole bajtov o veľkosti daného typu. Ako ju ale zistiť, keď sizeof (dynamic) nefunguje? Nezistíme ju nijako, musí sa definovať na tvrdo v nejakej tabuľke a na tento účel bude najvhodnejší Slovník, kde budú názvy typov slúžiť ako kľúče a hodnoty budú príslušné počty bajtov. Navyše zabijeme viac múch jednou ranou, pretože kolekciu kľúčov odovzdáme ComboBox pre výber typu ako "ItemsSource" a hodnoty použijeme v triede Vypocet pre kontrolu pretečeniu.
Ako prvý je verejná metóda, ktorá je volaná z metódy textBox_TextChanged a rozlišuje pomocou v parametri odovzdaného tabindex v ktorom textbox zrovna meníme hodnotu. Podľa toho odovzdá parametre vo forme textu z textbox a číselnej sústavy (2,10,16) privátne metóde, ktorá vykoná samotný prevod na zvolený typ. Ešte pred tým prejde foreach text a odstráni z neho medzery.
Try-catch blokom je ošetrené zadanie väčšieho čísla, než je max. Hodnota daného typu:
private dynamic prevedTyp(string s,int soustava) { dynamic ret=null; //odstraneni mezer z textu foreach (char c in s) { if (c == ' ') s = s.Remove(s.IndexOf(" "), 1); } try { switch (Typ) { case "byte": ret = Convert.ToByte(s, soustava); break; case "sbyte": ret = Convert.ToSByte(s, soustava); break; case "Int16": ret = Convert.ToInt16(s, soustava); break; case "Int32": ret = Convert.ToInt32(s, soustava); break; case "UInt16": ret = Convert.ToUInt16(s, soustava); break; case "UInt32": ret = Convert.ToUInt32(s, soustava); break; } } catch { throw new Exception("číslo je větší než max. hodnota zvoleného typu"); } return ret; }
Ďalšia je metóda pre konverziu opačným smerom, teda z dátového typu na reťazec znakov. V nej najprv zistíme zo slovníka, koľko má bytov a podľa toho definujeme pole bajtov:
int pocetBytu = dateTypeDictionary[Typ]; byte[] tmp = new byte[pocetBytu];
Bajty jeden po druhom prevedieme na znaky a spojíme do jedného reťazca. Súčasne doplníme medzerami pomocou metódy PadLeft (8, '0'), (prvý parameter určuje koľko miest sa bude dopĺňať a druhý čím sa bude dopĺňať). Nuly dopĺňame preto, aby bol vidieť celý bajt, je to prehľadnejšie.
for (int i = 0; i < tmp.Length; i++) { tmp[i] = (byte)val; val >>= 8; } for (int j = tmp.Length - 1; j >= 0; j--) { if (tmp[j] != 0) { if(soustava==2) { s += Convert.ToString(tmp[j], soustava).PadLeft(8, '0'); if(j!=0)s+=" "; } else if (soustava == 16) { s += string.Format(string.Format("{0:X}", tmp[j])).PadLeft(2, '0'); if (j != 0) s += " "; } } }
Možno si poviete, prečo som použil pre bin. číslo triedu Convert a pre hex. číslo string.Format. Po prvé pre ilustráciu, že máme viac možností, ale hlavne preto, že trieda Convert prevádza znaky af na malé písmená a ja mám radšej veľká. A formátovanie stringu mi dovolí oboje.
Projekt obsahuje ešte "Help" okno, ktoré je ale primitívne (obsahuje jeden textbox), že ho tu nemá cenu popisovať. Pre záujemcov prikladám celý projekt vo VS2010.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 266x (591.6 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#