Szukaj…


Składnia

Przykład FXML

Prosty dokument FXML opisujący AnchorPane zawierający przycisk i węzeł etykiety:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" 
        fx:controller="com.example.FXMLDocumentController">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
    </children>
</AnchorPane>

Ten przykładowy plik FXML jest powiązany z klasą kontrolera. Powiązanie między FXML a klasą kontrolera w tym przypadku polega na określeniu nazwy klasy jako wartości atrybutu fx:controller w elemencie głównym FXML: fx:controller="com.example.FXMLDocumentController" . Klasa kontrolera pozwala na wykonanie kodu Java w odpowiedzi na działania użytkownika dotyczące elementów interfejsu użytkownika zdefiniowanych w pliku FXML:

package com.example ;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class FXMLDocumentController {
    
    @FXML
    private Label label;
    
    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }
    
    @Override
    public void initialize(URL url, ResourceBundle resources) {
        // Initialization code can go here. 
        // The parameters url and resources can be omitted if they are not needed
    }    
    
}

Do załadowania pliku FXMLLoader można użyć programu FXMLLoader:

public class MyApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("FXMLDocument.fxml"));
        Parent root = loader.load();
        
        Scene scene = new Scene(root);
        
        stage.setScene(scene);
        stage.show();
    }

}

Metoda load wykonuje kilka akcji i warto zrozumieć kolejność ich występowania. W tym prostym przykładzie:

  1. FXMLLoader odczytuje i analizuje plik FXML. Tworzy obiekty odpowiadające elementom zdefiniowanym w pliku i odnotowuje zdefiniowane na nich atrybuty fx:id .

  2. Ponieważ element główny pliku FXML zdefiniował atrybut fx:controller , FXMLLoader tworzy nową instancję określonej przez siebie klasy. Domyślnie dzieje się tak przez wywołanie konstruktora bez argumentu dla określonej klasy.

  3. Dowolne elementy ze zdefiniowanymi atrybutami fx:id które mają pola w kontrolerze z pasującymi nazwami pól i które są albo public (niezalecane), albo opatrzone adnotacjami @FXML (zalecane) są „wstrzykiwane” do odpowiednich pól. W tym przykładzie, ponieważ w pliku FXML znajduje się Label o fx:id="label" i pole w kontrolerze zdefiniowane jako

    @FXML
    private Label label ;
    

    pole label jest inicjowane instancją Label utworzoną przez FXMLLoader .

  4. Procedury obsługi zdarzeń są rejestrowane z dowolnymi elementami w pliku onXXX="#..." ze zdefiniowanymi właściwościami onXXX="#..." . Te procedury obsługi zdarzeń wywołują określoną metodę w klasie kontrolera. W tym przykładzie, ponieważ Button ma onAction="#handleButtonAction" , a kontroler definiuje metodę

    @FXML
    private void handleButtonAction(ActionEvent event) { ... }
    

    gdy akcja jest uruchamiana na przycisku (np. użytkownik go naciska), ta metoda jest wywoływana. Metoda musi mieć void typ zwracany i może albo zdefiniować parametr pasujący do typu zdarzenia ( ActionEvent w tym przykładzie), albo nie może zdefiniować żadnych parametrów.

  5. Na koniec, jeśli klasa kontrolera definiuje metodę initialize metoda ta jest wywoływana. Zauważ, że dzieje się to po @FXML pól @FXML , aby można było bezpiecznie uzyskać do nich dostęp za pomocą tej metody i będą inicjowane instancjami odpowiadającymi elementom w pliku FXML. Metoda initialize() może nie przyjmować parametrów lub może przyjmować URL i ResourceBundle . W tym drugim przypadku parametry te zostaną wypełnione URL reprezentującym lokalizację pliku FXML oraz dowolnym zestawem ResourceBundle ustawionym na FXMLLoader za pośrednictwem loader.setResources(...) . Każda z nich może być null jeśli nie została ustawiona.

Zagnieżdżone kontrolery

Nie ma potrzeby tworzenia całego interfejsu użytkownika w jednym FXML za pomocą jednego kontrolera.

