Programujeme ve Visual Basic .NET - 27. díl - formátování textu

Převedení hodnoty objektu na jeho textovou reprezentaci se nazývá formátování. V tomto dílu seriálu o VB.NET se budeme věnovat metodám pro formátování textového výstupu.

Metody pro formátování textového výstupu do části seriálu věnované textovým řetězcům spadají díky tomu, že výstupem je vždy textový řetězec a formátovací metody proto zastřešuje třída System.String. Jako vstup formátování však může sloužit libovolný objekt, jehož obsah lze převést na řetězec. Problematika formátování není nijak složitá, ale s ohledem na počet použitelných možností je dosti rozsáhlá a proto v tomto úvodním kursu budou shrnuty jen její základní varianty. Vyplatí se ji však nastudovat, protože se v praxi často setkáváme s tím, že si programátoři neznalosti prostředí .NET formátování často zbytečně a nepřehledně implementují sami. Podrobnější informace lze získat prostřednictvím on-line nebo off-line nápovědy Microsoft .NET SDK na MSDN.

Obrácený postup k formátování se nazývá parsování a rozumíme jím převedení textového řetězce na data hodnotového typu. Jelikož lze hodnotový typ převést na formátovaný řetězec libovolným počtem způsobů, je zřejmé, že při něm může dojít ke ztrátě informace (tzv. rozšíření typu, type narrowing) a že formátování a parsování nejsou plně vratné (reverzibilní) postupy. Před formátováním hodnoty bychom se měli ujistit, že v kódu neparsujeme takto získaný textový řetězec zpět a při dalším výpočtu pracujeme s původní hodnotou. Protože VB.NET podporuje  implicitní typové konverze a umožňuje přitom typovou kontrolu potlačit, můžeme se s touto chybou setkat v jeho zdrojových kódech častěji, než v jiných jazycích.

Metoda ToString

Nejčastěji používanou formátovací metodou je System.Object.ToString (), což je univerzálně dostupná přetěžovatelná metoda instance všech objektů .NET, vracejících jejich vnitřní textovou reprezentaci. Na výstup metody ToString() tedy narazíme vždy, když se pokusíme vypsat hodnotu objektu do textového proudu např. na znakové zařízení System.Console. Pokud prostředí VB.NET uplatňuje ve výchozím nastavení implicitní datovou konverzi, je v takovém případě volána ve skutečnosti vždy metoda ToString() daného objektu. Pro hodnotové typy, které implementují rozhraní System.IFormattable takové volání metody ToString() odpovídá výchozímu formátování hodnoty na řetězec. Později se seznámíme s implementací metody ToString() i pro referenční typy objektů.

Metoda ToString() přebírá jeden či dva volitelné parametry, Prvním je formátovací řetězec, tzv. specifikátor formátu a druhý, rovněž volitelný je parametr, který označuje hodnotu popisovače CultureInfo() v případě, že formátování hodláme uplatnit s jinou, než výchozí hodnotou prostředí volajícího threadu (fronty procesních instrukcí).

Pokud je naše aplikace spuštěna v kontextu uživatele výchozím hodnota popisovače lze získat jako atribut CurrentCulture aktuálního threadu System.Threading.Thread.CurrentThread.CurrentCulture. Výchozí (invariantní) popisovač české kutury je v systému registrován pod řetězcem "cs-CZ" ("Czech (Czech Republic"). Aktuální seznam popisovačů prostředí podporovaných prostředím .NET Framework lze získat v off-line nebo online dokumentaci .NET Framework. Následující volání:

Console.WriteLine(System.Threading.Thread.CurrentThread.CurrentCulture)

v tomto případě vypíše do systémové konzole řetězec:

cs-CZ

Jako ukázku si předvedeme formátování hodnoty aktuálního systémového datumu a času, kterou lze získat metodou System.DateTime.Now(). Po jejím zformátování metodou ToString() vrátí řetězec, odpovídající aktuálnímu datumu a času, např. "27.2.2005 16:36:13". Tento formát odpovídá výchozímu specifikátoru předdefinovaného formátu ("G"), který pro prostředí  "cs-CZ" odpovídá uživatelskému formátu "dd.M.yyyy HH.mm.ss":

