2010-05-26 10 views
11

Ich versuche eine sauberere Alternative (die zu Scala idiomatisch ist) zu der Art von Dingen zu finden, die Sie bei Datenbindung in WPF/silverlight Datenbindung sehen - das heißt, INotifyPropertyChanged zu implementieren. Zuerst einige Hintergrund:idiomatische Eigenschaft geändert Benachrichtigung in scala?

In .Net WPF oder Silverlight-Anwendungen haben Sie das Konzept der Zwei-Wege-Datenbindung (dh Bindung der Wert eines Elements der Benutzeroberfläche an eine .net-Eigenschaft des DataContext in Auf diese Weise wirken sich Änderungen am Oberflächenelement auf die Eigenschaft aus und umgekehrt. Eine Möglichkeit, dies zu aktivieren, besteht darin, die INotifyPropertyChanged-Schnittstelle in Ihrem DataContext zu implementieren. Dies führt leider zu einer großen Menge Code für jede Eigenschaft, die Sie dem "ModelView" hinzufügen . "Typ Hier ist, wie es in Scala aussehen könnte:

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 
      } 
     } 
} 

aus Gründen der Raum, lassen sie uns die INotifyPropertyChanged Typ nehmen ist eine Eigenschaft, die eine Liste der Rückrufe vom Typ verwaltet (AnyRef, String) => Unit, und OnPropertyChanged ist eine Methode, die all diese Callbacks aufruft und "this" als AnyRef und den übergebenen String weiterleitet. Dies wäre nur ein Ereignis in C#.

Sie können sofort das Problem sehen: das ist eine Tonne Standardcode für nur zwei Eigenschaften.

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 
      } 
     } 
} 

Ich weiß, dass ich es leicht schreiben kann wie diese, wenn ObservableProperty [T] hat Wert/VALUE_ = Methoden (dies ist die Methode, die ich bin mit: Ich habe immer statt, so etwas schreiben will jetzt):

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)) 
     } 
    } 
} 

Aber ist es eine Möglichkeit, die erste Version mit implicits oder einem anderen Funktion/Idiom der Scala zu implementieren, um ObservableProperty Instanzen Funktion als wären sie normale „Eigenschaften“ in scala, ohne dass nennen zu machen die Wertmethoden? Das einzige, was ich denken kann, ist so etwas wie dieses, die als eine der beiden oben genannten Versionen ausführlicher ist, ist aber immer noch weniger ausführlich als das Original:

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

Dieses Papier könnte Sie interessieren: http://www.ganguin.net/frp2d.pdf –

+0

Siehe auch http: // stackoverflow.com/questions/1054179/funktional-reaktive-programmierung-in-scala Bisher kein Erfolg. Leider war das eine schlechte Designentscheidung in Scala. – thSoft

Antwort

2

Ich konnte nicht behaupten, dass dies eine ist

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) } 
} 

Die Motivation für die Erstellung dieser Klasse war, dass ich eine flexible Thread-sichere Art und Weise auf Veränderungen reagieren wollte, die dies ermöglicht (: kanonische Eigenschaftsänderung Rahmen in Scala, aber ich habe eine Klasse wie diese vor der Verwendung da es sowohl Callbacks liefert als auch Nachrichten an Schauspieler senden kann).

Es scheint mir - es sei denn, ich bin Missverständnis genau das, was Sie wollen, weil ich die Gelegenheit nicht gehabt haben, die WPF/Silverlight Dinge zu lernen -, dass dies alles umsetzen können Sie und mehr wollen.

Zum Beispiel

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 
    } 
} 

sollte etwa gleich sein, was Sie wollen. (Auch hier bin ich mir nicht sicher, wie flexibel das sein soll. Sie könnten eine Reihe von Symbol-> Notifier-Zuordnungen erstellen, die Sie mit einer Apply-Methode nachschlagen würden, damit das Ziel leichter etwas tun kann, wenn es dazu kommt das DrawOrder-Symbol.)

Der einzige wesentliche Unterschied zu Ihrer Verwendung besteht darin, dass der Notifier seine Anwendungs-/Aktualisierungsmethoden verwendet, um Standardwerte zu speichern. Sie müssen nicht jedesmal def x und def x_ = Methoden schreiben, aber Sie müssen() für den Zugriff verwenden.

Verwandte Themen