Znacznika <fx:include> można użyć do włączenia jednego pliku fxml do drugiego. Kontroler dołączonego pliku fxml można wstrzyknąć do kontrolera dołączonego pliku, tak jak każdy inny obiekt utworzony przez FXMLLoader .

Odbywa się to poprzez dodanie atrybutu fx:id do elementu <fx:include> . W ten sposób kontroler dołączonego pliku fxml zostanie wstrzyknięty do pola o nazwie <fx:id value>Controller .

Przykłady:

fx: wartość id nazwa pola do wstrzyknięcia
bla fooController
odpowiedź42 answer42Controller
xYz xYzController

Przykładowe fxmls

Licznik

To jest plik fxml zawierający StackPane z węzłem Text . Kontroler dla tego pliku fxml umożliwia uzyskanie bieżącej wartości licznika, a także zwiększenie licznika:

counter.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<StackPane prefHeight="200" prefWidth="200" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.CounterController">
    <children>
        <Text fx:id="counter" />
    </children>
</StackPane>

CounterController

package counter;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class CounterController {
    @FXML
    private Text counter;

    private int value = 0;
    
    public void initialize() {
        counter.setText(Integer.toString(value));
    }
    
    public void increment() {
        value++;
        counter.setText(Integer.toString(value));
    }
    
    public int getValue() {
        return value;
    }
    
}

W tym fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane prefHeight="500" prefWidth="500" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.OuterController">
    <left>
        <Button BorderPane.alignment="CENTER" text="increment" onAction="#increment" />
    </left>
    <center>
        <!-- content from counter.fxml included here -->
        <fx:include fx:id="count" source="counter.fxml" />
    </center>
</BorderPane>

OuterController

Kontroler dołączonego pliku fxml jest wstrzykiwany do tego kontrolera. Tutaj onAction obsługi zdarzenia onAction dla Button służy do zwiększania licznika.

package counter;

import javafx.fxml.FXML;

public class OuterController {

    // controller of counter.fxml injected here
    @FXML
    private CounterController countController;

    public void initialize() {
        // controller available in initialize method
        System.out.println("Current value: " + countController.getValue());
    }

    @FXML
    private void increment() {
        countController.increment();
    }

}

Pliki fxml mogą być ładowane w ten sposób, zakładając, że kod jest wywoływany z klasy w tym samym pakiecie co outer.fxml :

Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));

Zdefiniuj bloki i

Czasami element musi zostać utworzony poza zwykłą strukturą obiektową w fxml.

Oto miejsce, w które wchodzą Define Blocks :

Zawartość wewnątrz elementu <fx:define> nie jest dodawana do obiektu utworzonego dla elementu nadrzędnego.

Każdy element podrzędny <fx:define> potrzebuje atrybutu fx:id .

Do obiektów utworzonych w ten sposób można później odwoływać się za pomocą elementu <fx:reference> lub przez powiązanie wyrażenia.

Element <fx:reference> może służyć do odwoływania się do dowolnego elementu z atrybutem fx:id który jest obsługiwany przed obsługą elementu <fx:reference> przy użyciu tej samej wartości, co atrybut fx:id przywoływanego elementu w elemencie atrybut source elementu <fx:reference> .

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>


<VBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="300.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8">
    <children>
        <fx:define>
            <String fx:value="My radio group" fx:id="text" />
        </fx:define>
        <Text>
            <text>
                <!-- reference text defined above using fx:reference -->
                <fx:reference source="text"/>
            </text>
        </Text>
        <RadioButton text="Radio 1">
            <toggleGroup>
                <ToggleGroup fx:id="group" />
            </toggleGroup>
        </RadioButton>
        <RadioButton text="Radio 2">
            <toggleGroup>
                <!-- reference ToggleGroup created for last RadioButton -->
                <fx:reference source="group"/>
            </toggleGroup>
        </RadioButton>
        <RadioButton text="Radio 3" toggleGroup="$group" />
        
        <!-- reference text defined above using expression binding -->
        <Text text="$text" />
    </children>
</VBox>

Przekazywanie danych do FXML - dostęp do istniejącego kontrolera

Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.

Rozwiązanie

Podaj kontroler za pomocą atrybutu fx:controller i pobierz instancję kontrolera utworzoną podczas procesu ładowania z instancji FXMLLoader użytej do załadowania pliku fxml.