Imports System.Console, System.DateTime, System.Globalization
Module modMain
  Sub Main
    WriteLine(Now)
    WriteLine(Now.ToString())
    WriteLine(Now.ToString("G"))
    WriteLine(Now.ToString("dd.M.yyyy HH.mm.ss"))
    WriteLine(Now.ToString("G", New CultureInfo("cs-CZ")))
  End Sub
End Module

V důsledku uplatňování výchozích hodnot tedy všechny ukázky uvedené výše vypíšou tentýž řetězec "27.2.2005 16:36:13", představující datum zformátované podle výchozího nastavení prostředí "Czech (Czech Republic". V případě, že si nepřejeme, aby výstup aplikace byl formátován podle prostředí uživatelského kontextu, pod kterým běží můžeme metodě ToString() použitím volitelného parametru vnutit prostředí jiné. Např. metoda:

WriteLine(Now.ToString("G", New CultureInfo("en-US")))

vypíše hodnotu aktuálního datumu a času pomocí výchozího popisovače prostředí "en-US" (English - United States):

2/27/2005 4:36:13 PM

Prvním parametrem metody ToString() je formátovací řetězec, jehož funkce závisí na aktuálním datovém typu, jehož hodnotu hodláme formátovat. Prostředí .NET Framework zde nabízí dva základní typy formátovacího řetězce. Nejjednodušší použití spočívá v použití jednoho z předdefinovaných formátů, jehož výstup však závisí na použité kultuře kontextu uživatelského prostředí a jako takový může být předdefinován pomocí nastavení v sekci registru HKCU\Control Panel\International.

Poznámka

Prostředí .NET Framework je do značné míry modulární a tak v případech, že nám nevyhovuje ani flexibilita uživatelských specifikátorů, nebo potřebujeme implementovat výchozí či uživatelské formátování pro své vlastní hodnotové typy implementujících rozhraní System.IFormattable, máme možnost definovat si vlastní specifikátory a způsob formátování pomocí rozhraní System.IFormatProvider pro standardní a System.ICustomProvider pro uživatelský formát. Detailní popis implementace těchto rozhraní ovšem značně přesahuje rozsah úvodního kurzu VB.NET.

Formátování čísel

Nejčastějším předmětem formátování textového výstupu jsou číselné hodnoty, pro které má prostředí .NET připravenu sadu předdefinovaných formátů, které se specifikují znak. V tabulce níže jsou shrnuty nejčastěji používané specifikátory, kompletní přehled nalezneme na příslušném místě online dokumentace.

