2010-05-26 5 views
11

J'essaie de trouver une alternative plus propre (idiomatique à Scala) au type de chose que vous voyez avec la liaison de données dans la liaison de données WPF/silverlight - c'est-à-dire l'implémentation de INotifyPropertyChanged. En arrière-plan:La propriété idiomatique a changé de notification dans scala?

Dans .NET WPF ou applications Silverlight, vous avez le concept de liaison de données bidirectionnelle (c'est-à-dire, la liaison de la valeur d'un élément de l'interface utilisateur à une propriété .net du DataContext dans Une façon d'activer l'interface INotifyPropertyChanged dans votre DataContext, malheureusement, cela introduit beaucoup de code standard pour toutes les propriétés que vous ajoutez au "ModelView". . «type Voici comment cela pourrait ressembler à 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 
      } 
     } 
} 

Pour des raisons d'espace, supposons que le type INotifyPropertyChanged est un trait qui gère une liste des callbacks de type (AnyRef, String) => Unit, et que OnPropertyChanged est une méthode qui invoque tous ces callbacks, en passant "this" comme AnyRef, et la chaîne passée. Ce serait juste un événement en C#.

Vous pouvez immédiatement voir le problème: c'est une tonne de code standard pour deux propriétés. J'ai toujours voulu écrire quelque chose comme ceci:

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

Je sais que je peux facilement l'écrire comme ça, si ObservableProperty [T] a une valeur/VALUE_ = méthodes (ce qui est la méthode que je utilise maintenant):

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

Mais est-il possible de mettre en œuvre la première version en utilisant implicits ou une autre caractéristique/idiome de Scala pour faire fonctionner les instances de ObservableProperty comme si elles étaient des « propriétés » régulières dans scala, sans avoir besoin d'appeler les méthodes de la valeur? La seule autre chose que je peux penser à quelque chose comme ça, ce qui est plus prolixe que l'une des deux versions ci-dessus, mais il est encore moins prolixe que l'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

Ce document peut vous intéresser: http://www.ganguin.net/frp2d.pdf –

+0

Voir aussi http: // stackoverflow.com/questions/1054179/functional-reactive-programming-in-scala Aucun succès pour l'instant. Malheureusement, ce fut une mauvaise décision de conception à Scala. – thSoft

Répondre

2

Je ne pouvais pas prétendre que c'est un cadre de changement de propriété canonique à Scala, mais j'ai utilisé une classe comme ça avant:

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 motivation pour la création de cette classe était que je voulais un moyen de fil de sécurité flexible pour réagir aux changements, qui cette offre (car il livre les deux callbacks et peut envoyer des messages aux acteurs). Il me semble - à moins que je ne comprenne pas exactement ce que vous voulez parce que je n'ai pas eu l'occasion d'apprendre les trucs de WPF/Silverlight - que cela peut implémenter tout ce que vous voulez et plus encore.

Par exemple,

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

devrait être à peu près équivalent à ce que vous voulez. (Encore une fois, je ne suis pas sûr de la flexibilité que vous souhaitez, vous pouvez créer un ensemble de mappages de symboles -> notifier que vous rechercheriez avec une méthode d'application afin que la cible ait plus de facilité à faire quelque chose. le symbole DrawOrder.)

La seule différence significative par rapport à votre utilisation est que le Notifier utilise ses méthodes apply/update pour sauvegarder les éléments standard; vous n'avez pas à écrire des méthodes def x et def x_ = à chaque fois, mais vous devez utiliser() pour l'accès.

Questions connexes