Dodaj metody przekazywania danych do instancji kontrolera i przetwarzaj dane w tych metodach.

FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

Kontroler

package valuepassing;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    @FXML
    private Text target;

    public void setData(String data) {
        target.setText(data);
    }

}

Kod używany do ładowania pliku fxml

String data = "Hello World!";

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
Parent root = loader.load();
TestController controller = loader.<TestController>getController();
controller.setData(data);

Przekazywanie danych do FXML - Określanie instancji kontrolera

Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.

Rozwiązanie

Ustaw kontroler za pomocą instancji FXMLLoader użytej później do załadowania pliku fxml.

Upewnij się, że kontroler zawiera odpowiednie dane przed załadowaniem pliku fxml.

Uwaga: w tym przypadku plik fxml nie może zawierać atrybutu fx:controller .

FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

Kontroler

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    private final String data;

    public TestController(String data) {
        this.data = data;
    }
    
    @FXML
    private Text target;
    
    public void initialize() {
        // handle data once the fields are injected
        target.setText(data);
    }

}

Kod używany do ładowania pliku fxml

String data = "Hello World!";

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));

TestController controller = new TestController(data);
loader.setController(controller);

Parent root = loader.load();

Przekazywanie parametrów do FXML - za pomocą kontroleraFactory

Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.

Rozwiązanie

Określ fabrykę kontrolerów odpowiedzialną za tworzenie kontrolerów. Przekaż dane do instancji kontrolera utworzonej przez fabrykę.

FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

Kontroler

package valuepassing;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    private final String data;

    public TestController(String data) {
        this.data = data;
    }
    
    @FXML
    private Text target;
    
    public void initialize() {
        // handle data once the fields are injected
        target.setText(data);
    }

}

Kod używany do ładowania pliku fxml

Dane ciągu = „Hello World!”;

Map<Class, Callable<?>> creators = new HashMap<>();
creators.put(TestController.class, new Callable<TestController>() {

    @Override
    public TestController call() throws Exception {
        return new TestController(data);
    }

});

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));

loader.setControllerFactory(new Callback<Class<?>, Object>() {

    @Override
    public Object call(Class<?> param) {
        Callable<?> callable = creators.get(param);
        if (callable == null) {
            try {
                // default handling: use no-arg constructor
                return param.newInstance();
            } catch (InstantiationException | IllegalAccessException ex) {
                throw new IllegalStateException(ex);
            }
        } else {
            try {
                return callable.call();
            } catch (Exception ex) {
                throw new IllegalStateException(ex);
            }
        }
    }
});

Parent root = loader.load();

Może się to wydawać skomplikowane, ale może być przydatne, jeśli plik fxml powinien być w stanie zdecydować, jakiej klasy kontrolera potrzebuje.

Tworzenie instancji w FXML

Poniższa klasa służy do zademonstrowania sposobu tworzenia instancji klas:

JavaFX 8

Adnotacja w Person(@NamedArg("name") String name) musi zostać usunięta, ponieważ adnotacja @NamedArg jest niedostępna.

package fxml.sample;

import javafx.beans.NamedArg;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Person {

    public static final Person JOHN = new Person("John");

    public Person() {
        System.out.println("Person()");
    }
    
    public Person(@NamedArg("name") String name) {
        System.out.println("Person(String)");
        this.name.set(name);
    }
    
    public Person(Person person) {
        System.out.println("Person(Person)");
        this.name.set(person.getName());
    }

    private final StringProperty name = new SimpleStringProperty();

    public final String getName() {
        System.out.println("getter");
        return this.name.get();
    }

    public final void setName(String value) {
        System.out.println("setter");
        this.name.set(value);
    }

    public final StringProperty nameProperty() {
        System.out.println("property getter");
        return this.name;
    }
    
    public static Person valueOf(String value) {
        System.out.println("valueOf");
        return new Person(value);
    }
    
    public static Person createPerson() {
        System.out.println("createPerson");
        return new Person();
    }
    
}

Załóżmy, że klasa Person została już zainicjowana przed załadowaniem pliku fxml.

Uwaga na temat importu

W poniższym przykładzie fxml sekcja importowania zostanie pominięta. Jednak fxml powinien zaczynać się od

