WPF Programátorská kalkulačka - Dokončení

C# .NET WPF WPF Programátorská kalkulačka - Dokončení

V prvním díle jsem začal C# část kódu programátorské kalkulačku v C# .NET WPF. Dnes ji dokončíme.

I když by šel průběžně se zadávanými hodnotami zobrazovat i výsledek, nepřipadá mi to příliš dobré řešení a proto se výsledek ukáže až po stisku enteru na klávesnici. Také je potřeba kalkulačku zresetovat. K tomu stejně jako u jiných aplikací využijeme klávesu esc

Každý stisk klávesy, stejně jako pohyb myši, nebo změnu stavu tlačítka na ní, vyvolá příslušnou událost. My budeme obsluhovat událost "KeyDown". V properties hlavního okna si v eventech dvojklikem na "KeyDown" definujeme metodu pro obsluhu stisklé 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é, jak zjistíme která klávesa byla stisklá a co se dál provede. Při nezdařeném výpočtu zachytíme výjimku a aktivujeme MessageBox s příslušnou hláškou.

Dále se dostáváme k ovládání 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ět nad slunce jasné co metody událostí "Click" jednotlivých buttonů dělají. Snad jen k přesunu okna dodám, že je realizovaný metodou DragMove();, tedy při stisku a držení levého tlačítka myši.

Nakonec zbývá výběr typu a operátoru z comboBoxů. Metody si opět připravíme v properties dvojklikem na event SelectionChanged. Na výběru typu není nic zvláštního, jen se tu poprvé setkáme se slovníkem:

private void ComboBoxTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    prevody.Typ = ComboBoxTyp.SelectedItem.ToString();
    vypocty.PocetBytu = prevody.dateTypeDictionary[prevody.Typ];
}

Výběr operátoru je trochu složitější, protože se některé logické operace provádějí jen na jednom operandu a tak je nutné zakázat/povolit přístup k tomu druhému.

Třídy pro zpracování dat

Třída Vypocet

Je velmi jednoduchá. Má pět vlastností a jedinou metodu, která provádí samotné aritmetické a logické operace:

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í");
        }
    }
}

Co je na ní ovšem zajímavého je použití datového typu dynamic. Je to takový universální typ, který může zastoupit kterýkoliv, v našem případě hodnotový typ. Dynamic se navíc muže za běhu programu měnit, což není u běžných typů možné.

Musel jsem se k němu uchýlit z důvodu splnění poslední podmínky z úvodního článku - výběr typu, na kterém budou operace prováděny. Ovšem jako vždy je jeho univerzálnost je vykoupena určitým nepohodlím, nefunguje na něm intelisense, takže co si s ním můžete dovolit je potřeba hledat v dokumentaci a navíc na něm nejdou použít operátory jako sizeof, nebo kontrola přetečení za pomoci checked, což by se tu zrovna hodilo. Proto jsem musel přistoupit k poměrně krkolomnému zjištění max. hodnoty aktuálního typu (dynamic.MaxValue celkem logicky nefunguje) a porovnání s výsledkem operace s případným vyhozením výjimky při přetečení:

double x=Math.Pow(2,(double)(PocetBytu*8))-1;
if(Result> x)
{
   throw new Exception("při operaci došlo k přetečení");
}

Třída Prevod

Je už trochu složitější a to hlavně kvůli požadavku dělení hex a bin hodnot na jednotlivé bajty pomocí mezer v textu. Nejdříve se musí typ rozsekat na jednotlivé bajty tím, že definujeme pole bajtů o velikosti daného typu. Jak ji ale zjistit, když sizeof(dynamic) nefunguje? Nezjistíme ji nijak, musí se definovat na tvrdo v nějaké tabulce a k tomuto účelu bude nejvhodnější Slovník, kde budou názvy typů sloužit jako klíče a hodnoty budou příslušné počty bajtů. Navíc zabijeme více much jednou ranou, protože kolekci klíčů předáme comboBoxu pro výběr typu jako "ItemsSource" a hodnoty použijeme ve třídě Vypocet pro kontrolu přetečení.

Jako první je veřejná metoda, která je volaná z metody textBox_TextChanged a rozlišuje pomocí v parametru předaného tabIndexu ve kterém textBoxu zrovna měníme hodnotu. Podle toho předá parametry ve formě textu z textBoxu a číselné soustavy (2,10,16) privátní metodě, která provede samotný převod na zvolený typ. Ještě před tím projede foreachem text a odstraní z něj mezery.

Try-catch blokem je ošetřeno zadání většího čí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;
}

Další je metoda pro konverzi opačným směrem, tedy z datového typu na řetězec znaků. V ní nejdříve zjistíme ze slovníku, kolik má bytů a podle toho definujeme pole bajtů:

int pocetBytu = dateTypeDictionary[Typ];
byte[] tmp = new byte[pocetBytu];

Bajty jeden po druhém převedeme na znaky a spojíme do jednoho řetězce. Současně doplníme mezerami pomocí metody PadLeft(8, '0'), (první parametr určuje kolik míst se bude doplňovat a druhý čím se bude doplňovat). Nuly doplňujeme proto, aby byl vidět celý bajt, je to přehlednější.

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žná si řeknete, proč jsem použil pro bin. číslo třídu Convert a pro hex. číslo string.Format. Za prvé pro ilustraci, že máme více možností, ale hlavně proto, že třída Convert převádí znaky a-f na malá písmena a já mám raději velká. A formátování stringu mi dovolí obojí.

Projekt obsahuje ještě "Help" okno, které je ale primitivní (obsahuje jeden textBox), že ho zde nemá cenu popisovat. Pro zájemce přikládám celý projekt ve VS2010.


 

Stáhnout

Staženo 183x (591.6 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal ostrozan
Avatar
Autor se věnuje především embedded systémům (mikrokontrolery) a vývoji jejich GUI v C# WPF , Java.

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.54.54.54.54.5


 



 

 

Komentáře

Avatar
Michal Žůrek (misaz):

nechci být rýpavý, ale na můj vkus to řešíš složitě a vytváříš kolo. .NET framewrk umí převádět čísla mezi soustavami...

cokoliv z X do desítkové

System.Convert.ToInt32("1010", 2); // 10 - převe 1010 z 2 soustavy do 10 soustavy

cokoliv z desítkové do X soustavy

System.Convert.ToString(10, 2); // 1010 - převede 10 z desítkové soustavy do dvojkové
:)
Odpovědět 18.7.2014 21:31
Nesnáším {}, proto se jim vyhýbám.
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
ostrozan:

teď nějak nechápu o čem je řeč?!

v převodu řetězce na příslušný typ používám přesně to co doporučuješ

a ani v převodu čísla na text bych "nevymýšlel kolo", kdybych nechtěl odděleně zobrazovat jednotlivé bajty - FA 21 D2 3A hex a 11111010 00100001 11010010 00111010 bin je prostě přehlednější než FA21D23A a
1111101000100­0011101001000111010 - proto ta složitější konstrukce, ale o tom je zmínka v textu

ale pokud máš lepší nápad, jak to zrealizovat - sem s ním :)

Editováno 18.7.2014 22:34
 
Odpovědět 18.7.2014 22:33
Avatar
lastp
Redaktor
Avatar
lastp:

Ve funkci format je chyba. Odstraňuje nuly. Například když je DEC 256, pak zobrazí HEX 01. Správně je 01 00.

 
Odpovědět 22.7.2014 9:58
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na lastp
ostrozan:

Dík za upozornění - možnosti jsou dvě, buď zrušit podmínku

if (tmp[j] != 0)

pak se budou zobrazovat všechny bajty - 00000000 00000000 00000000 01110001

ale záměr byl , aby se nezobrazovala ta řada zbytečných nul před prvním bajtem s hodnotou > 0

druhá možnost vypadá takto:

internal string format(dynamic val, int soustava)
       {

           int pocetBytu = dateTypeDictionary[Typ];
           byte[] tmp = new byte[pocetBytu];
           string s = null;
           bool nuly = false;  //zmena - pridano
           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||nuly==true)//zmena - pridano ||nuly==true
              {
                  nuly = true;  //zmena-pridano
                  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 += " ";
                  }
              }
           }

           return s;
       }
   }

kód bude editován v článku

 
Odpovědět  +1 25.7.2014 7:21
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.