Можно ли использовать фабричные методы FXMLLoader для пользовательских классов?

В главе 3 книги «Pro JavaFX 8: Полное руководство по созданию настольных, мобильных и встроенных клиентов Java» приведен пример, иллюстрирующий, как указывать объекты непосредственно в файле FXML.

Вы можете найти полный файл FXML вместе с другими файлами в примере в конце этого поста.

Вот фрагмент, о котором я говорю. Поле sizes использует атрибут fx:factory, чтобы указать, что фабричный метод Utilities.createList() должен использоваться для создания списка целых чисел, который затем заполняется тремя целыми числами.

<sizes>
    <Utilities fx:factory="createMyCollection">
        <Integer fx:value="1"/>
        <Integer fx:value="2"/>
        <Integer fx:value="3"/>
    </Utilities>
</sizes>

Вот Утилиты.java:

package projavafx.fxmlbasicfeatures;

import java.util.ArrayList;
import java.util.List;

public class Utilities {
    public static final Double TEN_PCT = 0.1d;
    public static final Double TWENTY_PCT = 0.2d;
    public static final Double THIRTY_PCT = 0.3d;

    public static List<Integer> createList() {
        return new ArrayList<>();
    }
}

У меня вопрос: каков общий механизм использования этих фабричных методов?

Я хотел бы понять, как FXMLLoader знает, что три целых числа должны быть добавлены к созданному объекту с помощью метода add. Естественно, он должен каким-то образом знать о List или, возможно, Collection, но где указано это знание? Он встроен в FXMLLoader? Если да, то как такие фабричные методы могут быть предоставлены для определяемых пользователем классов?

На самом деле я пытался использовать его с пользовательским классом. Я добавил следующий фрагмент в Utilities.java, который создает класс MyCollection с единственным методом add(Integer) и определяет метод Utilities.createMyCollection:

public class Utilities {
    (...)
    public static class MyCollection {
        private List<Integer> myList = new LinkedList<>();
        public void add(Integer o) {
            myList.add(o);
        }
        public String toString() {
            return myList.toString();
        }
    }

    public static MyCollection createMyCollection() {
    return new MyCollection();    
    }
    (...)
}    

Однако, когда я заменил createMyCollection в файле FXML, я получил сообщение «MyCollections не имеет свойства по умолчанию. Поместите содержимое MyCollection в элемент свойства».

что заставляет меня задаться вопросом, как я могу объявить свойство по умолчанию для определяемого пользователем класса и как List уже имеет его.

Вот все файлы (кроме Utilities.java выше):

FXMLBasicFeatures.fxml:

<?import javafx.scene.paint.Color?>
<?import projavafx.fxmlbasicfeatures.FXMLBasicFeaturesBean?>
<?import projavafx.fxmlbasicfeatures.Utilities?>
<?import java.lang.Double?>
<?import java.lang.Integer?>
<?import java.lang.Long?>
<?import java.util.HashMap?>
<?import java.lang.String?>
<FXMLBasicFeaturesBean name="John Smith"
                       flag="true"
                       count="12345"
                       xmlns:fx="http://javafx.com/fxml/1">
    <address>12345 Main St.</address>
    <foreground>#ff8800</foreground>
    <background>
        <Color red="0.0" green="1.0" blue="0.5"/>
    </background>
    <price>
        <Double fx:value="3.1415926"/>
    </price>
    <discount>
        <Utilities fx:constant="TEN_PCT"/>
    </discount>
    <sizes>
        <Utilities fx:factory="createList">
            <Integer fx:value="1"/>
            <Integer fx:value="2"/>
            <Integer fx:value="3"/>
        </Utilities>
    </sizes>
    <profits>
        <HashMap q1="1000" q2="1100" q3="1200" a4="1300"/>
    </profits>
    <fx:define>
        <Long fx:id="inv" fx:value="9765625"/>
    </fx:define>
    <inventory>
        <fx:reference source="inv"/>
    </inventory>
    <products>
        <String fx:value="widget"/>
        <String fx:value="gadget"/>
        <String fx:value="models"/>
    </products>
    <abbreviations CA="California" NY="New York" FL="Florida" MO="Missouri"/>

</FXMLBasicFeaturesBean>

FXMLBasicFeaturesBean.java:

package projavafx.fxmlbasicfeatures;