<?xml version="1.0" encoding="UTF-8"?>

następnie sekcja importu importująca wszystkie klasy użyte w pliku fxml. Import ten jest podobny do importu niestatycznego, ale jest dodawany jako instrukcja przetwarzania. Nawet klasy z pakietu java.lang muszą zostać zaimportowane.

W takim przypadku należy dodać następujący import:

<?import java.lang.*?>
<?import fxml.sample.Person?>

JavaFX 8

@NamedArg konstruktor z adnotacjami

Jeśli istnieje konstruktor, w którym każdy parametr jest opatrzony adnotacją @NamedArg a wszystkie wartości adnotacji @NamedArg są obecne w pliku fxml, konstruktor zostanie użyty z tymi parametrami.

<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Oba dają następujące dane wyjściowe konsoli, jeśli są załadowane:

Person(String)

Brak konstruktora argumentów

Jeśli nie ma odpowiedniego konstruktora z adnotacjami @NamedArg dostępny będzie konstruktor, który nie przyjmuje parametrów.

Usuń adnotację @NamedArg z konstruktora i spróbuj załadować.

<Person name="John"/>

Spowoduje to użycie konstruktora bez parametrów.

Wynik:

Person()
setter

fx:value atrybut fx:value

Atrybutu fx:value można użyć do przekazania jego wartości do metody static valueOf , biorąc parametr String i zwracając instancję do użycia.

Przykład

<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>

Wynik:

valueOf
Person(String)

fx:factory

Atrybut fx:factory umożliwia tworzenie obiektów przy użyciu dowolnych static metod, które nie przyjmują parametrów.

Przykład

<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Wynik:

createPerson
Person()
setter

<fx:copy>

Za pomocą fx:copy można wywołać konstruktora kopiowania. Określanie fx:id innego Atrybut source znacznika wywoła konstruktor kopiowania z tym obiektem jako parametrem.

Przykład:

<ArrayList xmlns:fx="http://javafx.com/fxml">
    <Person fx:id="p1" fx:constant="JOHN"/>
    <fx:copy source="p1"/>
</ArrayList>

Wynik

Person(Person)
getter

fx:constant

fx:constant pozwala uzyskać wartość ze static final pola static final .

Przykład

<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>

nie wygeneruje żadnych danych wyjściowych, ponieważ odnosi się to tylko do JOHN który został utworzony podczas inicjowania klasy.

Ustawianie właściwości

Istnieje wiele sposobów dodawania danych do obiektu w formacie fxml:

Znacznik <property>

Znacznik z nazwą właściwości można dodać jako element podrzędny elementu używanego do tworzenia instancji. Element podrzędny tego znacznika jest przypisywany do właściwości za pomocą ustawiacza lub dodawany do zawartości właściwości (tylko do odczytu właściwości listy / mapy).

Właściwość domyślna

Klasę można @DefaultProperty adnotacją za @DefaultProperty adnotacji @DefaultProperty . W takim przypadku elementy można dodawać bezpośrednio jako element podrzędny bez użycia elementu o nazwie właściwości.

property="value" atrybut property="value"

Właściwości można przypisać, używając nazwy właściwości jako nazwy atrybutu i wartości jako wartości atrybutu. Ma to taki sam efekt, jak dodanie następującego elementu jako elementu podrzędnego znacznika:

<property>
    <String fx:value="value" />
</property>

setery statyczne

Właściwości można ustawić również za pomocą ustawień static . Są to metody static nazwie setProperty które przyjmują element jako pierwszy parametr, a wartość ustawiają jako drugi parametr. Metody te można uzyskać w dowolnej klasie i można ich używać przy użyciu ContainingClass.property zamiast zwykłej nazwy właściwości.

Uwaga: obecnie wydaje się, że konieczne jest posiadanie odpowiedniej metody statycznego gettera (tj. Metody statycznej o nazwie getProperty przyjmującej element jako parametr w tej samej klasie, co seter statyczny), aby działało, chyba że typem wartości jest String .

Wpisz Coercion

Poniższy mechanizm służy do uzyskania obiektu poprawnej klasy podczas przypisań, np. W celu dopasowania typu parametru metody ustawiającej.

