2010-05-26 4 views
11

Sto cercando di trovare un'alternativa più pulita (che sia idiomatica per Scala) al tipo di cosa che si vede con il data-binding in WPF/silverlight data-binding - cioè, implementando INotifyPropertyChanged. Per prima cosa, alcuni sottofondi:proprietà idiomatiche ha cambiato la notifica in scala?

Nelle applicazioni .Net WPF o silverlight, si ha il concetto di associazione dati bidirezionale (ovvero, vincolare il valore di qualche elemento dell'interfaccia utente a una proprietà .net del DataContext in un modo in cui le modifiche all'elemento dell'interfaccia utente influiscono sulla proprietà e viceversa. Un modo per abilitare questo è implementare l'interfaccia INotifyPropertyChanged nel DataContext. Sfortunatamente, questo introduce molto codice di codice per qualsiasi proprietà aggiunta a "ModelView" . "tipo Ecco come potrebbe apparire a Scala:

trait IDrawable extends INotifyPropertyChanged 
{  
     protected var drawOrder : Int = 0 
     def DrawOrder : Int = drawOrder 
     def DrawOrder_=(value : Int) { 
      if(drawOrder != value) { 
        drawOrder = value 
        OnPropertyChanged("DrawOrder") 
      } 
     } 

     protected var visible : Boolean = true 
     def Visible : Boolean = visible 
     def Visible_=(value: Boolean) = { 
      if(visible != value) { 
        visible = value 
        OnPropertyChanged("Visible") 
      } 
     } 
     def Mutate() : Unit = { 
      if(Visible) { 
       DrawOrder += 1 // Should trigger the PropertyChanged "Event" of INotifyPropertyChanged trait 
      } 
     } 
} 

Per motivi di spazio, supponiamo il tipo INotifyPropertyChanged è un tratto che gestisce un elenco di callback di tipo (AnyRef, String) => Unità e OnPropertyChanged è un metodo che richiama tutte quelle richiamate, passando "questo" come AnyRef e la stringa passata). Questo sarebbe solo un evento in C#.

Si può immediatamente vedere il problema: questo è un sacco di codice boilerplate per solo due proprietà. Ho sempre voluto scrivere qualcosa di simile a questo, invece:

trait IDrawable 
{ 
     val Visible = new ObservableProperty[Boolean]('Visible, true) 
     val DrawOrder = new ObservableProperty[Int]('DrawOrder, 0) 
     def Mutate() : Unit = { 
      if(Visible) { 
       DrawOrder += 1 // Should trigger the PropertyChanged "Event" of ObservableProperty class 
      } 
     } 
} 

So che posso facilmente scrivere in questo modo, se ObservableProperty [T] ha valore/VALUE_ = metodi (questo è il metodo che sto utilizzando ora):

trait IDrawable { 
     // on a side note, is there some way to get a Symbol representing the Visible field 
     // on the following line, instead of hard-coding it in the ObservableProperty 
     // constructor? 
     val Visible = new ObservableProperty[Boolean]('Visible, true) 
     val DrawOrder = new ObservableProperty[Int]('DrawOrder, 0) 
     def Mutate() : Unit = { 
      if(Visible.Value) { 
       DrawOrder.Value += 1 
      } 
     } 
} 

// given this implementation of ObservableProperty[T] in my library 
// note: IEvent, Event, and EventArgs are classes in my library for 
// handling lists of callbacks - they work similarly to events in C# 
class PropertyChangedEventArgs(val PropertyName: Symbol) extends EventArgs("") 
class ObservableProperty[T](val PropertyName: Symbol, private var value: T) { 
    protected val propertyChanged = new Event[PropertyChangedEventArgs] 
    def PropertyChanged: IEvent[PropertyChangedEventArgs] = propertyChanged 
    def Value = value; 
    def Value_=(value: T) { 
     if(this.value != value) { 
      this.value = value 
      propertyChanged(this, new PropertyChangedEventArgs(PropertyName)) 
     } 
    } 
} 

Ma esiste un modo per implementare la prima versione con impliciti o qualche altra caratteristica/linguaggio di Scala per far funzionare le istanze ObservableProperty come se fossero regolari "proprietà" a Scala, senza bisogno di chiamare i metodi di valore? L'unica altra cosa che posso pensare è qualcosa di simile, che è più dettagliato di uno di queste due versioni, ma è ancora meno prolissa rispetto all'originale:

trait IDrawable {  
    private val visible = new ObservableProperty[Boolean]('Visible, false) 
    def Visible = visible.Value 
    def Visible_=(value: Boolean): Unit = { visible.Value = value } 

    private val drawOrder = new ObservableProperty[Int]('DrawOrder, 0) 
    def DrawOrder = drawOrder.Value 
    def DrawOrder_=(value: Int): Unit = { drawOrder.Value = value } 

    def Mutate() : Unit = { 
    if(Visible) { 
     DrawOrder += 1 
    } 
    } 
} 
+3

Questo documento potrebbe interessarti: http://www.ganguin.net/frp2d.pdf –

+0

Vedere anche http: // stackoverflow.it/questions/1054179/functional-reactive-programming-in-scala Nessun successo finora. Sfortunatamente, questa è stata una decisione di design scarsa in Scala. – thSoft

risposta

2

non ho potuto affermare che si tratta di una proprietà canonica cambiamento quadro a Scala, ma ho usato una classe come questo prima:

abstract class Notifier[T,U](t0: T) { 
    import java.util.concurrent.atomic.AtomicReference 
    import scala.actors.OutputChannel 
    type OCUT = OutputChannel[(U,AtomicReference[T])] 
    val data = new AtomicReference[T](t0) 
    def id: U 
    protected var callbacks = Nil:List[T => Unit] 
    protected var listeners = Nil:List[OCUT] 
    def apply() = data.get 
    def update(t: T) { 
    val told = data.getAndSet(t) 
    if (t != told) { 
     callbacks.foreach(_(t)) 
     listeners.foreach(_ ! (id,data)) 
    } 
    } 
    def attend(f: T=>Unit) { callbacks ::= f } 
    def attend(oc: OCUT) { listeners ::= oc } 
    def ignore(f: T=>Unit) { callbacks = callbacks.filter(_ != f) } 
    def ignore(oc: OCUT) { listeners = listeners.filter(_ != oc) } 
} 

La motivazione per la creazione di questa classe era che volevo un modo thread-safe flessibile per reagire ai cambiamenti, che questo fornisce (in quanto offre sia callback che messaggi push agli attori).

Mi sembra - a meno che non mi fraintenda esattamente quello che vuoi perché non ho avuto l'occasione di imparare le cose di WPF/Silverlight - che questo può implementare tutto quello che vuoi e altro ancora.

Per esempio,

class IDrawable extends SomethingWithOnPropertyChanged { 
    val drawOrder = new Notifier[Int,Symbol](0) { def id = 'DrawOrder } 
    val visible = new Notifier[Boolean,Symbol](false) { def id = 'Visible } 
    drawOrder.attend((i:Int) => OnPropertyChanged(drawOrder.id)) 
    def mutate { 
    if (visible()) drawOrder() += 1 
    } 
} 

dovrebbe essere più o meno equivalente a quello che si vuole. (Ancora una volta, non sono sicuro di quanto tu voglia essere flessibile, potresti creare una serie di simboli -> notificatori che dovresti cercare con un metodo apply in modo che l'obiettivo abbia un tempo più semplice per fare qualcosa quando diventa il simbolo DrawOrder.)

L'unica differenza significativa dal tuo utilizzo è che il Notifier utilizza i suoi metodi apply/update per salvare boilerplate; non devi scrivere ogni volta i metodi def xe def x_ =, ma devi usare() per l'accesso.