Статьи

Привязка объектов Scala к компонентам Swing

Если вы занимаетесь разработкой Swing-приложений, особенно типа корпоративного приложения, вы знаете много кода, который выглядит следующим образом:

    public class Person {
        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
     
        public void setName(String aName){
            String oldValue = name;
            name = aName;
            pcs.firePropertyChange("name", oldValue, aName);
        }
     
        public String getName(){
            return name;
        }

Имея такие свойства, вы можете привязать их к компонентам Swing с помощью JGoodies Binding следующим образом:

MyBean bean = new MyBean();
      BeanAdapter adapter = new BeanAdapter(bean, true);
      ValueModel stringModel = adapter.getValueModel("name");
      JTextField field = BasicComponentFactory.createTextField(stringModel);

Если вы не знаете JGoodies Binding, прочитайте эту статью как можно скорее .

Если вы делаете Swing-приложения, основанные на формах, это путь: не пишите Слушатели, связывайте компоненты.

Тем не менее, у меня огромная проблема с этим кодом. На самом деле несколько проблем:

  • Вы должны повторить имя свойства в четырех местах, и вы окажетесь в аду, если вы не сделаете это правильно: во имя установщика, в имени получателя, в первом аргументе для firePropertyChange и в обращении к структуре привязки.
  • Тривиальное свойство занимает 8 строк кода. Вот восемь строк кода. Или в зависимости от качества вашего кода, вероятно, от 1/100 до 1/10 ошибки, для тривиального свойства.
  • Помимо большого количества кода, он также имеет высокую степень дублирования, потому что это один и тот же шаблон снова и снова. И все же их не кажется разумным способом абстрагироваться от такого рода вещей.

Если вы хотите переключиться на другой язык, вы действительно можете абстрагироваться от этого. Это, конечно, неудивительно, так как должно быть легко написать язык, который предлагает поддержку для такого рода свойств. Но это не то, о чем я говорю. Я, конечно, говорю о маленьком совершенстве Скалы. Как насчет этого для определения класса со свойством:

 

    class Person extends PropertyOwner {
        val name : Property[String] = "smith"
    }

Обратите внимание, что существует только одна строка, определяющая свойство (включая установку его начального значения). Так как же будет выглядеть привязка к JTextField? Как это:

        val p = new Person
        val nameTextField = new JTextField()
        Binder.bind(p.name, nameTextField)

Обратите внимание, что я нигде не использую строковое «имя». Все просто нормальные, строго типизированные значения. Если я случайно использую firstName вместо name, компилятор будет жаловаться. Хитрость заключается в том, что я на самом деле использую объекты типа Property вместо полей, геттеров и сеттеров. Однако использование этой вещи выглядит (почти) совершенно естественно:

        val p = new Person
        p.name := "Alf"
        println(p.name())

Код для достижения этого довольно прост и короток. Ключ заключается в том, что у свойства есть метод apply, возвращающий его значение, и оператор: = в качестве замены для метода установки.

    class Property[T](var value : T) {
        var listeners = List[T => Unit]()
     
        def apply() = value
     
        def :=(aValue : T) {
            if (value != aValue) {
                value = aValue
                fireEvent
            }
        }
     
        def registerListener(l : T => Unit) {
            listeners = l :: listeners
        }
     
        private def fireEvent {
            listeners.foreach(_(value))
        }
    }
     
    object Property {
        implicit def apply[T](t : T)(implicit owner : PropertyOwner) : Property[T] =
            new Property(t : T)
     
        implicit def toT[T](p : Property[T]) : T = p()
    }
     
    trait PropertyOwner {
        implicit val THE_OWNER = this
    }
     
    object Binder {
        def bind(p : Property[String], textField : JTextField) {
            var locked = false
     
            initTextField
            syncFromTextFieldToProperty
            syncFromPropertyToTextField
     
            def initTextField = p() match {
                case null => textField.setText("")
                case _    => textField.setText(p().toString())
            }
     
            def syncFromPropertyToTextField {
                p.registerListener { value : String =>
                    if (textField.getText != value)
                        textField.setText(value)
                }
            }
     
            def syncFromTextFieldToProperty = textField.getDocument.addDocumentListener(new DocumentListener() {
                def changedUpdate(e : DocumentEvent) = updateProperty
                def insertUpdate(e : DocumentEvent) = updateProperty
                def removeUpdate(e : DocumentEvent) = updateProperty
            })
     
            def updateProperty = {
                p := textField.getText
            }
        }
    }

В настоящее время это всего лишь маленькая игрушка, если хотите, назовите ее доказательством концепции. Он работает только с JTextFields, и уведомление о событии может нуждаться в настройке, поскольку оно предоставляет только новое значение свойства прямо сейчас. Есть еще много идей, как использовать это как основу для создания красивого чистого кода с графическим интерфейсом Swing. Посмотрите этот блог, чтобы узнать больше идей и игрушек.

От http://blog.schauderhaft.de/2011/05/01/binding-scala-objects-to-swing-components/