Purisme en XAML

 
06 mei 2011

In mijn post over RoutedCommands maakte ik een opmerking dat het zo jammer was dat een Commandbinding naar je code-behind wijst: voor puristen zoals ik en MVVM-aanhangers is een lege code-behind bijna een doel op zichzelf. Maar laat ik hier eens de advocaat van de duivel spelen.

Want laten we wel wezen – de code-behind is er nou eenmaal, dus waarom zou je er geen gebruik van maken…? Uiteindelijk is wat je meestal aan het doen bent, een grafisch componentje of scherm samenstellen, dat meer doet dan de som van de losse onderdelen. Knoppen voeren acties uit, selecties van de ene dropdown beïnvloeden de keuzemogelijkheden binnen andere dropdowns, enz. Weliswaar kan je in XAML een hele hoop zaken declaratief regelen, tot aan verschillende animaties en geluiden aan toe (en dat automatisch in reactie op toestandswijzigingen elders in je component of in je DataContext – althans voor zover de daarin gerefereerde objecten change notification ondersteunen). Maar zo krachtig als een volwassen programmeertaal als C# is het niet.

Dus als je voorbij de beperkingen van XAML wilt gaan – en het bouwen van een eventhandler zoals je die nodig hebt bij CommandBindings van RoutedEvents is nou eenmaal niet iets wat je altijd in je XAML wil of kan doen – dan zul je toch een stukje code moeten schrijven. De vraag is dus: waar laat je die code? Nou zijn er eigenlijk maar twee plekken die in aanmerking komen: de code-behind (zoals het nu geregeld is) of in je datacontext. Laten we eens kijken wat er zou gebeuren als we de CommandBinding-gerelateerde eventhandlers ook in de datacontext zouden zetten.

Dat voelt wel aan als een hele vieze constructie: tijdens het compileren is het type van je DataContext-object niet bekend, laat staan dat de compiler kan bepalen of er een methode met de juiste naam en juiste signature bestaat. Er is zo te zien dus een goeie mogelijkheid dat hier iets misgaat en dat de CommandBinding de lege ruimte in wijst: de WPF-motor moet op runtime reflectie gaan uitvoeren op je DataContext om de juiste methode (met de juiste naam en juiste parametertypes) te achterhalen, en als die niet bestaan heb je pech gehad.

Maar waarom voelt het dan niet als probleem als je vanuit je XAML naar een property van je DataContext verwijst? Want ook in dat geval heb je, om precies dezelfde reden, reflectie nodig, en kan er hetzelfde fout gaan. In feite is een property juist nog erger dan het gebruik van alleen een methode: want in feite is een property zelfs een combinatie van twee aparte methodes tegelijkertijd, met nog eens verschillende signaturen, en met dan ook nog een sausje erover.

Ik merk in de praktijk dat er toch vaak een één-op-één-relatie bestaat tussen een View en zijn ViewModel; zelden of nooit wordt een ViewModel hergebruikt in meerdere schermen. Ik zou vaak een (optionele) statische verwijzing van de View naar zijn ViewModel willen hebben, en een irritant gevolg van het ontbreken van zo’n verwijzing is dat je niet met een druk op de knop (F12, Go To Definition) van een binding-declaratie in je XAML naar de bijbehorende property in het ViewModel kan navigeren, terwijl dat toch erg vaak wel handig zou zijn. Of in elk geval een interface die de benodigde properties declareert. (Andersom kan je overigens wel middels een ResourceDictionary de weg andersom afleggen: dat je van een bepaald type aangeeft welke XAML hij daaroverheen moet leggen om objecten van dat type te tonen.) Al met al zie je dus vaak dat een View al redelijk is toegespitst op het werken met één soort data, of één setje gerelateerde data. Zou de View dan niet beter ook gewoon kennis kunnen hebben van hoe die data eruitzien?

Dus als de View en het ViewModel zo dicht aan elkaar gebonden zijn, en er toch al reflectie ingezet wordt, waarom dan dus niet ook CommandBindings in je ViewModel kunnen opnemen? Uiteindelijk komt het denk ik toch weer neer op dat oude thema waar we het bij objectgeoriënteerd programmeren wel vaker over hebben: scheiding van verantwoordelijkheden. De View heeft daarbij domweg de verantwoordelijkheid om dingen op het scherm te tonen en de interactie binnen dat scherm te regelen, en het ViewModel is meer de tussenschakel tussen die View en (de rest van) het domein, en reflecteert dus interacties die binnen het domein plaatsvinden die voor de View van belang zijn. De View kan die dan hieruit oppikken middels een databinding, en het ViewModel hoeft dus ook niet direct de View aansturen.

Dus uiteindelijk wil je überhaupt niet dat je ViewModel kennis heeft van de View. Als er dus als gevolg van het Executed-event dingen direct in de View aangepast moeten worden, dan zou het een slechte zaak zijn als die via het ViewModel moeten lopen: die zou daarmee expliciete kennis van de View moeten krijgen om bijv. een button uit te grijzen of een animatie te starten o.i.d. Dit soort functionaliteit hoort typisch in de View terecht en moet daar dus ook blijven: oftewel in de code-behind.

En mochten er toch als gevolg van dat Command dingen in de ViewModel aangepast moeten worden – de DataContext en alle Bindings zijn ook vanuit de code-behind beschikbaar. Dus in feite is het kinderspel om vanuit je code-behind wijzigingen of andere functionaliteit in je ViewModel aan te zwengelen, zonder de View (en dus de code-behind) expliciet van het ViewModel af te laten hangen. Laat de CommandBindings dus maar lekker naar naar de code-behind wijzen.

En toch knaagt er iets. Maar ja, ik ben dan ook een purist.


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development

Tags: , , , , ,