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

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

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

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 148x (38.1 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal ostrozan
Avatar
Jak se ti líbí článek?
2 hlasů
Autor se věnuje především embedded systémům (mikrokontrolery) a vývoji jejich GUI v C# WPF , Java.
Miniatura
Předchozí článek
3. Díl WPF - Pozadí elementů
Miniatura
Všechny články v sekci
Okenní aplikace v C# .NET WPF
Aktivity (1)

 

 

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

Avatar
ostrozan
Redaktor
Avatar
ostrozan:23. května 18:44

nemyslel jsem napsat v properties Auto do kolonky, ale poklepat na tačítko vedle - Set to Auto/Nastavit na Automaticky - podle jazykové mutace VS

 
Odpovědět 23. května 18:44
Avatar
Odpovídá na ostrozan
Andy Scheuchzer:23. května 18:45

To jsem taky dělal.

Odpovědět 23. května 18:45
Od ASM úroveň jazyků pouze klesá…
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na Andy Scheuchzer
ostrozan:23. května 18:54

V tom případě ti nepomůžu - zkus možná založit vlákno na toto téma, třeba se s tím problémem někdo setkal i v jiných aplikacích.

Možná ještě sem dej (nebo pošli na PM) tvůj kompletni xaml kód - zkusím to u sebe

 
Odpovědět 23. května 18:54
Avatar
Andy Scheuchzer:23. května 18:55
<Window x:Class="DecHexBinCalculator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DecHexBinCalculator"
        mc:Ignorable="d"
        Title="DecHexBinCalculator" Height="160" WindowStyle="None" Background="Black" Foreground="#ffd0d0d0" FontSize="11" Topmost="True" WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown" MouseLeftButtonDown="Window_MouseLeftButtonDown">
        <Grid Margin="5">
                <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <TextBlock Text="DEC"/>
                <TextBlock Text="HEX" Grid.Column="1"/>
                <TextBlock Text="BIN" Grid.Column="2"/>
                <TextBox Grid.Row="1" Grid.Column="0" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="0" TextChanged="TextBox_TextChanged" PreviewTextInput="DecTbx_PreviewTextInput"/>
                <TextBox Grid.Row="1" Grid.Column="1" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="2" TextChanged="TextBox_TextChanged" PreviewTextInput="HexTbx_PreviewTextInput"/>
                <TextBox Grid.Row="1" Grid.Column="2" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="4" TextChanged="TextBox_TextChanged" PreviewTextInput="BinTbx_PreviewTextInput"/>
                <StackPanel Grid.Row="2" Grid.ColumnSpan="3" Margin="0,5" Orientation="Horizontal" HorizontalAlignment="Center">
                        <ComboBox Margin="10,0" Background="#ff3f3f3f" Foreground="#d0d0d0" HorizontalAlignment="Center" SelectedIndex="0">
                                <ComboBoxItem Content="+"/>
                                <ComboBoxItem Content="-"/>
                                <ComboBoxItem Content="*"/>
                                <ComboBoxItem Content="/"/>
                                <ComboBoxItem Content="^"/>
                                <ComboBoxItem Content="√"/>
                                <ComboBoxItem Content="%"/>
                                <ComboBoxItem Content="&amp;"/>
                                <ComboBoxItem Content="|"/>
                                <ComboBoxItem Content="~"/>
                                <ComboBoxItem Content="&lt;&lt;"/>
                                <ComboBoxItem Content="&gt;&gt;"/>
                        </ComboBox>
                        <ComboBox Margin="10,0" Background="#ff3f3f3f" Foreground="#d0d0d0" HorizontalAlignment="Center" SelectedIndex="0">
                                <ComboBoxItem Content="Byte"/>
                                <ComboBoxItem Content="SByte"/>
                                <ComboBoxItem Content="Ushort"/>
                                <ComboBoxItem Content="Short"/>
                                <ComboBoxItem Content="UInt"/>
                                <ComboBoxItem Content="Int"/>
                                <ComboBoxItem Content="ULong"/>
                                <ComboBoxItem Content="Long"/>
                        </ComboBox>
                </StackPanel>
                <TextBox Grid.Row="3" Grid.Column="0" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="1" TextChanged="TextBox_TextChanged" PreviewTextInput="DecTbx_PreviewTextInput"/>
                <TextBox Grid.Row="3" Grid.Column="1" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="3" TextChanged="TextBox_TextChanged" PreviewTextInput="HexTbx_PreviewTextInput"/>
                <TextBox Grid.Row="3" Grid.Column="2" MinWidth="30" Text="0" Background="#ff3f3f3f" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" TabIndex="5" TextChanged="TextBox_TextChanged" PreviewTextInput="BinTbx_PreviewTextInput"/>
                <Rectangle Fill="#4f4f4f" Margin="5" Grid.Row="4" Grid.ColumnSpan="3" Height="5"/>
                <TextBox Grid.Row="5" Grid.Column="0" MinWidth="30" Text="0" Background="#ff202020" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" Focusable="False"/>
                <TextBox Grid.Row="5" Grid.Column="1" MinWidth="30" Text="0" Background="#ff202020" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" Focusable="False"/>
                <TextBox Grid.Row="5" Grid.Column="2" MinWidth="30" Text="0" Background="#ff202020" Foreground="#ffd0d0d0" TextAlignment="Right" Margin="5,0" Focusable="False"/>
                <Button Name="MinimizeButton" Content="____" Grid.Row="6" Height="20" Width="20" Margin="0,5" Click="MinimizeButton_Click"/>
                <Button Name="HelpButton" Content="?" Grid.Row="6" Grid.Column="1" Height="20" Width="20" Margin="0,5" Click="HelpButton_Click"/>
                <Button Name="CloseButton" Content="X" Grid.Row="6"  Grid.Column="2" Height="20" Width="20" Margin="0,5" Click="CloseButton_Click"/>
        </Grid>
</Window>

A co myslíš tím PM? Sorry, vážně moc neovládám zkratky.

Odpovědět 23. května 18:55
Od ASM úroveň jazyků pouze klesá…
Avatar
ostrozan
Redaktor
Avatar
ostrozan:23. května 19:38

PM - personal message - osobní zprávy - když klikneš na můj profil , tak tam někde bude "odeslat zprávu"

koukám, žes dělal více změn, tak ještě pošli MainWindow.xaml.cs - nejde mi to zkompilovat

 
Odpovědět 23. května 19:38
Avatar
Andy Scheuchzer:24. května 7:40
namespace DecHexBinCalculator {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window {
                public MainWindow() {
                        InitializeComponent();
                }
                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, (byte)tb.TabIndex); } catch (Exception ex) { MessageBox.Show(ex.Message); }
                                }
                                /*Vypocty.Result = 0;
                                ShowResult();
                                ViewDec();
                                ViewHex();
                                ViewBin();
                        } else {
                                if (tb.TabIndex % 2 == 0) ClearA();
                                else ClearB();
                                ClearRes();*/
                        }
                }
                private void DecTbx_PreviewTextInput(object sender, TextCompositionEventArgs e) {
                        if (!char.IsDigit(e.Text, 0)) e.Handled = true;
                }
                private void HexTbx_PreviewTextInput(object sender, TextCompositionEventArgs e) {
                        string hexValue = "0123456789ABCDEFabcdef";
                        if (!hexValue.Contains(e.Text)) e.Handled = true;
                }
                private void BinTbx_PreviewTextInput(object sender, TextCompositionEventArgs e) {
                        string binValue = "01";
                        if (!binValue.Contains(e.Text)) e.Handled = true;
                }
                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();
                        }*/
                }
                private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
                {
                        while (Mouse.LeftButton == MouseButtonState.Pressed) DragMove();
                }
                private void MinimizeButton_Click(object sender, RoutedEventArgs e) {
                        this.WindowState = WindowState.Minimized;
                }
                private void HelpButton_Click(object sender, RoutedEventArgs e) {
                        //WindowHelp.Show();
                }
                private void CloseButton_Click(object sender, RoutedEventArgs e) {
                        this.Close();
                }
        }
}

