WPF Programátorská kaklulačka - Design a CodeBehind

C# .NET WPF WPF Programátorská kaklulačka - Design a CodeBehind

Tento tutoriál je zaměřený na praktické využití nabytých znalostí z C#.NET a WPF ze zdejších tutoriálů. Přidává též pár zatím nezmiňovaných věcí a ne zcela typických řešení, popřípadě alternativ k podobným postupům v jiných tutoriálech, jako například užití "univerzálního" typu dynamic.

Není zaměřený na XAML jako takový, ten je popsán v předchozích dílech dost obšírně. Na konci budete mít funkční aplikaci, která najde uplatnění hlavně u programátorů jednočipových MCU.

Zadání

Zadáním je programátorská kalkulačka, která by měla splňovat tyto podmínky, které mi chybí u tradičních kalkulaček:

  1. Stále on top - Neschovávat se při každé volbě jiného okna, což mně u běžných kalkulaček slušně řečeno "irituje" :)
  2. Zabírat co nejméně místa - Což vychází z první podmínky, aby pokud možno nepřekážela. Na většině IDE (editorů) se nějaké to místo, kam ji strčit, najde.
  3. Zobrazovat každou hodnotu v decimálním, hexadecimáním a binárním tvaru najednou
  4. Přístup k jakékoli pozici v zadaném čísle pro jeho změnu
  5. V binární a hexa sekci oddělené jednotlivé bajty, doplněné o patřičný počet nul (0F,0001001) - pro přehlednost
  6. Použití nejběžnějších aritmetických a logických operací
  7. Výběr datového typu, na kterém se budou operace provádět
Programátorská kalkulačka

Design

Je minimalistiký a ryze účelový, vše je podřízeno podmínce co nejmenšího okna.

Aplikace se skládá z devíti textBoxů, tří buttonů, tří textBlocků, comboBoxu a jedné čáry, kterou reprezentuje rectangle a toť vše. Oproti standardním kalkulačkám je to vlastně jen jejich displej, vše potřebné je přeci na klávesnici, tak na co tam cpát spoustu tlačítek?

Všechny prvky mají svůj chlívek v gridu a jejich layout je vztažený právě k němu. To a zároveň skutečnost, že téměř všechny prvky včetně okna a gridu mají své property šířky a výšky až na výjimky nastaveny na auto, zajistí, že okno roste rovnoměrně s textem v boxech. TextBoxy mají jen nastavenou minimální šířku. Bez textu jsou moc úzké a kromě toho, že to špatně vypadá, se do nich taky špatně strefuje :)

Velikost okna je tak daná hlavně velikostí fontu zadaného v properties okna. Slepýši, jako jsem já, si ho můžou zvětšit.

Další úsporu místa zajistí to, že se zbavíme rámečku okna. Toho docílíme nastavením property WindowStyle na none nebo v XAMLU přidáním do řádku okna WindowStyle="None". Tím se ale připravíme o jakoukoli manipulaci s oknem. Když ho jednou otevřeme, tak už se ho nezbavíme. Leda ve správci úloh - co s tím? Musíme ovládací prvky přenést na okno samotné - ale o tom až v dalším díle.

Implementace

První podmínku zajistíme následovně: v XAMLu do kořenového elementu okna zadáme

Topmost="True"

Nebo prostě zaškrtneme checkBox u příslušné vlastnosti v properties okna. Odstraněním rámečku se zdá zbytečné zadávat oknu titulek a ikonu, ale protože se zobrazují na taksbaru, tak je tam dáme.

Pro zobrazení výsledku bych normálně použil textBlocky, ale protože mají trochu jiný design a text v okénku zobrazují trochu posunutý proti textBoxu, tak jsem z důvodu sjednocení vzhledu využil textBoxy a vlastnost "focusable" nastavil na false, aby se do nich nedalo psát.

Na spodní straně jsou umístěny tři buttony - dva pro minimize a close a jeden, který bude otvírat okno s nápovědou. Buttony nemají žádný text, jen jsou jako background použité ikony stažené z iconfinderu a jako jediné elementy mají dané pevné rozměry.

Code Behind

Deklarace a konstruktor

Deklarujeme/de­finujeme kolekci znaků zastupujících jednotlivé operátory. Tu pak předáme jako ItemsSource comboBoxu pro výběr operace a zřídíme instance tříd zajišťujících samotný převod a výpočet:

Prevod prevody;
Vypocet vypocty;

//itemy comboBoxu
List<string> operatory = new List<string>() { "+", "-", "x", "/", "%", "&", "|", "^", "~", "<<", ">>" };