import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FXMLBasicFeaturesBean {
    private String name;
    private String address;
    private boolean flag;
    private int count;
    private Color foreground;
    private Color background;
    private Double price;
    private Double discount;
    private List<Integer> sizes;
    private Map<String, Double> profits;
    private Long inventory;
    private List<String> products = new ArrayList<String>();
    private Map<String, String> abbreviations = new HashMap<>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Color getForeground() {
        return foreground;
    }

    public void setForeground(Color foreground) {
        this.foreground = foreground;
    }

    public Color getBackground() {
        return background;
    }

    public void setBackground(Color background) {
        this.background = background;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Double getDiscount() {
        return discount;
    }

    public void setDiscount(Double discount) {
        this.discount = discount;
    }

    public List<Integer> getSizes() {
        return sizes;
    }

    public void setSizes(List<Integer> sizes) {
        this.sizes = sizes;
    }

    public Map<String, Double> getProfits() {
        return profits;
    }

    public void setProfits(Map<String, Double> profits) {
        this.profits = profits;
    }

    public Long getInventory() {
        return inventory;
    }

    public void setInventory(Long inventory) {
        this.inventory = inventory;
    }

    public List<String> getProducts() {
        return products;
    }

    public Map<String, String> getAbbreviations() {
        return abbreviations;
    }

    @Override
    public String toString() {
        return "FXMLBasicFeaturesBean{" +
            "name='" + name + '\'' +
            ",\n\taddress='" + address + '\'' +
            ",\n\tflag=" + flag +
            ",\n\tcount=" + count +
            ",\n\tforeground=" + foreground +
            ",\n\tbackground=" + background +
            ",\n\tprice=" + price +
            ",\n\tdiscount=" + discount +
            ",\n\tsizes=" + sizes +
            ",\n\tprofits=" + profits +
            ",\n\tinventory=" + inventory +
            ",\n\tproducts=" + products +
            ",\n\tabbreviations=" + abbreviations +
            '}';
    }
}

FXMLBasicFeaturesMain.java:

package projavafx.fxmlbasicfeatures;

import javafx.fxml.FXMLLoader;

import java.io.IOException;

public class FXMLBasicFeaturesMain {
    public static void main(String[] args) throws IOException {
        FXMLBasicFeaturesBean bean = FXMLLoader.load(
            FXMLBasicFeaturesMain.class.getResource(
                "/projavafx/fxmlbasicfeatures/FXMLBasicFeatures.fxml")
        );
        System.out.println("bean = " + bean);
    }
}

person user118967    schedule 15.05.2015    source источник
comment
Реализация java.util.List рассматривается FXMLLoader как особый случай. Ваш класс MyCollection реализует List?   -  person James_D    schedule 15.05.2015
comment
Ах, спасибо, это то, что я хотел знать, была ли встроена поддержка List. MyCollection не реализует List намеренно; Я хотел посмотреть, можно ли добавить этот тип поддержки для произвольного класса.   -  person user118967    schedule 16.05.2015
comment
Если он содержит список, и вы готовы открыть весь список с помощью метода get, вы все равно можете это сделать. Я опубликую ответ с этим, когда вернусь за компьютер, если это будет полезно   -  person James_D    schedule 16.05.2015
comment
Спасибо, я, вероятно, должен сделать это сам в качестве упражнения. Моя проблема заключалась в том, что javadoc FXMLLoader практически ничего не говорит. Но я нашел страницу Oracle Introduction to FXML, которая кажется довольно полной.   -  person user118967    schedule 16.05.2015
comment
Документация для FXMLLoader довольно плачевна. Вы можете проголосовать за эту проблему.   -  person James_D    schedule 16.05.2015
comment
Спасибо за отличный ответ, и да, я пойду голосовать.   -  person user118967    schedule 16.05.2015


Ответы (1)


На самом деле здесь возникает несколько разных проблем. Как вы знаете, основное использование заключается в том, что FXMLLoader ищет свойства в классическом стиле с помощью схем именования JavaBean. Итак, если у вас есть класс

public class Bean {

    private String text ;

    public void setText(String text) {
        this.text = text ;
    }

    public String getText() {
        return text ;
    }
}

Затем (поскольку у класса есть конструктор по умолчанию без аргументов), вы можете создать экземпляр Bean в FXML:

<Bean>

и вы можете вызвать метод setText, сославшись на свойство text как на атрибут:

<Bean text="Some text"/>

или как элемент свойства:

<Bean>
    <text>
        <String fx:value="Some text"/>
    </text>
</Bean>

Экземпляры java.util.List имеют особое отношение. Если имя свойства соответствует свойству List только для чтения: то есть свойству типа java.util.List, которое имеет метод get..., но не имеет метода set..., дочерние узлы в FXML будут переданы соответствующему методу List экземпляров add(...). .

Итак, если мы добавим такое свойство к Bean:

import java.util.List ;
import java.util.ArrayList ;

public class Bean {

    private String text ;

    private List<String> elements ;

    public Bean() {
        this.elements = new ArrayList<>();
    }

    public List<String> getElements() {
        return elements ;
    }

    public void setText(String text) {
        this.text = text ;
    }

    public String getText() {
        return text ;
    }
}

Затем мы можем заполнить список в FXML:

<Bean text="Some text">
  <elements>
    <String fx:value="One"/>
    <String fx:value="Two"/>
    <String fx:value="Three"/>
  </elements>
<Bean>

Другая проблема, на которую вы ссылаетесь, - это «свойство по умолчанию». Вы можете указать свойство класса по умолчанию, используя аннотацию @DefaultProperty для класса и указав имя свойства, которое должно считаться значением по умолчанию:

import java.util.List ;
import java.util.ArrayList ;

@DefaultProperty("text")
public class Bean {

    private String text ;

    private List<String> elements ;

    public Bean() {
        this.elements = new ArrayList<>();
    }

    public List<String> getElements() {
        return elements ;
    }

    public void setText(String text) {
        this.text = text ;
    }

    public String getText() {
        return text ;
    }
}

Теперь, если вы укажете дочерние элементы элемента экземпляра <Bean> в FXML без указания свойства, они будут использоваться в качестве значений для свойства по умолчанию:

<Bean>
  <String fx:value="Some Text"/>
</Bean>

вызовет setText("Some Text") для экземпляра Bean.

И, конечно же, вы можете объединить эти идеи и сделать экземпляр List свойством по умолчанию (по сути, именно так работают контейнеры макета: Pane определяет "children" как свойство по умолчанию):

import java.util.List ;
import java.util.ArrayList ;

@DefaultProperty("elements")
public class Bean {

    private String text ;

    private List<String> elements ;

    public Bean() {
        this.elements = new ArrayList<>();
    }

    public List<String> getElements() {
        return elements ;
    }

    public void setText(String text) {
        this.text = text ;
    }

    public String getText() {
        return text ;
    }
}

и теперь вы можете сделать

<Bean text="Some Text">
  <String fx:value="One"/>
  <String fx:value="Two" />
  <String fx:value="Three" />
</Bean>

заполнит список elements ["One", "Two", "Three"].

person James_D    schedule 16.05.2015