Question

Je suis en train de trouver une alternative plus propre (qui est idiomatiques à Scala) pour le genre de chose que vous voyez avec des données de liaison dans WPF / silverlight données contraignant - qui est, la mise en œuvre INotifyPropertyChanged. Tout d'abord un bref rappel historique:

Dans .Net WPF ou applications silverlight, vous avez le concept de deux voies de données de liaison (qui est, liant la valeur de certains éléments de l'interface utilisateur à une propriété du .net DataContext de telle sorte que les changements à l'élément d'interface utilisateur affecte la propriété, et vice versa. une façon de permettre est d'implémenter l'interface INotifyPropertyChanged dans votre DataContext. Malheureusement, cela introduit beaucoup de code boilerplate pour toute propriété que vous ajoutez au type « MODELVIEW ». Voici comment il 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 appelle tous les callbacks, « this » comme le AnyRef, et la chaîne passée-in). Ce serait juste un événement en C #.

Vous pouvez voir immédiatement le problème: qui est une tonne de code passe-partout pour deux propriétés. Je l'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 me sers 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 ObservableProperty comme si elles étaient des « propriétés » régulière 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 
    }
  }
}
Était-ce utile?

La solution

Je ne pouvais pas prétendre que c'est un cadre canonique de changement de propriété à Scala, mais je l'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 fournit ce (car il offre à la fois et peut pousser callbacks messages aux acteurs).

Il me semble - à moins que je suis malentendu exactement ce que vous voulez parce que je ne l'ai pas eu l'occasion d'apprendre les trucs WPF / Silverlight -. Que cela peut mettre en œuvre tout ce que vous voulez et plus

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 sais pas comment flexible vous voulez que ce soit, vous pouvez créer un ensemble de symboles -> mappings notificateur que vous rechercher avec une méthode apply de sorte que la cible aurait un temps plus facile de faire quelque chose quand il est le symbole ORDRETRACE.)

La seule différence significative de votre utilisation est que le notificateur utilise ses méthodes appliquer / mise à jour pour sauver boilerplate; vous ne devez pas écrire def x et def = x_ méthodes chaque fois, mais vous ne devez utiliser () pour y accéder.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top