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!
Ukázka na WASM
Použijeme jednu z ukázek z mého minulého článku:
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
<StackPanel x:DefaultBindMode="OneWay"> | |
<TextBox Text="{x:Bind ViewModel.Name, Mode=TwoWay}" /> | |
<TextBlock Text="{x:Bind ViewModel.Name}" /> | |
</StackPanel> |
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.

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:
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
/// <summary> | |
/// The current DefaultBindMode for x:Bind bindings, as set by app code for the current Xaml subtree. | |
/// </summary> | |
private readonly Stack<string> _currentDefaultBindMode = new Stack<string>(new[] { "OneTime" }); |
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
:
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 IDisposable TrySetDefaultBindMode(XamlObjectDefinition xamlObjectDefinition) | |
{ | |
var definedMode = xamlObjectDefinition.Members.FirstOrDefault(m => m.Member.Name == "DefaultBindMode")?.Value?.ToString(); | |
if (definedMode == null) | |
{ | |
return null; | |
} | |
else if (!IsValid(definedMode)) | |
{ | |
throw new InvalidOperationException( | |
"Invalid value specified for attribute 'DefaultBindMode'. Accepted values are 'OneWay', 'OneTime', or 'TwoWay'."); | |
} | |
else | |
{ | |
_currentDefaultBindMode.Push(definedMode); | |
return new DisposableAction(() => _currentDefaultBindMode.Pop()); | |
} | |
bool IsValid(string mode) | |
{ | |
switch (mode) | |
{ | |
case "OneWay": | |
case "OneTime": | |
case "TwoWay": | |
return true; | |
default: | |
return false; | |
} | |
} | |
} |
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:
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
using (TrySetDefaultBindMode(xamlObjectDefinition)) | |
{ | |
... | |
} |
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:
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 GetDefaultBindMode() => _currentDefaultBindMode.Peek(); |
Metoda GetDefaultBindMode()
je pak volána při stavbě vyhodnocovací funkce x:Bind
:
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
var modeMember = bindNode.Members | |
.FirstOrDefault(m => m.Member.Name == "Mode")?.Value?.ToString() ?? GetDefaultBindMode(); |
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!