Ještě to není dodělané, ale to vidíš sám. A mohl bys mi ještě vysvětlit ty operátory od &?

Odpovědět 24. května 7:40
Od ASM úroveň jazyků pouze klesá…
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na Andy Scheuchzer
ostrozan:24. května 15:00

Takže - problém se šířkou okna vyřešíš nastavením vlastnosti SizeToContent na Width
a k té další otázce:
(nejen) vXAMLu jsou některé znaky zakázané používat ve své podobě (např. <,>,&) a když je chceš použít, tak je musíš zadat speciálním způsobem - tzv odescapovat

 
Odpovědět 24. května 15:00
Avatar
Odpovídá na ostrozan
Andy Scheuchzer:24. května 15:09
  1. Díky.
  2. Ano, tohle vím, ptal jsem se na funkci těch operátorů v C#. Tenhle řádek
List<string> operatory = new List<string>() { "+", "-", "x", "/", "%", "&", "|", "^", "~", "<<", ">>" };

co máš v kódu nahoře, konkrétně tuto část

{ "&", "|", "^", "~", "<<", ">>" };

tak, jak ji máš použitou ve 2. části tutoriálu.

Odpovědět 24. května 15:09
Od ASM úroveň jazyků pouze klesá…
Avatar
ostrozan
Redaktor
Avatar
ostrozan:24. května 15:37

Jo ták :)

to jsou logické operátory a operátory pro bitové operace (and,or,xor,ne­gace,posun vlevo,posun vpravo)

 
Odpovědět 24. května 15:37
Avatar
Odpovídá na ostrozan
Andy Scheuchzer:24. května 16:24

Díky, snad už to chápu. :-)

Odpovědět 24. května 16:24
Od ASM úroveň jazyků pouze klesá…
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 29. Zobrazit vše