Animační proxy

30. 11. 2011 23:39 (aktualizováno) zboj

V Cocoa můžeme každé instanci třídy NSView poslat zprávu animator, která vrátí objekt chovající se jako objekt původní, ale animující některé vlastnosti. Například setAlphaValue nastaví průhlednost prvku UI. Pokud použijeme setAlphaValue na animační objekt, nastaví se průhledost animovaně (s interpolovanými hodnotami). Protože více čtenářů zná pravděpodobně .NET než Cocoa, zde je jednoduchá ukázka, jak takový proxy objekt implementovat v C#.

Proxy třídu nazvěme prostě Animator. Ta bude dědit ze System.Dynamic.DynamicObject:

class Animator<T> : DynamicObject where T : Control {

Dále potřebujeme instanční proměnnou pro vlastní objekt, konstruktor, statickou metodu pro vytvoření proxy (abychom mohli použít při deklaraci var místo dynamic) a pro jistotu také implicitně přetížíme operátor přetypování, pokud by někdo předával proxy do nějaké metody jako parametr typu T (tedy typu původního objektu):

private T underlyingObject; public Animator(T obj) { underlyingObject = obj; } public static dynamic Wrap(T obj) { return new Animator<T>(obj); } public static implicit operator T(Animator<T> animator) { return animator.underlyingObject; }

Pro práci s vlastnostmi nyní musíme implementovat metody Try…:

public override bool TryGetMember(GetMemberBinder binder, out object result) { result = underlyingObject.GetType().GetProperty(binder.Name).GetValue(underlyingObject, null); return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { underlyingObject.GetType().GetProperty(binder.Name).SetValue(underlyingObject, value, null); return true; }

Tím máme hotové jádro proxy objektu a místo

var form = ...; form.Opacity = 0.5;

můžeme použít

var form = Animator<Form>.Wrap(...); form.Opacity = 0.5;

se stejným efektem. Každému alespoň průměrnému programátorovi je teď jistě zřejmé, jak přidat animaci. Do metody TrySetMember přidáme

if (binder.Name == "Opacity") { ... }

a nastavení vlastnosti Opacity ošetříme zvlášť. Abychom neblokovali vlákno pro UI, vytvoříme nové vlákno a v cyklu postupně nastavujeme interpolované hodnoty (protože kód poběží v jiném vlákně, je nutné použít Invoke a MethodInvoker). Aby byla tato třída obecně použitelná, musíme samozřejmě doplnit ostatní metody Try… a přidat alespoň metodu SetDuration, abychom mohli nastavit délku animace.

Každému je teď jistě zřejmé, že vlastnosti, pro které z nějakého důvodu nemáme kód pro animaci, fungují zcela stejně jako na původním objektu, například form.Size = new Size(100, 100).

Sdílet