Technologie WPF je plně vektorová. Týká se to jak návrhu uživatelského prostředí, tak samotného vykreslování. To přináší celou řadu výhod a možností, které budu v tomto seriálu postupně rozebírat.
Tento díl se věnuje troše nezbytné teorie o postupu, který zajišťuje WPF aplikacím nezávislost na nastavení hodnoty DPI operačního systému.
Windows Forms a práce s pixely
Pokud pracujete s technologií Windows Forms využíváte k pozicování pixely. Pixel je nejmenší fyzický obrazový bod. V rozlišení 800×600 máte k dispozici 800 pixelů na šířku a 600 na výšku. A tlačítko se šířkou 100 je tak fyzicky široké 100 pixelů.
Pokud máte monitor 17” s rozlišením 1920×1080 bude tlačítko zákonitě vypadat menší, než na rozlišení 1024×768. Ačkoliv je jejich rozměr v pixelech identický. Rozdíl se ještě víc prohlubuje například na mobilních telefonech, které mají hustotu pixelů ještě výrazně větší a naše tlačítko by tam vypadalo velmi malé.
Dots per inch - DPI
Proto existuje údaj dots per inch – DPI. Ten určuje, hustotu pixelů. Přesněji kolik pixelů se vejde do délky jednoho palce na našem zobrazovacím zařízení. Čím vyšší rozlišení nebo čím menší monitor, tím menší pixely jsou a tím je zákonitě větší jejich hustota a tím roste i samotný údaj DPI.
V ideálním světě má každý monitor a každé zobrazovací zařízení podle svého rozlišení a rozměrů své specifické DPI, které je nastavené v operačním systému a podle toho aplikace zobrazují uživatelské rozhraní v odpovídají velikosti – například monitor s poloviční velikostí bude zobrazovat uživatelské prostředí v pixelech v dvojnásobných rozměrech, aby mělo fyzickou velikost stejnou jako na monitoru dvakrát větším.
Nežijeme však v ideálním světě a velká část aplikací si s jiným DPI neumí správně poradit. Například právě Windows Forms s DPI pracovat neumí (pracujeme přímo s pixely) a proto bychom museli podle tohoto údaje ručně měnit velikost a pozice všech ovládacích prvků. Z toho důvodu má operační systém tuto hodnotu nastavenou na výchozí, což je 96 DPI (96 pixelů na palec) a neodpovídá tak na většině zobrazovacích zařízení skutečnosti. Změnit ale samozřejmě lze z ovládacích panelů. Já osobně používám 120 DPI (125% proti 96 DPI), protože mám 15.6” notebook s FullHD rozlišením 1920×1080, kde bylo při 96 DPI vše velmi titěrné. Změnu jsem si vykoupil občasným setkáním s aplikacemi, která nastavení nerespektují nebo se zobrazují špatně. Obvykle se to projeví přetékáním objektů mimo formulář a za jiné prvky a podobně.
Protože skutečné DPI většinou nastavené v systému není, ztrácí toto označení smysl a například ve Windows 7 se volba pro změnu zjednodušeně jmenuje “Zmenšit nebo zvětšit text a další položky”, kde na oko nenastavujeme přímo DPI, ale velikost v procentech. Máme na výběr 100% (=96DPI), 125% (=120DPI) a 150% (=144DPI). Poměrově tak lze nastavit velikost ovládacích prvků proti zažitému defaultním DPI.
Device independent pixels
WPF problémy s různě nastaveným DPI řeší velmi elegantně. Používá totiž jako defaultní jednotku k určování pozice a rozměrů takzvané device independent pixels. Jedná se o abstrakci nad skutečnými pixely a spočívá v automatické změně měřítka v závislosti na nastavení DPI na počítači, kde aplikaci spouštíme. Pracuje se tu s předpokladem, že za výchozího 96 DPI je poměr 1 device independent pixel = 1 fyzický pixel. Jinými slovy, pokud aplikaci budete spouštět pouze na počítačích, které mají výchozí nastavení DPI, objekty nastavené na šířku 100 budou mít šířku 100 pixelů. Pokud aplikaci spustíte na počítači s 144 DPI, velikost všech objektů bude poměrově zvětšena o 50% a prvek se šířkou 100 bude mít fyzicky šířku 150 pixelů.
Na následujícím obrázku je tlačítko ze stejné aplikace spuštěné na počítači s 96 DPI (vlevo) a s 192 DPI (vpravo).
Jak WPF zajistí změnu velikosti?
Transformace obecně
Jak už jsem psal, WPF je plně vektorová technologie. Změnu měřítka lze proto snadno provést pomocí transformací. Těch existuje hned několik. Za zmínku stojí například RotationTransform pro otáčení objektu. Pro změnu měřítka je tu pak ScaleTransform.
Transformace lze nastavit na libovolný objekt a WPF před jeho vykreslením spočítá finální transformační matici, kterou využije. Například pokud budete mít tlačítko zvětšené na čtyřnásobek umístěné v panelu, který vše naopak zmenšuje na čtvrtinu, výsledná transformace se vyruší. Důležitý je fakt, že se prvek vykresluje až s finální transformací která zahrnuje kombinaci všech aplikovaných transformací (například tlačítko na formuláři přijímá transformace celého okna, panelu ve kterém leží i jeho vlastní).
Jako příklad uvádím tento jednoduchý XAML kód. Nastavuji vloženému tlačítku do vlastnosti LayoutTransform instanci třídy ScaleTransform zvětšující tlačítko na 4-násobek původní velikosti:
<Button Width="60" Height="30" Content="VbNet.cz">
<Button.LayoutTransform>
<ScaleTransform ScaleX="4" ScaleY="4" />
</Button.LayoutTransform>
</Button>
Poznámka: Transformaci lze nastavit krom do vlastností LayoutTransform také do RenderTransform. Ta transformuje pouze vzhled objektu při vykreslování, ale layout nechává nedotčený (při pozicování se počítá s originálními rozměry a pozicí elementu). Této tématice se budu věnovat v jednom z dalších dílů.
Poznámka: Vysvětlení zápisu “…<Button><Button.LayoutTransform>…” naleznete v odstavci “Nastavování vlastností” v článku o XAMLu.
Transformace pro DPI
Změna velikosti prvků podle DPI se provádí automaticky na úrovni kompozice vykreslování a je tak aplikována na veškerý obsah formuláře. Provádí se poměrově proti výchozímu 96 DPI. Například:
- Pokud je DPI 96, transformace je 1. Tedy žádná změna velikosti.
- Pokud je DPI 120, transformace je 1,25. Vše se tedy bude vykreslovat o 25% větší.
- Pokud je DPI 144, transformace je 1,5. Vše se vykresluje o 50% větší.
Opět i zde platí způsob výpočtu finální transformace. Mějme pro příklad formulář, kde je tlačítko zmenšené na 80%. V systému je nastavené DPI 120 a transformace na úrovni kompozice je tak 125%. Vykreslovací jádro vynásobí 80% a 125% a výsledkem je 100% (0,8×1,25=1). Takže objekt v rozměru dejme tomu 60 se vykreslí s finálním rozměrem 60 pixelů.
Možné problémy
Pokud budete psát aplikaci pracující pod různými DPI, můžete se nejčastěji setkat s problémy s bitmapovými obrázky. Pokud jej zvětšíte, zhorší se kvalita a na druhou stranu, pokud jej nezvětšíte, bude rozložení prvků jiné (okolo obrázku bude místo / nevyplní celou požadovanou plochu a podobně). Další problém může nastat při jejich pozicování a možném efektu “rozpíjení”, kdy obrázek má díky nějaké transformaci pozici mezi dvěma fyzickými pixely a výsledek může působit rozmazaně.
Všechny tyto uvedené problém jsou důsledkem špatného použití technologie a většinu z nich je možné poměrně snadno ve WPF řešit. Ale tomu se budu věnovat v samostatném dílu.
Drobná ukázka na závěr
Na závěr přikládám krátký kód, který přidá do formuláře sadu 20ti tlačítek, přičemž každé nastaví jinou transformaci změny měřítka – ScaleTransformation. Po spuštění jsou všechny tlačítka plně funkční.
Pro vygenerování vložte do hlavní třídy okna tento kód (není potřeba zásah do XAML kódu):
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// vytvořit panel na umístění tlačítek
var stackPanel = new StackPanel();
this.Content = stackPanel;
// vytvořit tlačítka
for (int i = 0; i < 20; i++)
{
double scale = ((double)i) / 15.0 + 0.5;
stackPanel.Children.Add(new Button()
{
Width = 120,
Height = 30,
Content = "VbNet.cz",
LayoutTransform = new ScaleTransform(scale,scale)
});
}
}
}
Závěr
Díku vektorovému přístupu je možné připravovat aplikace, které se dynamicky zvětšují a zmenšují podle rozlišení nebo správně reagují na změny hodnoty DPI systému. To otevírá celou řadu nových typů aplikací, které lze s Windows Presentation Foundation realizovat.
Další díl budu věnovat úvodu do vestavěných komponent a pozicování, které je ve WPF vyřešeno naprosto skvěle. Konečně tak nakousnu praktickou stránku věci. Pokud jste tak ještě neučinili, doporučuji se na další díl připravit dřívějším článkem o jazyce XAML.