Jeśli klasy można przypisać, wówczas używana jest sama wartość.

W przeciwnym razie wartość jest konwertowana w następujący sposób

Typ docelowy Wartość używana (wartość źródło s )
Boolean , boolean Boolean.valueOf(s)
char , Character s.toString.charAt(0)
inny typ pierwotny lub typ opakowania Właściwy sposób typu docelowej w przypadku, gdy s jest Number The valueOf(s.toString()) dla typu owijającego inaczej
BigInteger BigInteger.valueOf(s.longValue()) jest s to Number , new BigInteger(s.toString()) inaczej
BigDecimal BigDecimal.valueOf(s.doubleValue()) jest s to Number , new BigDecimal(s.toString()) inaczej
Numer Double.valueOf(s.toString()) jeśli s.toString() zawiera . , Long.valueOf(s.toString()) przeciwnym razie
Class Class.forName(s.toString()) wywołany przy użyciu kontekstowego ClassLoader bieżącego wątku bez inicjowania klasy
wyliczanie Wynik metody valueOf , dodatkowo przekonwertowanej na String wielkich liter oddzielony przez _ wstawiony przed każdą wielką literą, jeżeli s jest String rozpoczynającym się od małej litery
inny wartość zwrócona przez static metodę valueOf w parametrze targetType, która ma parametr pasujący do typu s lub nadklasy tego typu

Uwaga: To zachowanie nie jest dobrze udokumentowane i może ulec zmianie.

Przykład

public enum Location {
    WASHINGTON_DC,
    LONDON;
}
package fxml.sample;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.beans.DefaultProperty;

@DefaultProperty("items")
public class Sample {
    
    private Location loaction;

    public Location getLoaction() {
        return loaction;
    }

    public void setLoaction(Location loaction) {
        this.loaction = loaction;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
    
    int number;

    private final List<Object> items = new ArrayList<>();

    public List<Object> getItems() {
        return items;
    }
    
    private final Map<String, Object> map = new HashMap<>();

    public Map<String, Object> getMap() {
        return map;
    }
    
    private BigInteger serialNumber;

    public BigInteger getSerialNumber() {
        return serialNumber;
    }

    public void setSerialNumber(BigInteger serialNumber) {
        this.serialNumber = serialNumber;
    }

    @Override
    public String toString() {
        return "Sample{" + "loaction=" + loaction + ", number=" + number + ", items=" + items + ", map=" + map + ", serialNumber=" + serialNumber + '}';
    }
    
}
package fxml.sample;

public class Container {

    public static int getNumber(Sample sample) {
        return sample.number;
    }

    public static void setNumber(Sample sample, int number) {
        sample.number = number;
    }

    private final String value;

    private Container(String value) {
        this.value = value;
    }

    public static Container valueOf(String s) {
        return new Container(s);
    }

    @Override
    public String toString() {
        return "42" + value;
    }

}

Wydrukowanie wyniku ładowania poniższego pliku fxml daje

Sample{loaction=WASHINGTON_DC, number=5, items=[42a, 42b, 42c, 42d, 42e, 42f], map={answer=42, g=9.81, hello=42A, sample=Sample{loaction=null, number=33, items=[], map={}, serialNumber=null}}, serialNumber=4299}
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import fxml.sample.*?>

<Sample xmlns:fx="http://javafx.com/fxml/1" Container.number="5" loaction="washingtonDc">
    
    <!-- set serialNumber property (type coercion) -->
    <serialNumber>
        <Container fx:value="99"/>
    </serialNumber>
    
    <!-- Add elements to default property-->
    <Container fx:value="a"/>
    <Container fx:value="b"/>
    <Container fx:value="c"/>
    <Container fx:value="d"/>
    <Container fx:value="e"/>
    <Container fx:value="f"/>
    
    <!-- fill readonly map property -->
    <map g="9.81">
        <hello>
            <Container fx:value="A"/>
        </hello>
        <answer>
            <Container fx:value=""/>
        </answer>
        <sample>
            <Sample>
                <!-- static setter-->
                <Container.number>
                    <Integer fx:value="33" />
                </Container.number>
            </Sample>
        </sample>
    </map>
</Sample>


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow