Могат ли фабричните методи на 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>

Ето Utilities.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