2010-05-26 19 views
11

Estoy tratando de encontrar una alternativa más limpia (que sea idiomática para Scala) al tipo de cosa que se ve con el enlace de datos en el enlace de datos WPF/silverlight, es decir, implementar INotifyPropertyChanged. Primero, algunos antecedentes:propiedad idiomática cambió la notificación en scala?

En las aplicaciones .Net WPF o Silverlight, tiene el concepto de vinculación de datos bidireccional (es decir, vincula el valor de algún elemento de la interfaz de usuario a una propiedad .net del DataContext en de tal forma que los cambios en el elemento UI afecten la propiedad, y viceversa. Una forma de habilitar esto es implementar la interfaz INotifyPropertyChanged en su DataContext. Desafortunadamente, esto introduce una gran cantidad de código repetitivo para cualquier propiedad que agregue a "ModelView". . "tipo Aquí es cómo puede ser que mire en 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 
      } 
     } 
} 

Por razones de espacio, vamos a suponer el tipo INotifyPropertyChanged es un rasgo que gestiona una lista de las devoluciones de llamada de tipo (AnyRef, cadena) => Unidad, y que OnPropertyChanged es un método que invoca todas las devoluciones de llamada, pasando "this" como AnyRef y la cadena pasada). Esto solo sería un evento en C#.

Puede ver el problema de inmediato: eso es un montón de código repetitivo para solo dos propiedades. Siempre he querido escribir algo como esto en su lugar:

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

sé que puedo escribir fácilmente de esta manera, si ObservableProperty [T] tiene un valor =/VALUE_ métodos (este es el método que estoy usando ahora):

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

pero ¿hay alguna manera de poner en práctica la primera versión utilizando implícitos o alguna otra característica/idioma de Scala para hacer la función casos ObservableProperty como si fueran normales "propiedades" en Scala, sin necesidad de llamar los métodos de valor? La única otra cosa que puedo pensar es algo como esto, que es más detallado que cualquiera de las dos versiones anteriores, pero todavía es menos detallado que el 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

Te puede interesar este artículo: http://www.ganguin.net/frp2d.pdf –

+0

Ver también http: // stackoverflow.com/questions/1054179/functional-reactive-programming-in-scala Hasta ahora no hay ningún éxito. Desafortunadamente, esta fue una decisión de diseño pobre en Scala. – thSoft

Respuesta

2

no podía afirmar que se trata de una marco de cambio de propiedad canónica en Scala, pero yo he usado una clase como esto antes:

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 motivación para la creación de esta clase era que quería una manera segura para los subprocesos flexible para reaccionar a los cambios, que esto proporciona (ya que ofrece ambas devoluciones de llamada y puede enviar mensajes a los actores).

Me parece a menos que malinterprete exactamente lo que quiere porque no he tenido la oportunidad de aprender las cosas de WPF/Silverlight, que esto puede implementar todo lo que quiera y más.

Por ejemplo,

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

debería ser más o menos equivalente a lo que desea. (Una vez más, no estoy seguro de cuán flexible quiere que sea esto; podría crear un conjunto de asignaciones de notificador de símbolos que podría buscar con un método de aplicación para que el objetivo tenga un momento más fácil de hacer algo cuando se pone el símbolo DrawOrder).

La única diferencia significativa con respecto a su uso es que el notificador utiliza sus métodos de aplicación/actualización para guardar el texto repetitivo; no tiene que escribir def x y def x_ = métodos cada vez, pero sí tiene que usar() para acceder.

Cuestiones relacionadas