//konstruktor
public MainWindow()
{
    InitializeComponent();
    vypocty = new Vypocet();
    prevody = new Prevod(vypocty);
    comboBoxOperator.ItemsSource = operatory;
    comboBoxOperator.FontSize = 10;
    ComboBoxTyp.ItemsSource = prevody.dateTypeDictionary.Keys;
    ComboBoxTyp.FontSize = 10;
}

Začnu asi nejsložitější metodou:

private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox tb = sender as TextBox;

    string s = tb.Text;
    tb.CaretIndex = tb.Text.Length;
    if (tb.IsFocused)
    {
        if (s != "")
        {
            try
            {
                prevody.prevod(s, tb.TabIndex);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            vypocty.Result = 0;
            showResult();
            wiewDec();
            wiewHex();
            wiewBin();
        }
        else
        {
            if (tb.TabIndex % 2 == 0) clearA();
            else clearB();
            clearRes();
        }

    }
}

Je to metoda pro obsluhu události textChanged a bude společná pro všechny textBoxy, protože jinak by tam bylo šest metod, které dělají to samé - volají metodu pro převod řetězce na hodnotu proměnné.

Na druhou stranu ale musíme vědět, se kterým textBoxem právě pracujeme, takže si na začátku zřídíme jeho instanci. Jelikož se ale sender prezentuje jako object, musíme ho na textBox přetypovat:

TextBox tb = sender as TextBox;

Pro identifikaci použijeme jeho tabIndex. Zadáme v properties nebo XAMLU, jak je komu libo, hodnoty od 0 do 5 pro jednotlivé textBoxy podle následujícího schématu:

Kalkulačka v C# .NET WPF

A protože v této metodě měníme texty všech šesti operandových textBoxů, tak se také šestkrát zavolá. Jde vlastně o rekurzivní volání metody (volá i když nepřímo sama sebe), což by nadělalo pěkný zmatek a tak musíme zajistit, aby se její kód provedl jen na textBoxu, se kterým pracujeme. To zjistíme vlastností:

if (tb.IsFocused)

Po testu, jestli není řetězec prázdný (po mazání backspacem) se provede samotný převod, který je v bloku try-catch. To proto, aby se někdo nepokoušel převádět třeba 354 na byte. Dále zobrazíme hodnotu ve všech požadovaných tvarech (dec,hex,bin)

if (s != "")
{
    try
    {
        prevody.prevod(s, tb.TabIndex);
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    vypocty.Result = 0;
    showResult();
    wiewDec();
    wiewHex();
    wiewBin();
}

Pokud je prázdný, vymaže se příslušný řádek s operandy. Který řádek to je se zjistí právě z tabIndexu. Horní řádek má sudé a spodní liché, což zjistíme jednoduše operátorem modulo (liché budou mít zbytek).

else
{
    if (tb.TabIndex % 2 == 0) clearA();
    else clearB();
    clearRes();
}

Dále budeme pokračovat metodami pro obsluhou vstupů z HID - tedy myši a klávesnice. Vzniká tu nutnost filtrovat znaky přicházející z klávesnice a to nikoliv na událost textChanged, to už je pozdě. Musíme filtrovat v metodě obsluhy události textBox_Previ­ewTextInput, tedy dokud není znak do textu zapracován.

U decimálního zobrazení je to jednoduché. Potřebujeme vědět, jestli se jedná o číslici. To se dozvíme za pomoci :

char.IsDigit

U hex hodnoty potřebujeme přidat ještě znaky A-F (a-f). K tomu slouží zřízená "tabulka" string hexValue = "0123456789AB­CDEFabcdef";

Stejně tak to bude fungovat i pro binární hodnoty.

// znaky 0,1,2,3,4,5,6,7,8,9,0
private void textBoxDec_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    if (!char.IsDigit(e.Text, 0)) e.Handled = true;
}

// znaky 0,1,2,3,4,5,6,7,8,9,0,A,B,C,D,E,F
private void textBoxHex_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    string hexValue = "0123456789ABCDEFabcdef";
    if (!hexValue.Contains(e.Text)) e.Handled = true;

}

// znaky 0,1
private void textBoxBin_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    string binValue = "01";
    if (!binValue.Contains(e.Text)) e.Handled = true;
}

V dalším díle dokončíme Code Behind a podíváme se na třídy provádějící samotné převody a operace. Zdrojový kód včetně ikonek je přiložen.


 

Stáhnout

Staženo 122x (38.1 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 (2 hlasů) :
55555


 


Miniatura
Předchozí článek
3. Díl WPF - Pozadí elementů
Miniatura
Všechny články v sekci
Okenní aplikace v C# .NET WPF

 

 

Komentáře
Zobrazit starší komentáře (4)

Avatar
beibovaneki
Redaktor
Avatar
beibovaneki:

Ahoj, jsem záčátečník a potřeboval bych poradit.

1. Proč je v XAML kódu hlavního okna tohle:
mc:Ignorable="d" xmlns:d="http­://schemas.mi­crosoft.com/ex­pression/blen­d/2008" xmlns:mc="htt­p://schemas.o­penxmlformats­.org/markup-compatibility/2006" d:DesignWidth="136"
Zkoušel jsem to vymazat a nic to nedělá.

2. Je nějaký rozdíl když ikonu přidám takhle: Icon="Images/bin­.ico"
a ne takhle: Icon="/DecHex­BinCalc;compo­nent/Images/bin­.ico"?

3. K čemu přesně slouží

tb.CaretIndex = tb.Text.Length;

Je to v metodě při události textChanged

4. Nejsem si úplně jistý co dělá

e.Handled = true

Znamená to, že je o událost postaráno a že už nemusí nic dělat?

 
Odpovědět 27.12.2014 17:38
Avatar
BlugW
Redaktor
Avatar
Odpovídá na beibovaneki
BlugW:

Začni http://www.itnetwork.cz/csharp/zaklady
a než se dobereš sem tak pochopíš vše na co se ptáš.

Odpovědět 27.12.2014 17:45
Pořiď si mac na www.appletrh.cz. Novinky a zajímavosti ze světa Apple na https://www.applemagazin.eu
Avatar
beibovaneki
Redaktor
Avatar
Odpovídá na BlugW
beibovaneki:

To všechno jsem už pročetl a s tímhle jsem se ještě nesetkal, proto se taky ptám.

 
Odpovědět 27.12.2014 19:01
Avatar
beibovaneki
Redaktor
Avatar
Odpovídá na BlugW
beibovaneki:

To všechno jsem už pročetl a s tímhle jsem se ještě nesetkal, proto se taky ptám.

 
Odpovědět 27.12.2014 19:01
Avatar
beibovaneki
Redaktor
Avatar
beibovaneki:

Tak nic, už jsem si to zjistil jinak.

 
Odpovědět 28.12.2014 14:42
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na beibovaneki
ostrozan:

Právě jsem dokončoval odpověď, ale byls rychlejší

Nicméně - třeba by to zajímalo i někoho jiného a ty ses s tím cos našel nepochlubil.

Tady jsou mé odpovědi:

  1. - ten kód si generuje VS a o jeho ne-/potřebnosti se tu kdesi vedla diskuse -pro tebe je důležité, že si ho vůbec nemusíš všímat
  2. - není
  3. carret je kursor v textu - CaretIndex je tedy jeho index(poloha) v textu a konečně příkaz tb.CaretIndex = tb.Text.Length; ho umísťuje na konec textu - jinak je automaticky na začátku, což je nepraktické
  4. to je trochu složitější - ale zjednodušeně to zarazí událost,aby nepostupovala dál - zde zamezí tomu, aby se "nevhodný" znak fyzicky objevil v TextBlocku

jinak problematika směrovaných událostí (Routed Events) je slušně popsaná tady: http://msdn.microsoft.com/…c785480.aspx#…

 
Odpovědět  +1 28.12.2014 16:01
Avatar
beibovaneki
Redaktor
Avatar
Odpovídá na ostrozan
beibovaneki:

Moc děkuju.

A ještě k té první otázce. Stejný kód se automaticky přidá u vytváření vlastní kontrolky, ale ne v hlavním okně a to mě mate.

Je tu ještě jedna možnost, že to ve starší verzi VS bylo jinak, ale to já nevím.

 
Odpovědět 28.12.2014 18:16
Avatar
ostrozan
Redaktor
Avatar
ostrozan:

To je tím že User Control je malá WPF aplikace - prakticky nezávislá na hlavním okně - můžeš ji pak používat v i v jiných projektech, ale jak už jsem psal - to co automaticky generuje VS prostě ber, že to tak je :)

 
Odpovědět 28.12.2014 18:55
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na beibovaneki
ostrozan:

Ještě k tomu e.Handled = true -
zapomněl jsem uvést, žes měl v podtatě pravdu - je to jakési potvrzení, že událost byla "v pořádku" obsloužena - já to ale používám proto, abych zamezil obsluze události tam kde by měla skutečně proběhnout

 
Odpovědět 28.12.2014 19:08
Avatar
beibovaneki
Redaktor
Avatar
Odpovídá na ostrozan
beibovaneki:

Dobře, ještě jednou děkuju.

 
Odpovědět 28.12.2014 19: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 10 zpráv z 14. Zobrazit vše