Specifikátor Formátovací řetězec Formát Výstup čísla 1.42D Výstup čísla 12400%
C {0:c} Měna (currency) $1.42 -$12,400
D {0:d} Decimální (celá čísla System.FormatException -12400
E {0:e} Vědecký 1.420000e+000 -1.240000e+004
F {0:f} Pevná řádová čárka ("fixed decimal") 1.42 -12400.00
G {0:g} Obecný ("general") 1.42 -12400
N {0:n} Číslo s čárkami na místě oddělovačů tisícovek 1.42 -12,400
R {0:r} Parsovatelný zpátky na původní číslo 1.42 System.FormatException
X {0:x4} Hexadecimální (celá čísla) System.FormatException cf90

Tabulka 1: Standardní specifikátory pro předdefinovaný formát čísla

Specifikátor "R" zajišťuje, že výsledek formátování bude zpátky převoditelný na původní číslo metodou Parse() původního typu a nelze jej uplatnit na čísla bez řádové čárky, zatímco použitelnost  specifikátorů "D" a "X" je omezena na celá čísla maximálního rozsahu System.Int64 (Long). V opačném případě použití vyvolá výjimku typu System.FormatException.

Imports System
Module MMain
  Sub Main
    Console.Write("Použitá kultura:")
    Console.WriteLine(System.Threading.Thread.CurrentThread.CurrentCulture)
    Dim d As Double = 123456789D
    Console.WriteLine(d.ToString("C"))
    Console.WriteLine(d.ToString("E"))
    Console.WriteLine(d.ToString("P"))
    Console.WriteLine(d.ToString("N"))
    Console.WriteLine(d.ToString("F"))
  End Sub
End Module  

V našem případě ukázka do konzole vypíše výstup uvedený níže. Pro orientaci byla současně vypsána metodou CurrentThread.CurrentCulture() aktuálně použitá kultura prostředí definovaná třídu System.Globalization.CultureInfo. Jelikož v prostředí Windows může každá prováděcí fronta instrukcí (threadu) běžet v samostatném bezpečnostním kontextu s definovanou hodnotou popisovače CultureInfo, je vypsáno nastavení lokalizace aktuálního threadu:

Použitá kultura: cs-CZ
123 456 789,00 Kč
1,234568E+008
12 345 678 900,00%
123 456 789,00
123456789,00

V případě, že nám nevyhovují předdefinované číselné formáty, nebo jejich závislost na nastavení lokalizace, můžeme si implementovat vlastní s pomocí zástupných znaků, které jsou shrnuty v následující tabulce:

Specifikátor Formát Příklad Výstup 1500.42D Poznámka
0 umístění nuly {0:00.0000} 1500.4200 Zarovná výstup nulami
# umístění číslice {0:(#).##} (1500).42 Specifikuje počet platných míst před, popř. za desetinnou čárkou
. desetinná tečka {0:0.0} 1500.4  
, oddělovač tisíců {0:0,0} 1,500 Musí být umístěn vždy mezi dvěma nulami
,. dělitel {0:0,.} 2 Každá čárka následovaná tečkou vydělí číslo tisícem
% procentuální formát {0:0%} 150042% Vynásobí číslo stem, doplní znak procenta "%".
e umístění exponentu {0:00e+0} 15e+2 K dispozici je několik formátů "E", "E+", "E-", "e", "e+", nebo "e-"
; oddělovač skupin viz níže   Odlišuje formátování kladných a záporných čísel

Tabulka 2: Specifikátory pro uživatelský formát čísla

Oddělovač skupiny je užitečný pro formátování hodnot měny, kde jsou často vyžadováno, aby záporné hodnoty byly uzavřeny v závorkách, nebo nulové částky vyjádřeny slovem, apod. Příklad s formátováním měny v poslední z následujících ukázek demonstruje použití oddělovače skupiny:

  String.Format("#####", 123) ` vrátí 123
  String.Format(" (###) ### - ####", 1234567890) ` vrátí (123) 456 – 7890
  String.Format("#.##", 1.2) ` vrátí 1.2
  String.Format("0.00", 1.2) ` vrátí 1.20
  String.Format("00.00", 1.2) ` vrátí 01.20
  String.Format("#,#", 1234567890) ` vrátí 1,234,567,890
  String.Format("#,,", 1234567890) ` vrátí 1235
  String.Format("#,,,", 1234567890) ` vrátí 1
  String.Format("#,##0,, ", 1234567890) ` vrátí 1,235
  String.Format("#0.##% ", 0.086) ` vrátí 8.6%
  String.Format("0.###E+0", 86000) ` vrátí 8.6E+4
  String.Format("0.###E+000", 86000) ` vrátí 8.6E+004
  String.Format("0.###E-000", 86000) ` vrátí 8.6E004
  String.Format("[##-##-##]", 123456) ` vrátí [12-34-56]
  String.Format("##;(##)", 1234) ` vrátí 1234
  String.Format("##;(##)", -1234 ` vrátí (1234)
  String.Format("{0:(###) ###-####}", 18005551212) ` vrátí (800) 555-1212
  String.Format("{0:$#,##0.00;($#,##0.00);Zero}", 1243.50) ` vrátí $1,240.00

Formátování datumu

Jak si podrobněji uvedeme později, datum a čas je v prostředí .NET ukládán ve struktuře System.DateTime. Tato struktura je vnitřně reprezentována číslem rozsahu System.Int64, tj., číslem dostatečně velkým, aby zachytilo časové rozpětí od 1.ledna roku 0001 gregoriánského kalendáře do prosince roku 9999 s rozlišením na 100 nanosekund. Tento rozsah umožňuje datový typ DateTime formátovat celou řadou variant. Stejně jako při formátování čísel zde máme k dispozici několik nejčastěji používaných předdefinovaných formátů, jejichž výstup závisí na aktuálním nastavení systému, nebo předané hodnotě popisovače CultureInfo, předané jako druhý parametr metody ToString():

Specifikátor Type System.DateTime.Now.toString($)
d Krátké datum 10/12/2002
D Dlouhý formát datumu December 10, 2002
t Krátký formát času 10:11 PM
T Dlouhý formát datumu 10:11:29 PM
f Datum a čas ve zkráceném formátu December 10, 2002 10:11 PM
F Datum a čas ve nezkráceném formátu December 10, 2002 10:11:29 PM
g Výchozí formát datumu a času ve zkráceném formátu 10/12/2002 10:11 PM
G Výchozí formát datumu a času v nekráceném formátu 10/12/2002 10:11:29 PM
M Formát Měsíc, Den December 10
r Formát datumu dle RFC1123 specifikace Tue, 10 Dec 2002 22:11:29 GMT
s Tříditelný formát 2002-12-10T22:11:29
u Univerzální tříditelný formát v lokální  notaci 2002-12-10 22:13:50Z
U Univerzální tříditelný formát v GMT notaci December 11, 2002 3:13:50 AM
Y Formát Měsíc, Rok December, 2002

Tabulka 3: Standardní specifikátory pro předdefinovaný formát datumu a času

Dále máme opět možnost vytvořit si s použitím specifikátorů vlastní formát datumu, nezávislý na aktuálním nastavení prostředí systému v Ovládacích panelech Windows.

Specifikátor

Type

Příklad

System.DateTime.Now.toString($)

dd

Číslo dne

{0:dd}

27

ddd

Zkrácený název dne - lokalizovaný

{0:ddd}

ne

dddd

Plný název dne - lokalizovaný

{0:dddd}

neděle

f, ff, ...

Zlomky sekundy [msec]

{0:fff}

932

gg, ...

Zkratka letopočtu

{0:gg}

n.l. (našeho letopočtu), A.D. ("Anno Domini"), apod.

hh

Číslo hodiny dvoumístné [00-12]

{0:hh}

10

HH

Číslo hodiny ve 24 hod. formátu [00-24]

{0:HH}

22

mm

Číslo minuty [00-59]

{0:mm}

38

MM

Číslo měsíce [01-12]

{0:MM}

12

MMM

Zkratka měsíce

{0:MMM}

II

MMMM

Plný název měsíce - lokalizovaný

{0:MMMM}

únor

ss

Číslo vteřin  [00-59]

{0:ss}

46

tt

zkratka dopoledne - odpoledne

{0:tt}

odp. / dop, AM / PM, apod.

yy

Číslo roku - dvoumístný formát

{0:yy}

05

yyyy

Číslo roku - čtyřmístný formát

{0:yyyy}

2005

zz

Posun časové zóny - dvoumístný

{0:zz}

+01

zzz

Posun čas. zóny s přesností na minuty

{0:zzz}

+01:00

:

Oddělovač času

{0:hh:mm:ss}

10:43:20

/

Oddělovač datumu

{0:dd/MM/yyyy}

25/02/2005

Tabulka 4: Specifikátory pro uživatelský formát datumu a času

Formátování výčtových typů

Posledním hodnotovým typem, na který lze uplatnit formátovací řetězce, kterým jsme se zatím blíže nezabývali je výčtové typy System.Enum, vnitřně reprezentovaný konstantou pojmenovanou příznakem (tzv. flag) výchozího typu System.Int32 System.DayOfWeek.Monday: Samozřejmě si můžeme pro své účely definovat vlastní výčtoové typy, s čímž se seznámíme později.

Specifikátor Typ  System.DayOfWeek.Monday.ToString($)
g Výchozí - název příznaku, pokud je uvedeno Monday
f Název příznaku použito vždy Monday
d Hodnota konstanty vždy 1
x Osmimístné číslo v hexadecimálním tvaru 00000001

Kompozitní formátování

Zatímco metoda ToString() podporuje formátování jediné výstupní hodnoty, rozhraní .NET formátovací pravidla konzistentně uplatňuje i v případě dalších metod pracujících s textovým výstupem, jako jsou metody třídy System.String.Format(), metod třídy System.Text.StringBuilder, System.IO.TextWriter nebo nám již dobře známe metody System.Console.Write() a System.Console.WriteLine() - a to tak, že umožňuje začlenit do formátovacícho specifikátoru jeden či více výstupních hodnot spolu s literály aj textovými konstantami. Takový zápis podstatně zjednodušuje a zpřehledňuje formátování výstupu, kombinovaného s textovými řetězci. Použítí a výhody kompozitního formátování si nejsnáze osvětlíme na konkrétním příkladu:

Dejme tomu, že v průběhu déletrvajícího výpočtu požadujeme sledovat jeho průběh následujícím výpisem:

Aktuální čas 27.2.2005: vypočteno 37,0%...
Aktuální čas 28.2.2005: vypočteno 42,5%...

Každý řádek výpisu zobrazuje dvě hodnoty současně, ve výpisu zvýrazněné tučně: aktuální datum ve zkráceném formátu a procentuální hodnotu dosaženého průběhu výpočtu. Se současnými znalostmi formátování takový výstup pro nás nepředstavuje problém, a ke spojení řetězců do výstupu můžeme použít nám již známý operátor spojování (konkatenace) řetězců, znak "&" (znak ampersand):

WriteLine("Aktuální čas " & DateTime.Now.ToString("d") & ": vypočteno " & 0.37.ToString("0.0%") & "...")

Operátor spojení řetězců však činí tento zápis značně nepřehledný, a především tento způsob zápisu ignoruje hlavní zásadu formátování dat, kterou je oddělení vlastních hodnot od popisovače jejich formátu. Pro tyto situace metody pracující se znakovým výstupem disponují standardně podporovaným přetížením (tj. možností rozšíření vstupních parametrů) v notaci:

{formátovací řetězec, včetně literálových konstant a umisťovače hodnot}, hodnota0, hodnota1, hodnota2...)    popř.
{formátovací řetězec, včetně literálových konstant a umisťovače hodnot}, pole_hodnot..)

Daná ukázka se s použitím kompozitní notace zapíše mnohem přehledněji a kompaktněji následovně:

WriteLine("Aktuální čas {0:d}: vypočteno {1:0.0%}...", DateTime.Now, 0.37)

Vidíme, že kromě zkrácení zápisu došlo k jeho podstatnému zpřehlednění a především oddělení výstupních hodnot od formátu vstupních dat. Výstupní data jsou začleněna do výstupního proudu pomocí tzv. umísťovačů (placeholders), který je vymezen dvojicí složených závorek "{ }", Uvnitř závorek je kompozitní specifikátor formátu, který má následující notaci:

{pořadové_číslo_hodnoty[,[+|-]počet_znaků_zarovnání][:formátovací_řetězec]}

Hranaté závorky symbolizují, že parametr je volitelný. Pořadové číslo výstupní položky je standardně indexováno od nuly. K zarovnávání se použije zadaný počet mezer, je-li použito záporné číslo, dojde k zarovnání výstupu zleva. Následující ukázky formátovacího řetězce tedy vedou ke shodnému výstupu, kterým je výpis řetězce "0,37" do systémové konzole:

WriteLine("{0}...", 0.37)
WriteLine("{0:0.00}...", 0.37)
WriteLine("{0,4:0.00}...", 0.37)

Formátované pole hodnot typu SystemObject() s použitím kompozitního formátovacího specifikátoru vypíšeme následovně:

WriteLine("{0:00} |{1,-5}| {2:0.0}", New Object(){0, 0.37, -70}) ` vypíše
00 |0,37 | -70,0

Metoda String.Format

V případě, že nepožadujeme výsledek formátování vypsat bezprostředně do textového proudu třídami System.Console, System.IO.TextWriter nebo System.TextString.Builder, můžeme pro jednoduché i kompozitní formátování využít statickou metodu SystemString.Format(). S hodnotou textového řetězce, kterou tato metoda vrací, pak můžeme dále v kódu pracovat podle potřeby. Například jí můžeme nahradit specializované metod y String.PadLeft() a String.PadRight(),zarovnávajících textové řetězce mezerami, popř. jinými znaky na daný počet míst zleva, resp. zprava. Způsob zápisu parametrů je shodný s ostatními metodami, pracujícími s textovým proudem:

    Dim sTest As String
    sTest = String.Format("->{0,10}<-",  "Ahoj") ` přiřadí:  ->     Ahoj<-
    sTest = String.Format("->{0,-10}<-", "Ahoj") ` přiřadí:  ->Ahoj     <-
    Console.WriteLine("->{0,10}<-",  "Ahoj")  ` vypíše:   ->     Ahoj<-
    Console.WriteLine("->{0,-10}<-", "Ahoj")  ` vypíše:   ->Ahoj     <-

Podobně jako metoda ToString() i metoda String.Format() podporuje volitelný parametr, kterým se předává instance objektu, implementující rozhraní System.IFormatProvider, což umožňuje implementovat vlastní formátovací pravidla a jejich řetězcové specifikátory. Popisem tohoto rozhraní a jeho implementací se v úvodním kursu VB.NET nebudeme zabývat.

Stručné shrnutí

Formátování dat odpovídá jejich převodu na textovou reprezentaci a je inverzní operací k parsování řetězců. Pro základní formátování se využívá metoda ToString(), pro kompozitní programování můžeme využít metodu String.Format(), implementované i v dalších třídách, pracujících s textovými proudy, jako System.Console, System.Text.String.Builder apod. Výchozí formátování datumu a desetinných čísel je závislé na lokalizaci prostředí (hodnotě popisovače System.Globalization.CultureInfo volajícího threadu) - proto požadujeme-li formátování nezávislé na aktuálním nastavení jazykového prstředí, je nutné CultureInfo specifikovat, nebo využívat specifikátory uživatelského formátu.

Popisem formátování řetězců jsme zakončili blok seriálu, věnovaný práci s textovými řetězci, který představuje závěr úvodní části seriálu, věnovaného popisu základního rozhraní VB.NET. Doposud získané znalosti nám umožní vyvíjet jednodušší, procedurálně orientované skripty a aplikace pro příkazovou řádku. Jelikož prostředí .NET Framwork i VB.NET je objektově orientované, pro pokročilejší práci - zejména pro vývoj aplikací pro grafické uživatelské prostředí a vývoj databázových a webových aplikací - se začneme postupně seznamovat se základními principy komponentního a objektově orientovaného programování.

Témata článku: Software, Microsoft, Programování, Proud, Formát, Základní popis, Následující den, FF, Textový výstup, MMA, Currency, Amper, Díl, Libovolný objekt, Anna, Anno, Textový řetězec, Code, December, Původní data, Uživatelské rozhraní, Původní objekt, Ampersand, Framework, Základní znalost

Určitě si přečtěte

Velká podzimní aktualizace Windows 10 je tady: Co přináší Fall Creators Update

Velká podzimní aktualizace Windows 10 je tady: Co přináší Fall Creators Update

** Po půl roce je tu další aktualizace Windows ** A opět přináší hlavně hromadu drobných kosmetických vylepšení ** Podívali jsme se na ty nejzajímavější

17.  10.  2017 | Jakub Čížek | 185

Budoucností Windows 10 je Fluent Design. Takto bude jednou vypadat celý systém

Budoucností Windows 10 je Fluent Design. Takto bude jednou vypadat celý systém

** Fluent Design je vzhled, do kterého postupně Microsoft převleče celý systém ** Staví na průhlednosti a velkých plochách ** Do Windows 10 se z části dostane už zítra při vydání podzimní aktualizace

16.  10.  2017 | Stanislav Janů | 155

Jak funguje největší akumulátor v Česku: podívejte se do elektrárny Dlouhé Stráně

Jak funguje největší akumulátor v Česku: podívejte se do elektrárny Dlouhé Stráně

** Přečerpávací vodní elektrárna Dlouhé stráně je obdivuhodné technické dílo ** Stejná turbína vyrábí elektřinu i tlačí vodu zpět do horního jezera ** Strojovna elektrárny je zabudována v podzemí

19.  10.  2017 | David Polesný | 17

Nejlepší optické iluze: Z toho vám půjde hlava kolem

Nejlepší optické iluze: Z toho vám půjde hlava kolem

** Mozek se nechá snadno ošálit, a to mnoha způsoby ** Podívejte se na několik nejlepších optických iluzí ** Iluze dokazují, že vnímání reality může být značně zkreslené

16.  10.  2017 | Vojtěch Malý


Aktuální číslo časopisu Computer

Nový seriál o programování elektroniky

Otestovali jsme 17 bezdrátových sluchátek

Jak na nákup vánočních dárků ze zahraničí

4 tankové tiskárny v přímém souboji