x:DefaultBindMode v Uno Platform a jeho implementace

Xamarin Uno Platform WinUI XAML

4 years ago

Minulý týden jsem publikoval článek o x:DefaultBindMode v UWP XAMLu. Uno Platform, multiplatformní UWP/WinUI bridge pro Android, iOS a WebAssembly, v tu dobu ještě tuto funkci neimplementoval. Co se však stalo potom bylo doslova fascinující. Za pouhý jeden den byla tato funkce nejen implementována, ale i mergnuta (spolu s několika dalšími vylepšeními jako je BindBack!) a dostupná v novém prerelease balíčku na NuGetu. Wow!

Uno Platform

Ukázka na WASM

Použijeme jednu z ukázek z mého minulého článku:

A po spuštění na WebAssembly s Uno Platform to jednoduše funguje – TextBlock používá OneWay binding, zatímco TextBox jej explicitně přepisuje s TwoWay. To samé na všech ostatních Uno Platform targetech.

x:DefaultBindMode in action on WebAssembly!

x:DefaultBindMode v akci na WebAssembly!

Jak to dokázali?

Pro zajímavost se podívejme, jak tým Uno Platform dokázal za tak krátkou dobu x:DefaultBindMode implementovat. Vše, co tato funkčnost vyžaduje, je zahrnuto v třídě XamlFileGenerator, která zajišťuje generování C# kódu z XAMLu. x:DefaultBindMode funguje v jako "scope". To znamená, že deklarovaný mód na elementu platí pro něj a všechny jeho potomky, pokud není explicitně přepsán někde níže. Protože XAML soubor je parsován postupem "do hloubky", nejvhodnější datovou strukturou pro reprezentaci scope je zásobník. A to také Uno Platform používá pro udržování stavu zde:

Všimněme si, že je zásobník inicializován s hodnotou OneTime – to odpovídá výchozímu módu x:Bind v UWP. Takže pokud nikde nepoužijeme x:DefaultBindMode, OneTime je výchozí "fallback". Jak vložíme nový "výchozí mód" na zásobník? Zde přichází v činnost metoda TrySetDefaultBindMode:

Nejprve metoda přečte hodnotu atributu DefaultBindMode z definice elementu. Pokud atribut na elementu není deklarován, metoda skončí a vrací null. Pokud deklarování je, proběhne jeho validace pomocí metody IsValid. Pokud je hodnota DefaultBindMode validní, vložíme ji na vrchol zásobníku (vstupujeme do nového scope). Metoda pak vrací instanci DisposableAction, která implementuje rozhraní IDisposable a při jejím Dispose() vykoná předanou lambda funkci. V tomto případě se odebere vrchní hodnota ze zásobníku. Tento způsob implementace skvěle napomáhá čitelnosti kódu, protože to umožňuje jednoznačně ukázat, že metoda vytváři nový "scope", když ji voláme uvnitř using bloku:

Poznamenám ještě, že v případě že metoda vrátí null (pro případ kdy DefaultBindMode není nastaven), je prostě ignorován a using se nepokouší na konci bloku volat Dispose(). Získat aktuálního mód je možné přes jednoduchou metodu která volá pouze Peek aby vrátila vrchol zásobníku:

Metoda GetDefaultBindMode() je pak volána při stavbě vyhodnocovací funkce x:Bind:

Jednoduše řečeno, pokud výraz x:Bind explicitně určí Mode, má přednost. V opačném případě použije aktuálně platný DefaultBindMode. A to je vše, přátelé! Jak jsme mohli vidět, celá funkce byla implementována velmi elegantně a díky Uno Platform máme opravdu #WinUIEverywhere!