11

我正在尝试找到一种更简洁的替代方案(对于 Scala 来说是惯用的),而不是您在 WPF/silverlight 数据绑定中看到的那种数据绑定 - 即实现 INotifyPropertyChanged。首先,一些背景:

在 .Net WPF 或 silverlight 应用程序中,您有双向数据绑定的概念(即,将 UI 的某些元素的值绑定到 DataContext 的 .net 属性,从而改变 UI 元素影响属性,反之亦然。启用此功能的一种方法是在 DataContext 中实现 INotifyPropertyChanged 接口。不幸的是,这为您添加到“ModelView”类型的任何属性引入了大量样板代码。这是它的外观在斯卡拉:

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

为了空间的缘故,我们假设 INotifyPropertyChanged 类型是一个特征,它管理类型为 (AnyRef, String) => Unit 的回调列表,并且 OnPropertyChanged 是一个调用所有这些回调的方法,将“this”作为 AnyRef 传递,以及传入的字符串)。这只是 C# 中的一个事件。

您可以立即看到问题所在:这只是两个属性的大量样板代码。我一直想写这样的东西:

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

我知道如果 ObservableProperty[T] 有 Value/Value_= 方法(这是我现在使用的方法),我可以很容易地写成这样:

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

但是有没有办法使用 Scala 的隐式或其他一些特性/习语来实现第一个版本,以使 ObservableProperty 实例的功能就像它们是 scala 中的常规“属性”一样,而无需调用 Value 方法?我能想到的唯一另一件事是这样的,它比上述两个版本中的任何一个都更冗长,但仍然比原来的更冗长:

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

1 回答 1

2

我不能声称这是 Scala 中的规范属性更改框架,但我以前使用过这样的类:

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

创建这个类的动机是我想要一种灵活的线程安全方式来响应变化,这提供了(因为它提供回调并且可以将消息推送到参与者)。

在我看来——除非我完全误解了你想要什么,因为我没有机会学习 WPF/Silverlight 的东西——这可以实现你想要的一切,甚至更多。

例如,

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

应该大致相当于你想要的。(同样,我不确定您希望它有多灵活;您可以创建一组符号 -> 通知器映射,您将使用 apply 方法查找这些映射,以便目标在获得时更容易做某事DrawOrder 符号。)

与您使用的唯一显着区别是通知程序使用其应用/更新方法来保存样板文件;您不必每次都编写 def x 和 def x_= 方法,但您必须使用 () 进行访问。

于 2010-05-27T15:38:18.283 回答