Vítejte v druhé části vývoje našeho prvního pull requestu do Uno Platform. V prvním článkuhe této série jsme se podívali na Uno jako celek a poprvé nahlédli do jeho kódu. Tentokrát se podíváme na existující implementaci třídy DisplayInformation
, přidáme do ukázkové aplikace novou stránku na které náš kód budeme testovat, seznámíme se s projektem UWPSyncGenerator
a implementujeme ScreenWidthInRawPixels
a ScreenHeightInRawPixels
.
Třída DisplayInformation
Již minule jsem zmínil, že issue kterou jsem se rozhodl řešit má číslo #385 – “Add support for DisplayInformation Dimensions“.
Nejprve se podívejme co UWP třída DisplayInformation
umí. Můžeme ji najít v namepsace Windows.Graphics.Display
a poskytuje informace o displeji na kterém je aplikace zobrazena. Navíc, tato třída zahrnuje události o změnách displeje jako je OrientationChanged
.
Abychom API mohli implementovat, je nutné dodžovat co nejpřesněji chování platformy UWP. Naštěstí Microsoft Docs jsou velmi dobře napsané a každá vlastnost je důkladně popsána.
Rozhodl jsem se implementovat všechny vlastnosti DisplayInformation
na Androidu a iOS a přenechat ostatní platformy a události na další pull requesty. Tím bude pull request výstižný a získám rychlejší zpětnou vazbu. Jakmile bude potvrzeno, že je můj přístup správný, můžu s větší jistotou pokračovat dál.
Existující Uno implementace
Když jsem nahlédl do složky Graphics/Display
v projektu Uno
, zjistil jsem, že soubory DisplayInformation.cs
, DisplayInformation.Android.cs
a DisplayInformation.iOS.cs
již existují a implementují AutoRotationPreferences
, CurrentOrientation
a OrientationChanged
na iOS a AutoRotationPreferences
na Androidu.
Pro získání instance DisplayInformation
na UWP je třeba zavolat metodu GetForCurrentView
z UI vlákna. Na UWP je instance svázaná s vláknem ale Uno nyní používá jednodušší přísutp a vyvtáří singleton instanci nezávislou na vlýkně:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static DisplayInformation GetForCurrentView() | |
{ | |
if (_instance == null) | |
{ | |
_instance = new DisplayInformation(); | |
} | |
return _instance; | |
} |
Bezparametrický konstruktor je označen jako private
takže nové instance není možné mimo třídu vytvořit. Konstruktor volá partial
metodu Initialize
.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private DisplayInformation() | |
{ | |
Initialize(); | |
} | |
partial void Initialize(); |
Metoda Initialize
je implementována pro Android a iOS zvlášť v souborech DisplayInformation.Android.cs
a DisplayInformation.iOS.cs
.
Například na iOS Initialize
volá InitializeCurrentOrientation
, která nastavuje výchozí hodnotu pro CurrentOrientation
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void InitializeOrientation() | |
{ | |
_didChangeStatusBarOrientationObserver = NSNotificationCenter | |
.DefaultCenter | |
.AddObserver( | |
UIApplication.DidChangeStatusBarOrientationNotification, | |
n => { | |
UpdateCurrentOrientation(); | |
OrientationChanged?.Invoke(this, CurrentOrientation); | |
} | |
); | |
UpdateCurrentOrientation(); | |
} | |
private void UpdateCurrentOrientation() | |
{ | |
var currentOrientationMask = UIApplication.SharedApplication | |
.StatusBarOrientation; | |
switch (currentOrientationMask) | |
{ | |
case UIInterfaceOrientation.LandscapeLeft: | |
CurrentOrientation = DisplayOrientations.LandscapeFlipped; | |
break; | |
case UIInterfaceOrientation.LandscapeRight: | |
CurrentOrientation = DisplayOrientations.Landscape; | |
break; | |
case UIInterfaceOrientation.Portrait: | |
CurrentOrientation = DisplayOrientations.Portrait; | |
break; | |
case UIInterfaceOrientation.PortraitUpsideDown: | |
CurrentOrientation = DisplayOrientations.PortraitFlipped; | |
break; | |
} | |
NativeOrientation = CurrentOrientation; | |
} |
Jak si můžete všimnout, pro určení orientace se používá orientace StatusBaru
, protože jde o nejspolehlivější způsob
Úprava ukázkové aplikace
Abych si usnadnil testování implementace, vytvořil jsem v SamplesApp jednoduchý user control:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<UserControl | |
x:Class="UITests.Shared.Windows_Graphics_Display.DisplayInformation.DisplayInformation_Properties" | |
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" | |
mc:Ignorable="d" | |
d:DesignHeight="300" | |
d:DesignWidth="400"> | |
<Grid> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto" /> | |
<RowDefinition Height="*" /> | |
</Grid.RowDefinitions> | |
<Button Content="Refresh" Click="Refresh_Click" /> | |
<ListView x:Name="PropertyListView" Grid.Row="1"> | |
<ListView.ItemTemplate> | |
<DataTemplate> | |
<Grid Padding="4"> | |
<Grid.RowDefinitions> | |
<RowDefinition /> | |
<RowDefinition /> | |
</Grid.RowDefinitions> | |
<TextBlock Text="{Binding Name}" FontWeight="Bold" /> | |
<TextBlock Text="{Binding Value}" Grid.Row="1" /> | |
</Grid> | |
</DataTemplate> | |
</ListView.ItemTemplate> | |
</ListView> | |
</Grid> | |
</UserControl> |
Control hostuje ListView
, který zobrazí seznam vlastností DisplayInformation
, jejich hodnoty a tlačítko, které je aktualizuje.
UserControl
označíme atributem SampleControlInfoAttribute
jež se používá pro nalezení a kategorizaci ukázkových příkladů.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace UITests.Shared.Windows_Graphics_Display.DisplayInformation | |
{ | |
[SampleControlInfo("DisplayInformation", "DisplayInformation_Properties", description: "Shows the values from DisplayInformation class properties. N/A for not implemented.")] | |
public sealed partial class DisplayInformation_Properties : UserControl | |
{ | |
public DisplayInformation_Properties() | |
{ | |
this.InitializeComponent(); | |
RefreshDisplayInformation(); | |
} | |
public class PropertyInformation | |
{ | |
public PropertyInformation( string name, string value) | |
{ | |
Name = name; | |
Value = value; | |
} | |
public string Name { get; set; } | |
public string Value { get; set; } | |
} | |
private void Refresh_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) | |
{ | |
RefreshDisplayInformation(); | |
} | |
... |
Metoda RefreshDisplayInformation
získává aktuální hodnoty vlastností ze třídy DisplayInformation
. Můj původní plán byl použít reflexi:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void RefreshDisplayInformation() | |
{ | |
var info = Windows.Graphics.Display.DisplayInformation.GetForCurrentView(); | |
var type = info.GetType(); | |
var typeInfo = type.GetTypeInfo(); | |
var propertyInfos = typeInfo.GetProperties(); | |
var properties = propertyInfos.Select(p => new PropertyInformation() { Name = p.Name, Value = SafeGetValue(p, info) }).ToArray(); | |
PropertyListView.ItemsSource = properties; | |
} |
To fungovalo poměrně dobře až do chvíle než jsem kód spustil na iOS kde většina vlastností v seznamu chyběla. Důvodem byl fakt, že linker pro zmenšení výsledné assembly odstraňuje kód, na který nic nepřístupuje a nové vlastnosti DisplayInformation
byly mezi nimi. Kód jsem opravil tak, že jsem vlastnosti zadal ručně:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void RefreshDisplayInformation() | |
{ | |
var info = DisplayInfo.GetForCurrentView(); | |
var properties = new PropertyInformation[] | |
{ | |
new PropertyInformation(nameof(DisplayInfo.AutoRotationPreferences), SafeGetValue(()=>DisplayInfo.AutoRotationPreferences)), | |
new PropertyInformation(nameof(info.CurrentOrientation), SafeGetValue(()=>info.CurrentOrientation)), | |
new PropertyInformation(nameof(info.NativeOrientation), SafeGetValue(()=>info.NativeOrientation)), | |
new PropertyInformation(nameof(info.ScreenHeightInRawPixels), SafeGetValue(()=>info.ScreenHeightInRawPixels)), | |
new PropertyInformation(nameof(info.ScreenWidthInRawPixels), SafeGetValue(()=>info.ScreenWidthInRawPixels)), | |
new PropertyInformation(nameof(info.LogicalDpi), SafeGetValue(()=>info.LogicalDpi)), | |
new PropertyInformation(nameof(info.DiagonalSizeInInches), SafeGetValue(()=>info.DiagonalSizeInInches)), | |
new PropertyInformation(nameof(info.RawPixelsPerViewPixel), SafeGetValue(()=>info.RawPixelsPerViewPixel)), | |
new PropertyInformation(nameof(info.RawDpiX), SafeGetValue(()=>info.RawDpiX)), | |
new PropertyInformation(nameof(info.RawDpiY), SafeGetValue(()=>info.RawDpiY)), | |
new PropertyInformation(nameof(info.ResolutionScale), SafeGetValue(()=>info.ResolutionScale)), | |
}; | |
PropertyListView.ItemsSource = properties; | |
} |
Toto samozřejmě pozbývá jednoduchost a obecnost původního řešení, ale na druhou stranu to funguje 🙂 .
Pro úplnost taky uvedu kód metody SafeGetValue
, která pomocí delegátu Func<T>
získá hodnotu vlastnosti a převede ji na string
je-li to možné:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private string SafeGetValue<T>(Func<T> getter) | |
{ | |
try | |
{ | |
var value = getter(); | |
if ( value == null) | |
{ | |
return "(null)"; | |
} | |
return Convert.ToString(value); | |
} | |
catch (NotImplementedException ex) | |
{ | |
return "(Not implemented)"; | |
} | |
} |
Všimněme si, že kód odcbytává výjimku NotImplementedException
, kterou Uno používá pro API která zatím nemají implementaci.
Na obrázku níže je ukázková aplikace běžící na Windows proti UWP API. Je před námi hodně práce!

Synchronizace s UWP API
Nyní je čas nahlédnout pod pokličku dalšího z UWP kouzel. Jak dokáže tým udržet API plně synchronizované s UWP? Aktualizace Windows 10 přichází pravidelně dvakrát do roka, takže procházet vždy celé API a kontrolovat změny by byl skutečně náročný (a nezáviděníhodný) úkol. Zde ale přichází na scénu projekt Uno.UWPSyncGenerator
.
Musíme nejprve porozumět tomu, jak Uno pracuje s neimplementovaným API. Uvnitř projektů Uno
a Uno.UI
můžeme najít složky s názvem Generated
. Uvnitř jsou podsložky pro všechna UWP API a každý typ pak má svůj vlastní .cs
soubor. Na příklad DisplayInformation.cs
je uvnitř podsložky Generated/3.0.0.0/Windows.Graphics.Display
a obsahuje následující:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma warning disable 108 // new keyword hiding | |
#pragma warning disable 114 // new keyword hiding | |
namespace Windows.Graphics.Display | |
{ | |
#if false || false || false || false || false | |
[global::Uno.NotImplemented] | |
#endif | |
public partial class DisplayInformation | |
{ | |
// Skipping already declared property CurrentOrientation | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public float LogicalDpi | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member float DisplayInformation.LogicalDpi is not implemented in Uno."); | |
} | |
} | |
#endif | |
// Skipping already declared property NativeOrientation | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public float RawDpiX | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member float DisplayInformation.RawDpiX is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public float RawDpiY | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member float DisplayInformation.RawDpiY is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public global::Windows.Graphics.Display.ResolutionScale ResolutionScale | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member ResolutionScale DisplayInformation.ResolutionScale is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public bool StereoEnabled | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member bool DisplayInformation.StereoEnabled is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public double RawPixelsPerViewPixel | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member double DisplayInformation.RawPixelsPerViewPixel is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public double? DiagonalSizeInInches | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member double? DisplayInformation.DiagonalSizeInInches is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public uint ScreenHeightInRawPixels | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member uint DisplayInformation.ScreenHeightInRawPixels is not implemented in Uno."); | |
} | |
} | |
#endif | |
#if __ANDROID__ || __IOS__ || NET46 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public uint ScreenWidthInRawPixels | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member uint DisplayInformation.ScreenWidthInRawPixels is not implemented in Uno."); | |
} | |
} | |
#endif | |
// Skipping already declared property AutoRotationPreferences | |
//… and more! | |
} | |
} |
Toto vše je automaticky generovaný kód, který pro všechna neimplementovaná API přidává NotImplementedException
wrapper. Také si všimněme, že jsou všechny třídy partial
. Skutečné třídy Una pak používají partial
také a mohou tak například implementovat jen část dané třídy.
Uno.UWPSyncGenerator
má na starosti generovat všechen tento kód a jde na to opravdu chytře. Používá balíček Microsoft.CodeAnalysis
a prochází projekty Uno
a Uno.UI
a kontroluje co je již pro které platformy implementované.
Pro příklad přidám první tři vlastnosti DisplayInformation
do DisplayInformation.cs
v Uno
projektu:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public uint ScreenHeightInRawPixels { get; private set; } | |
public uint ScreenWidthInRawPixels { get; private set; } | |
public float LogicalDpi { get; private set; } |
Po spuštění UWPSyncGenerator
deklarované vlastnosti zmizí ze složky Generated
a jsou nahrazeny informativním komentářem:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Skipping already declared property LogicalDpi | |
// Skipping already declared property ScreenHeightInRawPixels | |
// Skipping already declared property ScreenWidthInRawPixels |
Ale to není vše! Co kdybychom se rozhodli implementovat nějakou z vlastností pouze pro Android?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if __ANDROID__ | |
public uint ScreenHeightInRawPixels { get; private set; } | |
#endif |
Nástroj tuto situace zvládne i tak bravurně:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false || __IOS__ || NET461 || __WASM__ || __MACOS__ | |
[global::Uno.NotImplemented] | |
public uint ScreenHeightInRawPixels | |
{ | |
get | |
{ | |
throw new global::System.NotImplementedException("The member uint DisplayInformation.ScreenHeightInRawPixels is not implemented in Uno."); | |
} | |
} |
Et voilà, direktiva #if
nyní obsahuje namísto symbolu __ANDROID__
neškodné false
.
Implementace ScreenWidthInRawPixels
a ScreenHeightInRawPixels
Nebudu ve článku do detailů rozebírat implementaci celé třídy DisplayInformation
, protože jde z většiny o platformně závislý kód a tato série článků je věnována procesu přispívání do Una jako takévho. Ale pro ukázku můžeme ukázat proces na vlastnostech ScreenWidthInRawPixels
a ScreenHeightInRawPixels
. Tyto vlastnosti by měly vrátit skutečné rozlišení displeje v pixlech.
Ze všeho nejdřív přidám do souboru DisplayInformation.cs
nové vlastnosti:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public uint ScreenHeightInRawPixels { get; private set; } | |
public uint ScreenWidthInRawPixels { get; private set; } |
Toto způsobí konflikt s NotImplemented
vlastnostmi ve složce Generated
, ale spuštěním UWPSyncGenerator
konflikt zmizí.
Nyní vlastnostem nastavíme skutečnou hodnotu!
iOS
Přidal jsem novou metodu UpdateProperties
kterou volá metoda Initialize
v DisplayInformation.iOS.cs
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
partial void Initialize() | |
{ | |
InitializeOrientation(); | |
UpdateProperties(); | |
} |
Použiji UpdateProperties
pro nastavení hondnot vlastností ve chvíli, kdy se displej změní. Pro tuto chvíli ale události neimplementujeme, takže vlastnosti zatím nastavím při inicializaci. Aby byl kód jasnější, rozdělím metodu UpdateProperties
na logické skupiny.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void UpdateProperties() | |
{ | |
UpdateRawProperties(); | |
} | |
private void UpdateRawProperties() | |
{ | |
} |
Veĺikost obrazovky na iOS je možné zjistit pomocí vlastnosti UIScreen.MainScreen.Bounds.Size
. To však vrací pouze velikost v layout “bodech”. Vše se potom renderuje ve zvětšení UIScreen.MainScreen.Scale
a pak zase změnší na nativní rozlišení displeje. Tato infografika je pro pochopení tohoto procesu na iOS velmi užitečná. Naštěstí máme i API UIScreen.MainScreen.NativeScale
které vrací skutečné “nativní” zvětšení:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void UpdateRawProperties() | |
{ | |
var screenSize = UIScreen.MainScreen.Bounds.Size; | |
var scale = UIScreen.MainScreen.NativeScale; | |
ScreenHeightInRawPixels = (uint)(screenSize.Height * scale); | |
ScreenWidthInRawPixels = (uint)(screenSize.Width * scale); | |
} |
A ukázková aplikace nyní ukalzuje:

Android
Na Androidu jsem postupoval poobně jako na iOS:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
partial void Initialize() | |
{ | |
UpdateProperties(); | |
} | |
private void UpdateProperties() | |
{ | |
var realDisplayMetrics = new DisplayMetrics(); | |
var windowManager = ContextHelper.Current.GetSystemService(Context.WindowService).JavaCast<IWindowManager>(); | |
windowManager.DefaultDisplay.GetRealMetrics(realDisplayMetrics); | |
UpdateRawProperties(realDisplayMetrics); | |
} |
Je potřeba trochu kódu navíc pro získání instance DisplayMetrics
a IWindowManager
, protože tyto budou znovu použity pro implementaci dalších vlastností. Základní implementace UpdateRawProperties
vypadá takto:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void UpdateRawProperties(DisplayMetrics realDisplayMetrics) | |
{ | |
ScreenWidthInRawPixels = (uint)realDisplayMetrics.WidthPixels; | |
ScreenHeightInRawPixels = (uint)realDisplayMetrics.HeightPixels; | |
} |
DisplayMetrics
které jsme výše získali přimo obsahují vlastnosti, které vrací šířku a výšku displeje v pixelech, takže je jednoduše přetypujeme a jsme hotovi:

Shrnutí
Podívali jsme se na existující implementaci DisplayInformation
v Unu, pochopili, jak framework synchronizuje své API s UWP a implementovali první dvě vlastnosti.
Příště se v krátkosti podíváme na to nejzajímavější ze zbýavjících vlastností a implementujeme událost OrientationChanged
na Androidu. Potom konečně vytvoříme svůj pull request a odešleme jej pro review!
Appreciating the hard work you put into your website and detailed information you present.
It’s good to come across a blog every once in a
while that isn’t the same old rehashed information. Wonderful read!
I’ve bookmarked your site and I’m including your RSS feeds to my Google account.
Thank you, I have just been searching for info about this topic for a
while and yours is the best I have discovered so far.
But, what about the conclusion? Are you positive concerning the supply?