Domanda

Sto cercando di trovare un'alternativa più pulita (che è idiomatica a Scala) per il genere di cosa che si vede con associazione dati in WPF / Silverlight associazione dati - che è, in attuazione INotifyPropertyChanged. In primo luogo, alcuni retroscena:

In Net WPF o applicazioni Silverlight, si ha il concetto di due vie di associazione dati (che è, vincolante il valore di qualche elemento dell'interfaccia utente a una proprietà .net del DataContext in modo tale che i cambiamenti a l'elemento dell'interfaccia utente influenzano la proprietà, e viceversa. un modo per consentire questo è quello di implementare l'interfaccia INotifyPropertyChanged nel vostro DataContext. Purtroppo, questo introduce un sacco di codice boilerplate per qualsiasi proprietà si aggiunge al tipo "modelview". 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 è una caratteristica che gestisce un elenco di callback di tipo (AnyRef, String) => Unità, e che OnPropertyChanged è un metodo che invoca tutti quei callback, passando "questa" come l'AnyRef, e il passato-in String). Questo sarebbe solo un evento in C #.

È possibile vedere immediatamente il problema: che è una tonnellata di codice standard solo per 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 usando 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 fare ObservableProperty casi funzionano come se fossero regolari "proprietà" in 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 
    }
  }
}
È stato utile?

Soluzione

Non ho potuto affermare che si tratta di un quadro modifica della proprietà canonica 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 e può spingere i messaggi agli attori).

Mi sembra - a meno che non sto fraintendendo esattamente quello che vuoi perché non ho avuto occasione di imparare la roba WPF / Silverlight -. Che questo può implementare tutto quello che volete e più

Ad 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. (Anche in questo caso, io non sono sicuro di come flessibile che si desidera che questo sia, è possibile creare un set di simbolo -> mappature notificatore che si dovrebbe guardare con un metodo di applicare in modo che il target potrebbe avere un tempo più facile di fare qualcosa quando si fa il simbolo ORDINEDIS.)

L'unica differenza significativa dal vostro utilizzo è che il notificante utilizza i suoi applicare / metodi di aggiornamento per salvare boilerplate; non c'è bisogno di scrivere def x e DEF x_ = metodi ogni volta, ma è necessario utilizzare () per l'accesso.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top