JAVA : WIĘCEJ O JAVAFX 1. WSTĘP

Wielkość: px
Rozpocząć pokaz od strony:

Download "JAVA : WIĘCEJ O JAVAFX 1. WSTĘP"

Transkrypt

1 JAVA : WIĘCEJ O JAVAFX 1. WSTĘP Ten tekst jest bezpośrednią kontynuacją poprzedniego tekstu o JavFX, który nie został uwzględniony ze względu na liczbę stron lub tematów, które są bardziej techniczne: - Właściwości i wiązanie danych - Zaawansowane kontrole - Przeciągnij i upuść - Architektura i MVC - Kontrole zdefiniowane przez użytkownika - JavaFX i współbieżność - Kształty 3D - Wykresy - Ostatni przykład Można również powiedzieć, że książka uzupełnia to, co jest potrzebne do pisania aplikacji komputerowych Java za pomocą graficznego interfejsu użytkownika. Jak pokazano na powyższej liście, ta książka zawiera wiele tematów, ale oprócz pierwszej na temat właściwości i wiązania poszczególne tematy nie mają dla siebie znaczenia i można je czytać zgodnie z bieżącymi potrzebami. Książka powinna zatem pokazać, jak rozwiązać konkretne problemy, co ilustrują przykłady, aw porównaniu do poprzedniej książki będzie tylko kilka ćwiczeń. 2 WŁAŚCIWOŚCI JAVAFX Właściwości i komponenty zostały wspomniane kilka razy w poprzednich książkach, a wszystkie klasy w JavaFX definiują właściwości, które można czytać lub modyfikować jako programista. W typowym komponencie bean właściwość jest niczym innym jak metodami get i setd setd, które reprezentują właściwość bieżącej klasy i zazwyczaj odpowiadają zmiennej z powiązanymi metodami get i set. Ponadto Java Bean korzysta z określonej konwencji nazw. Jednak w JavaFX właściwość jest nieco większa, ponieważ czasem trzeba ją znać. Wszystkie właściwości JavaFX są obserwowalne, a obserwator może otrzymywać powiadomienia dotyczące unieważnień i zmian. Istnieje ścisłe rozróżnienie między właściwościami do odczytu / zapisu a właściwościami tylko do odczytu. Wartość właściwości może być pojedynczą wartością lub kolekcją. Inną ważną różnicą jest to, że właściwość jest zawsze obiektem, którego klasa jest częścią określonej hierarchii klas dla właściwości. Jako przykłady można wymienić - IntegerProperty - DoubleProperty (i istnieją odpowiednie klasy dla innych prostych typów danych). Klasy te są w rzeczywistości abstrakcyjne i dla każdej klasy istnieją dwie konkretne klasy, które są dla IntegerProperty - SimpleIntegerProperty - ReadOnlyIntegerWrapper

2 gdzie pierwszy reprezentuje właściwość odczytu / zapisu, a drugi reprezentuje właściwość tylko do odczytu. Klasa właściwości definiuje jak zwykle metody get i set, ale klasa definiuje również dwie metody o nazwie getvalue () i setvalue (). W przypadku typów pierwotnych metody get i set działają na typach pierwotnych (int, double i tak dalej), podczas gdy pozostałe dwa działają na obiektach (takich jak Integer, Double itp.). Dla typów referencyjnych takich jak - StringProperty - ObjectProperty <T> wszystkie cztery metody działają na obiektach, a ze względu na autoboxing nie ma różnicy w praktyce, na przykład w get i getvalue (). Jako przykład poniżej pokazano metodę, która tworzy wartość całkowitą do odczytu / zapisu private static void test01() IntegerProperty p = new SimpleIntegerProperty(100); System.out.println(p.get()); p.set(200); System.out.println(p); a metoda (która jest metodą w programie FXProperties) jest wykonywana, otrzymujesz wynik: 100 IntegerProperty [wartość: 200] W tym miejscu należy zwrócić uwagę na ostatni wiersz będący wynikiem funkcji tostring () w klasie SimpleIntegerProperty, podczas gdy pierwszy wypisuje wartość int. Właściwości tylko do odczytu są w rzeczywistości nieco bardziej złożone, ponieważ są opakowaniem dwóch właściwości, które system zapewnia synchronizacji. Tutaj jedna jest właściwością tylko do odczytu, a druga jest do odczytu / zapisu. Chodzi o to, że z zewnątrz jest to właściwość tylko do odczytu, ale można ją zmienić wewnętrznie z klasy, w której jest zdefiniowana, a aplikacja jest typowa jako zmienna instancji prywatnej. Na przykład poniżej pokazano klasę z dwiema właściwościami JavaFX: package fxproperties; import javafx.beans.property.*; public class Counter private IntegerProperty step = new SimpleIntegerProperty(1); private ReadOnlyIntegerWrapper value = new ReadOnlyIntegerWrapper(0); public final IntegerProperty stepproperty()

3 return step; public final int getstep() return step.get(); public final void setstep(int step) this.step.set(step); public final ReadOnlyIntegerProperty valueproperty() return value.getreadonlyproperty(); public final int getvalue() return value.get(); public void increase() value.set(value.get() + step.get()); public void decrease() value.set(value.get() step.get());

4 @Override public String tostring() return "" + value.get(); To jest przykład komponentu JavaFX. Dwie właściwości nazywane są krokiem i wartością i są właściwością do odczytu / zapisu oraz właściwością tylko do odczytu. Dla obu właściwości zdefiniowano metodę, która zwraca wartość, na przykład: public final IntegerProperty stepproperty() return step; W tym miejscu należy zwrócić uwagę na konwencje nazw, po których następuje nazwa właściwości słowa, a ponadto jest ona zdefiniowana jako ostateczna. Na koniec zdefiniowano wspólne metody get i set (które są również zdefiniowane jako ostateczne). Zasadniczo nie są one konieczne, ale jest to również część konwencji, aby być zgodnym ze zwykłą konwencją Java Bean. Należy zauważyć, że metody metody zwiększania () i zmniejszania () wykonują ustawioną metodę wartości właściwości - nawet jeśli jest ona tylko do odczytu. To dobrze, ponieważ odbywa się wewnętrznie w liczniku klas. Poniżej znajduje się metoda wykorzystująca klasę: private static void test02() Counter counter = new Counter(); System.out.printf("%d %s\n", counter.getvalue(), counter); for (int i = 0; i < 10; ++i) counter.increase(); System.out.printf("%d %s\n", counter.getvalue(), counter); counter.setstep(10); for (int i = 0; i < 10; ++i) counter.increase(); System.out.printf("%d %s\n", counter.getvalue(), counter); // counter.valueproperty().setvalue(10); Nie ma wiele do zapamiętania, ale należy zwrócić uwagę na ostatnią instrukcję, która jest komentarzem, a instrukcja nie może być faktycznie przetłumaczona, ponieważ wartośćproperty () zwraca właściwość tylko do odczytu i dlatego nie ma metody set ().

5 Klasa właściwości zawiera trzy wartości 1. odniesienie do fasoli, do której należy własność 2. nazwa, która jest tylko ciągiem 3. wartość a klasa ma konstruktory, więc możesz zainicjować te wartości według potrzeb. Na przykład dla klasy SimpleIntegerProperty: - SimpleIntegerProperty () - SimpleIntegerProperty (wartość int) - SimpleIntegerProperty (komponent bean, nazwa ciągu) - SimpleIntegerProperty (komponent bean, nazwa ciągu, wartość int) a właściwość ma metodę getbean () i getname (). Aby pokazać inny przykład wzorca używanego przez JavaFX w odniesieniu do właściwości, poniżej pokazano klasę, która jest normalnym bean: package fxproperties; import javafx.beans.property.*; public class King private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name"); private IntegerProperty from = new SimpleIntegerProperty(this, "from"); private IntegerProperty to = new SimpleIntegerProperty(this, "to", 9999); public King() public King(String name, int from, int to) this.name.set(name); this.from.set(from); this.to.set(to);

6 public final String getname() return name.get(); public final ReadOnlyStringProperty nameproperty() return name.getreadonlyproperty(); public final int getfrom() return from.get(); public final void setfrom(int from) this.from.set(from); public final IntegerProperty fromproperty() return from; public final int getto() return to.get(); public final void setto(int to) this.to.set(to);

7 public final IntegerProperty toproperty() return to; Klasa ma trzy właściwości, z których pierwsza jest właściwością tylko do odczytu, a pozostałe są właściwościami do odczytu / zapisu. Należy zauważyć, że w JavaFX właściwości nie są tworzone jako typy pierwotne, ale jako obiekty Property. Metody get i set są definiowane w zwykły sposób i przy użyciu tej samej konwencji nazw dla standardu dla komponentów bean Java. Należy zauważyć, jak te metody odnoszą się do poszczególnych właściwości i że są one zdefiniowane jako ostateczne. Jest to część wzorca używanego przez JavaFX. Dla każdej właściwości zdefiniowana jest również metoda, która zwraca rzeczywisty obiekt właściwości, taki jak fromproperty (). Jest to również część wzoru. Na koniec należy zauważyć, że nazwa jest właściwością tylko do odczytu i dlatego nie ma ustawionej metody. Należy również zauważyć, że metoda nameproperty () zwraca ReadOnlyStringWrapper, a nie StringProperty. Te właściwości, jak pokazano powyżej, są typami klas, co oznacza, że wiele obiektów musi zostać utworzonych. Powodem, dla którego właściwość jest zdefiniowana jako typ obiektu, jest wiele zaawansowanych funkcji, które są powiązane z właściwością (jako zdarzenia pożaru i powiązanie), ale często nie są one używane, dlatego JavaFX używa w dużej mierze opóźnionego aktualizowania, gdzie obiekt jest najpierw tworzone w razie potrzeby. Zasadę można zilustrować klasą King w następujący sposób: public class King private static final String DK = "DK"; private StringProperty country; public String getcountry() return country == null? DK : country.get(); public void setcountry(string country) if (country!= null!dk.equals(country)) countryproperty().set(country);

8 public StringProperty countryproperty() if (country == null) country = new SimpleStringProperty(this, "country", DK); return country; gdzie klasa ma teraz dodatkową własność, reprezentującą nazwę kraju, z którego pochodzi król. Jeśli zdecydowana większość królów pochodzi z Danii, a gdy trzeba tylko odczytać tę wartość, nie jest właściwe (ze względu na wydajność) tworzenie instancji obiektu właściwości dla każdego obiektu króla. Dlatego zmienny kraj od początku ma wartość NULL, a obiekt jest najpierw tworzony, jeśli spróbujesz zmienić wartość na inną niż DK lub jeśli odwołujesz się do właściwości za pomocą countryproperty (). Powyższe może wydawać się przytłaczające, ale przyczyną jest to, że właściwości w JavaFX są obserwowalne i mogą być powiązane z InvalidationListener i ChangeListener <? super T>. Pierwszy interfejs definiuje metodę unieważnioną (), podczas gdy drugi definiuje metodę zmienioną (). Gdy wartość właściwości zmienia się z prawidłowej na niepoprawną, generowane jest zdarzenie unieważnienia. Jeśli nie zawsze tak się dzieje za każdym razem, gdy wartość właściwości zmienia się (lub w inny sposób staje się nieważna), należy unikać generowania linii zdarzeń unieważnienia. Zdarzenia zmiany są jednak generowane za każdym razem, gdy zmienia się wartość właściwości. Następująca metoda zilustruje wystąpienie tych zdarzeń private static void test03() IntegerProperty p = new SimpleIntegerProperty(100); p.addlistener(fxproperties::invalidated); // p.addlistener(fxproperties::changed); System.out.println("Set to 101"); p.set(101); System.out.println("Changed to 101"); System.out.println("Set to 102"); p.set(102); System.out.println("Changed to 102"); System.out.println("Set to 102"); p.set(102); System.out.println("Changed to 103"); System.out.println("Set to " + p.get());

9 p.set(103); System.out.println("Changed to 103"); public static void invalidated(observable p) System.out.println("Property is invalid"); public static void changed(observablevalue<? extends Number> p, Number oldvalue, Number newvalue) System.out.println("Property is changed"); U dołu zdefiniowano procedury obsługi zdarzeń odpowiednio dla zdarzenia unieważnienia i zmiany. Należy zwrócić uwagę na parametry, w których pierwszy zawiera odwołanie do właściwości, która wywołała dane zdarzenie. Drugi ma podobne odwołanie do właściwości, a także do starej wartości i wartości po zmianie. Zauważ, że typem wartości jest Liczba, która jest klasą bazową dla liczby całkowitej. Jeśli wykonasz tę funkcję (bez usuwania komentarza), wynikiem będzie: Ustaw na 101 Właściwość jest nieprawidłowa Zmieniono na 101 Ustaw na 102 Zmieniono na 102 Ustaw na 102 Zmieniono na 102 Ustaw na 103 Właściwość jest nieprawidłowa Zmieniono na 103 i to znaczy, że procedura obsługi zdarzenia invalidated () jest wykonywana dwukrotnie. Funkcja tworzy wartość całkowitą do odczytu / zapisu o wartości 100, a następnie jest poprawna. Następnie dodawany jest moduł obsługi zdarzeń unieważniających zdarzenia. Gdy wartość p zmienia się na 101, właściwość staje się nieprawidłowa i wywoływane jest zdarzenie unieważniające. Gdy wartość zmienia się na 102, zdarzenie nie jest wywoływane, ponieważ jest już nieprawidłowe. Następnie metoda odczytuje tę właściwość (w System.out.println ()), co między innymi oznacza, że staje się ona ważna. Gdy następnie zostanie ustawiony na 102, nie zostanie uruchomione żadne zdarzenie unieważnienia, ponieważ

10 wartość nie zostanie zmieniona. Dzieje się tak, gdy wartość zmienia się na 103. Jeśli następnie usuniesz komentarz i ustawisz komentarz przed pierwszym addlistener (), zamiast tego zostanie dodany ChangeListener, a jeśli następnie wykonasz metodę, otrzymasz wynik: Ustaw na 101 Właściwość została zmieniona Zmieniono na 101 Ustaw na 102 Właściwość została zmieniona Zmieniono na 102 Ustaw na 102 Zmieniono na 102 Ustaw na 103 Właściwość została zmieniona Zmieniono na 103 Należy zauważyć, że zdarzenie zmiany jest wykonywane za każdym razem, gdy wartość się zmienia, ale nie wtedy, gdy wartość jest ustawiona na 102 za drugim razem, ponieważ wartość nie ulega zmianie. Inną różnicą między zdarzeniami unieważniającymi a zdarzeniami zmieniającymi jest to, że JavaFX dla zdarzeń unieważniających używa leniwej oceny, podczas gdy zdarzenia zmian używają szybkiej oceny, ponieważ wartość musi zostać przesłana do procedury obsługi zdarzeń. Do pewnego stopnia można to osiągnąć za pomocą zdarzenia unieważnienia i zdarzenia zmiany, ale zdarzenie unieważnienia jest nieco bardziej skuteczne, ponieważ niekoniecznie jest uruchamiane za każdym razem, gdy zmienia się wartość, i ponieważ niekoniecznie aktualizuje wartość. Jeśli chcesz wybrać zdarzenie, którego chcesz słuchać, główną zasadą jest to, że jeśli nie czytasz wartości w module obsługi zdarzeń, powinieneś słuchać zdarzeń unieważnienia, ale jeśli czytasz wartość, powinieneś zamiast tego słuchać zmiany zdarzenie, ponieważ automatycznie aktualizuje wartość (a tym samym sprawdza poprawność właściwości). Możesz zobaczyć efekt, jeśli w powyższym programie usuniesz oba komentarze: Ustaw na 101 Właściwość jest nieprawidłowa Właściwość została zmieniona Zmieniono na 101 Ustaw na 102 Właściwość jest nieprawidłowa Właściwość została zmieniona Zmieniono na 102

11 Ustaw na 102 Zmieniono na 102 Ustaw na 103 Właściwość jest nieprawidłowa Właściwość została zmieniona Zmieniono na 103 W rezultacie zdarzenie unieważnienia jest uruchamiane za każdym razem, gdy zmienia się wartość. 2.1 WIĄZANIE WŁAŚCIWOŚCI JavaFX obsługuje wiązanie właściwości, w których właściwość komponentu lub węzła może być powiązana z właściwością innego obiektu. Zasadniczo jest stosunkowo prosty w użyciu, ale w rzeczywistości wiele szczegółów jest przypisanych do koncepcji, jednak klasy w JavaFX są najbardziej ukryte. Poniżej zilustruję kilkoma prostymi przykładami, na czym polega wiązanie. Rozważ następującą metodę: private static void test04() IntegerProperty x = new SimpleIntegerProperty(3); IntegerProperty y = new SimpleIntegerProperty(5); IntegerProperty z = new SimpleIntegerProperty(7); z.bind(x.add(y)); System.out.println("Bound = " + z.isbound() + ", z = " + z.get()); x.set(11); y.set(13); System.out.println("Bound = " + z.isbound() + ", z = " + z.get()); z.unbind(); x.set(17); y.set(19); System.out.println("Bound = " + z.isbound() + ", z = " + z.get()); Metoda definiuje trzy właściwości typu IntegerProperty zainicjowane wartościami 3, 5 i 7. IntegerProperty ma metodę add (), która wykonuje dodawanie wartości dwóch właściwości i zwraca wiązanie sumy dwóch właściwości, które jest wyrażeniem typu NumberBinding, reprezentujący sumę wartości dwóch właściwości. Wiązanie to jest następnie łączone z właściwością z. Następna instrukcja zostanie wydrukowana Bound = true, z = 8

12 który stwierdza, że zmienna z jest związana, a jej wartość wynosi 8, a zatem suma xiy. Oznacza to, że wartość z została zmieniona. Następnie dwie właściwości x i y są zmieniane i wypisywana jest następna instrukcja Bound = true, z = 24 Oznacza to, że zmiany dwóch właściwości x i y automatycznie aktualizują z, i właśnie o to chodzi w wiązaniu. Następna instrukcja ponownie usuwa wiązanie z, po czym x i y są ponownie zmieniane. Następnie zostanie wydrukowane ostatnie oświadczenie Bound = false, z = 24 co częściowo pokazuje, że z nie jest już związany, a jego wartość nie zmieniła się, ponieważ nie jest już powiązany z dwiema właściwościami x i y. Wiązanie jest zatem wyrażeniem, które jest oceniane na wartość. Wyrażenie składa się z jednej lub więcej obserwowalnych wartości, zwanych zależnościami. Wiązanie obserwuje zmiany swoich zależności, a jego wartość jest automatycznie aktualizowana, gdy zmieniają się wartości zależności. Wszystkie klasy właściwości w JavaFX mają wbudowaną obsługę wiązania. Istnieją dwie formy wiązania: wiązanie jednokierunkowe i wiązanie dwukierunkowe. Powyżej jest przykładem jednokierunkowego wiązania, które jest zdefiniowane przez metodę bind (). Istnieje kilka ograniczeń dla wiązań jednokierunkowych i nie można na przykład bezpośrednio zmienić wartości powiązanej właściwości. Nie możesz bezpośrednio zmienić wartości z powyżej. Ponadto właściwość można powiązać tylko z jednym wyrażeniem za pomocą powiązań jednokierunkowych, a próba ponownego powiązania powoduje zastąpienie pierwszego powiązania. Powiązanie może być również dwukierunkowe, co oznacza, że można również zmienić wartość powiązanej właściwości. Oczywiście powiązanie takie jak powyższe nie może być dwukierunkowe, w przypadku zmiany z nie ma możliwości automatycznej aktualizacji wartości xiy. Aby wiązanie było dwukierunkowe, musi znajdować się między właściwościami tego samego typu, ale w zamian właściwość może być powiązana z kilkoma innymi właściwościami. Poniższa metoda pokazuje zasady wiązania dwukierunkowego: private static void test05() IntegerProperty x = new SimpleIntegerProperty(3); IntegerProperty y = new SimpleIntegerProperty(5); IntegerProperty z = new SimpleIntegerProperty(7); System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); x.bindbidirectional(y); System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); x.bindbidirectional(z); System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); z.set(11);

13 System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); x.unbindbidirectional(y); x.unbindbidirectional(z); System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); x.set(13); y.set(17); z.set(19); System.out.println("x = " + x.get() + ", y = " + y.get() + ", z = " + z.get()); Trzy właściwości są tworzone ponownie, a pierwsza println () wyświetla wartości tych właściwości przed ich powiązaniem. Następnie x jest powiązane z y, a następna instrukcja println () pokazuje, że x ma teraz tę samą wartość co y. Następnie x jest powiązane z, co oznacza, że x ma taką samą wartość jak z, a ponieważ x jest powiązane z y, a ponieważ wiązanie jest dwukierunkowe, wartość y również staje się wartością x. Następna instrukcja println () pokazuje, że wszystkie trzy właściwości mają teraz tę samą wartość. Teraz wartość z zmienia się na 11, a gdy trzy właściwości są ponownie drukowane, wszystkie mają wartość 11. Następnie powiązania są usuwane, a trzy właściwości są drukowane ponownie, i widać, że nadal mają wartość 11 Na koniec wartości trzech właściwości są zmieniane, po czym są drukowane i wyświetlane są nowe wartości: x = 3, y = 5, z = 7 x = 5, y = 5, z = 7 x = 7, y = 7, z = 7 x = 11, y = 11, z = 11 x = 11, y = 11, z = 11 x = 13, y = 17, z = 19 W JavaFX wszystkie właściwości odczytu / zapisu obsługują wiązanie dwukierunkowe, a jako przykład tego, jak można go użyć, rozważ następującą klasę: package fxproperties; import javafx.application.application; import javafx.scene.*; import javafx.scene.paint.*; import javafx.stage.stage; import javafx.scene.shape.*; import javafx.beans.binding.*;

14 public class CircleView extends public void start(stage primarystage) Circle c = new Circle(); c.setfill(color.red); Group root = new Group(c); Scene scene = new Scene(root, 200, 200); c.centerxproperty().bind(scene.widthproperty().divide(2)); c.centeryproperty().bind(scene.heightproperty().divide(2)); c.radiusproperty().bind(bindings.min(scene.widthproperty(), scene.heightproperty()).divide(2)); primarystage.settitle("binding"); primarystage.setscene(scene); primarystage.show(); Zauważ, że jest to okno JavaFX (dziedziczy aplikację klasy) i istnieje tylko metoda start (). Tutaj czerwone kółko jest wstawiane jako węzeł na wykresie sceny. Następnie trzy właściwości są związane, tj. Środek i promień okręgu, z szerokością i wysokością obiektu sceny, dzięki czemu środek okręgu staje się środkiem okna, a promień staje się największy z możliwych, tak że wyświetla się cały okrąg. W ten sposób okno pokaże okrąg i zmień rozmiar okna, zobaczysz, że okrąg podąża za oknem, ponieważ zawsze znajduje się na środku okna, a promień zmienia się w zależności od rozmiaru okna. Należy również pamiętać, jak otworzyć okno z programu głównego: private static void test06 ()

15 Application.launch (CircleView.class); Jako kolejny przykład metoda test07 () otwiera następujące okno: który zawiera suwak, etykietę i trzy przyciski. W tym przypadku komponent Slider jest powiązany z krokiem właściwości licznika klasy za pomocą wiązania dwukierunkowego, a komponent Label jest powiązany z wartością właściwości licznika klasy za pomocą jednokierunkowego wiązania: public class CounterView extends Application private Counter counter = new public void start(stage primarystage) Slider slider = new Slider(1, 10, 1); Label label = new Label(""); label.setfont(font.font("arial", FontWeight.BOLD, FontPosture.REGULAR, 48)); Button cmd1 = new Button("Reset"); cmd1.setonaction(e -> counter.setstep(1)); Button cmd2 = new Button("Down"); cmd2.setonaction(e -> counter.decrease()); Button cmd3 = new Button("Up"); cmd3.setonaction(e -> counter.increase()); HBox commands = new HBox(10, cmd1, cmd2, cmd3); commands.setalignment(pos.center); VBox root = new VBox(20, slider, label, commands); root.setalignment(pos.top_center); root.setpadding(new Insets(30, 30, 30, 30));

16 Scene scene = new Scene(root, 300, 200); label.textproperty().bind(counter.valueproperty().asstring()); slider.valueproperty().bindbidirectional(counter.stepproperty()); primarystage.settitle("binding"); primarystage.setscene(scene); primarystage.show(); Formant Label ma właściwość StringProperty textproperty, a Licznik ma właściwość valueproperty typu ReadOnlyIntegerWrapper. Ponieważ ten ostatni jest tylko do odczytu, nie można oczywiście utworzyć wiązania dwukierunkowego, ale ReadOnlyIntegerWrapper ma metodę asstring (), która zwraca obiekt StringBinding i pozwala mu utworzyć wiązanie jednokierunkowe. Powoduje to, że jeśli wartość właściwości w obiekcie Licznik zmieni się (klikając przyciski W dół i W górę), wówczas obiekt Etykieta zostanie automatycznie zaktualizowany. Element sterujący suwaka ma wartość Właściwość typu IntegerProperty i to samo dotyczy licznika klasy, który ma właściwość stepproperty tego samego typu. Można zatem utworzyć dwukierunkowe powiązanie między tymi dwiema właściwościami. Powoduje to, że jeśli zmienisz suwak, właściwość kroku obiektu Licznik zostanie zaktualizowana. Kliknięcie przycisku Resetuj powoduje ustawienie wartości kroku na 1, a ponieważ wiązanie suwaka jest dwukierunkowe, składnik suwaka jest automatycznie aktualizowany. ĆWICZENIE 1 Musisz napisać aplikację, która otworzy następujące okno: gdzie możesz przechowywać informacje o osobie. Wyświetlane nazwisko i stanowisko są wartościami domyślnymi. Jeśli klikniesz ostatni przycisk, musisz otrzymać alert pokazujący imię i nazwisko oraz stanowisko, a po kliknięciu pierwszego przycisku wartości należy zmienić na domyślne. Program musi używać następującej klasy modelu: package editperson; import javafx.beans.property.*; public class Person private StringProperty name = new SimpleStringProperty("Gorm den Gamle"); private StringProperty job = new SimpleStringProperty("King");

17 public String getname() return name.get(); public void setname(string name) this.name.set(name); public StringProperty nameproperty() return name; public String getjob() return job.get(); public void setjob(string job) this.job.set(job); public StringProperty jobproperty() return job; a dwa pola wejściowe muszą być powiązane z modelem za pomocą powiązań dwukierunkowych. Po kliknięciu przycisku Domyślne wartości domyślne należy ustawić, bezpośrednio aktualizując model, wykonując w ten sposób metody setname () i setjob ().

18 2.2 OBSERWOWANE KOLEKCJE JavaFX definiuje wiele kolekcji, które są rozszerzeniami klasycznych klas kolekcji. Istnieją trzy zdefiniowane interfejsy - ObervableList <T> - ObervableSet <T> - ObervableMap <K, V> który dziedziczy odpowiednio List <T>, Set <T> i Map <K, V>, ale także dziedziczy interfejs Obserowalny. JavaFX nie ma natychmiast klas, które implementują te interfejsy, ale zamiast tego istnieje klasa FXCollections, która ma metody statyczne, które zwracają obiekty, które implementują te interfejsy. Widziana przez programistę obserwowalna kolekcja to lista, zestaw lub mapa, które można obserwować pod kątem unieważnienia i zmian treści. Rozważ następujący przykład: private static void test08() ObservableList<String> list = FXCollections.observableArrayList("Gorm den Gamle", "Harald Blåtand"); list.addlistener(fxproperties::onchanged); list.addall("svend Tveskæg", "Harald d. 2."); list.add("knud d. Store"); list.remove(3); show(list); java.util.collections.sort(list); show(list); private static void show(java.util.list<string> list) System.out.println(); for (String name : list) System.out.println(name); private static void onchanged(listchangelistener. Change<? extends String> change)

19 System.out.println("List has changed"); Jeśli metoda zostanie wykonana, wynikiem jest: Lista się zmieniła Lista się zmieniła Lista się zmieniła Gorm den Gamle Harald Blåtand Svend Tveskæg Knud d. Lista sklepów uległa zmianie Gorm den Gamle Harald Blåtand Knud d. Sklep Svend Tveskæg Lista jest tworzona z dwóch elementów, a następnie skojarzony jest z nią ChangeListener, który nic nie robi, tylko drukuje komunikat na konsoli. Pierwsze zdarzenie ma miejsce po dodaniu dwóch dodatkowych elementów, a następne po dodaniu kolejnego elementu. Wreszcie trzecia występuje po usunięciu elementu. Metoda show () pokazuje zawartość listy na konsoli, a następnie odczytuje listę. Zauważ, że parametrem jest List <String>, co jest w porządku, ponieważ ObservableList <String> jest specjalnie Listą <String>. Po wydrukowaniu listy jest ona sortowana, a ponieważ oznacza to, że kolejność elementów listy zmienia się, następuje kolejne ChangeEvent. Klasa ListChangeListener.Change ma wiele różnych metod, które informują o przyczynie zdarzenia i zachęcamy do zbadania, które z nich. Poniższy przykład powinien pokazać trochę, w jaki sposób można zastosować te metody. Po pierwsze, dodałem prostą klasę modelu z dwiema właściwościami, które są zgodne ze wzorem bean JavaFX: package fxproperties; import javafx.beans.property.*; public class Person implements Comparable<Person> private StringProperty name = new SimpleStringProperty(); private StringProperty job = new SimpleStringProperty(); public Person(String name, String job)

20 setname(name); setjob(job); public final String getname() return name.get(); public final void setname(string name) this.name.set(name); public StringProperty nameproperty() return name; public final String getjob() return job.get(); public final void setjob(string job) this.job.set(job); public StringProperty jobproperty()

21 return public int compareto(person p) int val = getname().compareto(p.getname()); if (val == 0) val = this.getjob().compareto(p.getjob()); return public String tostring() return getname() + ": " + getjob(); a w stosunku do tego, co powiedziano wcześniej, nie ma nic nowego. Należy jednak pamiętać, że obiekty klasy są porównywalne i dlatego można je sortować. Następnie zdefiniowałem klasę detektora dla zdarzeń zmiany dla kolekcji ObservableList z obiektami Person: package fxproperties; import javafx.collections.listchangelistener; public class PersonChangeListener implements public void onchanged(listchangelistener.change<? extends Person> change) while (change.next())

22 if (change.waspermutated()) permutated(change); else if (change.wasupdated()) updated(change); else if (change.wasreplaced()) replaced(change); else if (change.wasremoved()) removed(change); else if (change.wasadded()) added(change); private void permutated(listchangelistener.change<? extends Person> change) System.out.println("Sort: " + change.getfrom() + " "+ change.getto()); private void updated(listchangelistener.change<? extends Person> change) System.out.println("Updated: " + change.getlist().sublist(change.getfrom(), change.getto())); private void replaced(listchangelistener.change<? extends Person> change) removed(change); added(change); private void removed(listchangelistener.change<? extends Person> change) System.out.println("Removed " + change.getremovedsize() + " person(s): " + change.getremoved()); private void added(listchangelistener.change<? extends Person> change) System.out.println("Added " + change.getaddedsize() + " person(s): " + change.getaddedsublist());

23 Pojedyncza procedura obsługi zdarzeń musi zostać zaimplementowana o nazwie onchanged (), której parametrem jest a ListChangeListener.Change <? rozszerza Osoba> obiekt o nazwie zmiana. Gdy wystąpi zdarzenie i zostanie wywołana metoda, zdarzenie może zostać wyzwolone przez jedną lub więcej zmian na liście, a zatem metoda rozpoczyna się od pętli, która iteruje te zmiany. Jak pokazano w metodzie, może istnieć 5 różnych rodzajów zmian, a każda iteracja pętli wywołuje metodę odpowiadającą danej zmianie. W tym przypadku wszystkie metody są trywialne i nie robią nic poza drukowaniem tekstu na ekranie, a celem jest pokazanie tylko w przypadku wystąpienia zdarzenia. Przy powyższych dwóch klasach możesz wykonać następującą metodę: private static void test09() Person p1 = new Person("Gudrun Jensen", "Heks"); Person p2 = new Person("Carlo Andersen", "Skarpretter"); Person p3 = new Person("Valborg Kristensen", "Spåkone"); Person p4 = new Person("Egon Knudsen", "Kriger"); Person p5 = new Person("Abelone Jensen", "Sin mands kone"); Person p6 = new Person("Viktor Hansen", "Høvding"); Callback<Person, Observable[]> cb = (Person p) -> new Observable[] p.nameproperty(), p.jobproperty() ; ObservableList<Person> list = FXCollections.observableArrayList(cb); list.addlistener(new PersonChangeListener()); System.out.println(list); list.add(p1); System.out.println(list); list.addall(p2, p3); System.out.println(list); FXCollections.sort(list); System.out.println(list); p1.setname("gunhild Mikkelsen"); p2.setjob("natmand"); list.set(0, new Person("Olga Hansen", "Sypige")); System.out.println(list); list.setall(p4, p5, p6); System.out.println(list); list.removeall(p4, p6); System.out.println(list);

24 Wykonanie metody daje wynik: [] Dodano 1 osoba (-y): [Gudrun Jensen: Heks] [Gudrun Jensen: Heks] Dodano 2 osób: [Carlo Andersen: Skarpretter, Valborg Kristensen: Spåkone] [Gudrun Jensen: Heks, Carlo Andersen: Skarpretter, Valborg Kristensen: Spåkone] Sortuj: 0 3 [Carlo Andersen: Skarpretter, Gudrun Jensen: Heks, Valborg Kristensen: Spåkone] Zaktualizowano: [Gunhild Mikkelsen: Heks] Zaktualizowano: [Carlo Andersen: Natmand] Usunięto 1 osób: [Carlo Andersen: Natmand] Dodano 1 osoba (y): [Olga Hansen: Sypige] [Olga Hansen: Sypige, Gunhild Mikkelsen: Heks, Valborg Kristensen: Spåkone] Usunięto 3 osób: [Olga Hansen: Sypige, Gunhild Mikkelsen: Heks, Valborg Kristensen: Spåkone] Dodano 3 osób: [Egon Knudsen: Kriger, Abelone Jensen: Sin mands kone, Viktor Hansen: Høvding] [Egon Knudsen: Kriger, Abelone Jensen: Sin mands kone, Viktor Hansen: Høvding] Usunięto 1 osób: [Egon Knudsen: Kriger] Usunięto 1 osób: [Viktor Hansen: Høvding] [Abelone Jensen: Sin mands kone] Metoda testowa rozpoczyna się od utworzenia obiektów 6-osobowych. Następnie definiowany jest obiekt wywołania zwrotnego, który wskazuje, które właściwości obiektu Person należy obserwować w odniesieniu do zmian. W następnym kroku klasa FXCollections jest używana do utworzenia ObservableList <Person> z powyższym obiektem Callback jako parametrem. Następnie lista jest drukowana na ekranie, który jest pierwszym wierszem wyniku, i jest tylko pustą listą. Następna instrukcja dodaje p1 do listy, po czym lista jest drukowana ponownie. Wynik pokazuje, że moduł obsługi zdarzeń wykonał metodę added () i dodał listę obiektów, a następnie lista zawiera jeden obiekt. W następnej operacji p2 i p3 są dodawane do listy i jest drukowane ponownie. Wynik pokazuje, że moduł obsługi zdarzeń wykonał ponownie metodę added () i dodał dwa obiekty, a lista zawiera teraz 3 obiekty. Następnie lista jest sortowana i ponownie drukowana. Jak pokazuje wynik, procedura obsługi zdarzeń wykonała metodę permutowaną () odpowiadającą co najmniej dwóm obiektom listy zostały zastąpione. Następnie wartość właściwości odpowiednio dla p1 i p2 zostaje zmieniona i tutaj należy zauważyć, że moduł obsługi zdarzeń wykonuje metodę updated () i należy zauważyć, że nie zmieniono zawartości listy, ale obiekty, które lista zawiera. Następna instrukcja ponownie zmienia rzeczywistą zawartość listy: list.set (0, nowa osoba ( Olga Hansen, Sypige )); i możesz zobaczyć z wyniku, że moduł obsługi zdarzeń wykonał metodę replace (). Po ponownym wydrukowaniu listy metoda wykona instrukcję

25 list.setall (p4, p5, p6); co oznacza, że zawartość listy zostaje zastąpiona trzema nowymi obiektami. Z wyniku widać, że zarówno metoda remove (), jak i added () są wykonywane. Na koniec dwa obiekty są usuwane, a następnie jeden zostaje. Patrząc na powyższy przykład, który powinien zilustrować działanie ObservableList, możesz napisać podobne programy, które mogą zilustrować działanie ObservableSet i ObservableMap. Nie chcę tutaj pokazywać przykładów, ponieważ nie ma dużych różnic w porównaniu z powyższym przykładem. 2.3 WIĄŻĄCE OBSERWOWANE KOLEKCJE Istnieje klasa ListProperty, która reprezentuje właściwość dla ObservableList, i można myśleć o tej klasie jako opakowaniu dla ObervableList. Jest to typ właściwości podobny do innych właściwości przedstawionych w tym rozdziale. Możesz powiązać trzy rodzaje detektorów z ListProperty: - InvallidationListener - ChangeListener - ListChangeListener i nasi słuchacze otrzymają powiadomienia o zmianie OberservableList jako Obiekt ListProperty w opakowaniu opakowania. Rozważ następującą metodę: private static void test10() ListProperty<String> property = new SimpleListProperty(FXCollections.observableArrayList()); IntegerProperty count = new SimpleIntegerProperty(); count.bind(property.sizeproperty()); System.out.println(count.get()); property.addlistener(fxproperties::propertyinvalidated); property.addlistener(fxproperties::propertychanged); property.addlistener(fxproperties::propertylistchanged); property.add("gorm"); property.add("harald"); System.out.println(count.get()); property.set(fxcollections.observablearraylist("svend", "Knud", "Valdemar")); System.out.println(count.get()); property.remove("knud"); System.out.println(count.get());

26 System.out.println(property.get()); Metoda tworzy właściwość o nazwie ListProperty jako SimpleListProperty, która otacza ObservableList. Ponadto liczba całkowita jest zdefiniowana z liczbą nazw i jest ograniczona do wielkości listy. Należy tutaj zauważyć, że ListProperty implementuje interfejs ObservableList, a zatem ma te same właściwości co ObservableList. właściwość ma zatem właściwość sizeproperty, która jest liczbą całkowitą i dlatego może być ograniczona do zliczania. Następna instrukcja drukowania wydrukuje zatem 0 na ekranie, ponieważ lista jest obecnie pusta. Następnym krokiem są metody detektora dla trzech zdarzeń. Pierwszy jest trywialny i nie robi nic poza drukowaniem tekstu: private static void propertyinvalidated(observable list) System.out.println("Property invalid "); Należy zauważyć, że parametr jest obserwowalny. Następna metoda nasłuchiwania jest również trywialna i wypisuje zawartość listy przed i po jej zmianie: private static void propertychanged( ObservableValue<? extends ObservableList<String>> observable, ObservableList<String> oldlist, ObservableList<String> newlist) System.out.println("Old: " + oldlist); System.out.println("New: " + newlist); Należy przede wszystkim zwrócić uwagę na parametry, które są odniesieniem do listy (obiekt, który spowodował dane zdarzenie) oraz wartość listy przed zmianą i jej wartość po zmianie. Ostatni moduł obsługi zdarzeń nie robi nic poza drukowaniem tekstu (prawdopodobnie więcej), ale rozróżnia przyczynę zdarzenia: private static void propertylistchanged( ListChangeListener.Change<? extends String> change) while (change.next()) if (change.waspermutated()) System.out.println("Permutated"); else if (change.wasupdated()) System.out.println("Updated"); else if (change.wasreplaced()) System.out.println("Replaced"); else if (change.wasremoved()) System.out.println("Removed");

27 else if (change.wasadded()) System.out.println("Added"); Poniżej pokazano wynik wykonania metody testowej: 0 Property invalid Old: [Gorm] Added Property invalid Old: [Gorm, Harald] New: [Gorm, Harald] Added 2 Property invalid Old: [Gorm, Harald] New: [Svend, Knud, Valdemar] Replaced 3 Property invalid Old: [Svend, Valdemar] New: [Svend, Valdemar] Removed 2 [Svend, Valdemar] Łatwo jest śledzić kod programu i porównywać z wynikami, a ważne jest, aby obserwować, kiedy wykonuje się każda procedura obsługi zdarzenia. Możesz także powiązać ze sobą dwa obiekty ListProperty, co oznacza, że listy, które zawijają, są powiązane. Rozważ następującą metodę: private static void test11() ListProperty<String> property1 = new SimpleListProperty<>(FXCollections.observableArrayList()); ListProperty<String> property2 = new SimpleListProperty<>(FXCollections.observableArrayList()); property1.add("kristian"); property2.add("frederik"); System.out.println(property1.get()); System.out.println(property2.get()); property1.bind(property2); property1.addall("svend", "Knud", "Valdemar"); System.out.println(property1.get());

28 System.out.println(property2.get()); property2.set(fxcollections.observablearraylist("gorm", "Harald")); System.out.println(property1.get()); System.out.println(property2.get()); // property1.set(fxcollections.observablearraylist("harld", "Oluf")); property1.unbind(); property1.bindbidirectional(property2); property1.set(fxcollections.observablearraylist("erik", "Kristoffer")); property2.set(fxcollections.observablearraylist("niels", "Abel")); property1.add("oluf"); property2.add("knud"); System.out.println(property1.get()); System.out.println(property2.get()); Powyżej tego zdefiniowano dwa obiekty ListProperty, które zawijają każdy swój ObservableList, a zatem zostaną wydrukowane pierwsze dwie instrukcje print: [Kristian] [Frederik] w czym nie ma nic dziwnego. Ze względu na właściwości JavaFX można je oczywiście powiązać: property1.bind (property2); która wiąże właściwość1 z właściwością2 za pomocą jednokierunkowego wiązania. Dokładnie oznacza to, że lista, dla której właściwość1 jest opakowaniem, jest tą samą listą, dla której właściwość2 jest opakowaniem. Następna instrukcja dodaje trzy nazwy do właściwości1, a następna instrukcja ponownie określa nazwę do właściwości2. Kolejne dwie statystyki drukowania dają wynik [Frederik, Svend, Knud, Valdemar, Hans] [Frederik, Svend, Knud, Valdemar, Hans] i być może nie było to, czego można by oczekiwać, ponieważ jest to wiązanie jednokierunkowe, ale ponieważ dwa obiekty właściwości zawijają tę samą listę (i tę, dla której właściwość2 była pierwotnie opakowana), w obu przypadkach lista jest aktualizowana. Jeśli następnie wykonasz instrukcję property2.set (FXCollections.observableArrayList ( Gorm, Harald )); właściwość2 jest teraz opakowaniem dla listy o dwóch nazwach, a ze względu na powiązanie obie właściwości będą się odnosić do tej listy. Jeśli zamiast tego spróbujesz wykonać instrukcję, która jest komentowana, otrzymasz wyjątek ze względu na fakt, że propert1 jest powiązany z właściwością 2 z jednokierunkowym wiązaniem. Dlatego nie zmieniaj obiektu, ponieważ właściwość1 jest opakowaniem. Następnie powiązanie jest usuwane, a zamiast tego tworzone jest powiązanie dwukierunkowe. Teraz możesz zmienić listę obu właściwości, ponieważ wiązanie jest teraz dwukierunkowe i obowiązuje ostatnia wartość. Ostatnie instrukcje wydruku powodują zatem:

29 [Niels, Abel, Oluf, Knud] [Niels, Abel, Oluf, Knud] Powyższe powiązania wiążą listy, dla których właściwości są opakowaniami, ale zamiast tego można powiązać ich zawartość, tak jak w przypadku metod bindcontent () i bindcontentbidirectional (): private static void test12() ListProperty<String> property1 = new SimpleListProperty<>(FXCollections.observableArrayList()); ListProperty<String> property2 = new SimpleListProperty<>(FXCollections.observableArrayList()); property1.bindcontent(property2); property1.addall("svend", "Knud", "Valdemar"); System.out.println(property1.get()); System.out.println(property2.get()); property2.set(fxcollections.observablearraylist("gorm", "Harald")); System.out.println(property1.get()); System.out.println(property2.get()); property1.unbindcontent(property2); property1.bindcontentbidirectional(property2); property1.add("oluf"); property2.add("knud"); System.out.println(property1.get()); System.out.println(property2.get()); Powyżej określa powiązanie zawartości dwóch list, ale oznacza to, że zawartość dwóch list przez wiązanie jednokierunkowe nie jest zsynchronizowana: [Svend, Knud, Valdemar] [] [Gorm, Harald, Svend, Knud, Valdemar] [Gorm, Harald] [Gorm, Harald, Oluf, Knud] [Gorm, Harald, Oluf, Knud] Jako ostatni przykład pokażę, jak powiązać z pojedynczym elementem w ObservableList:

30 private static void test13() ListProperty<Person> property = new SimpleListProperty<>(FXCollections.observableArrayList()); ObjectBinding<Person> last = property.valueat(property.sizeproperty().subtract(1)); property.add(new Person("Gudrun Jensen", "Heks")); property.add(new Person("Carlo Andersen", "Skarpretter")); property.add(new Person("Valborg Kristensen", "Spåkone")); System.out.println(property.get()); System.out.println(last.get()); property jest ListProperty, która otacza ObservableList obiektami Person. last to obiekt ObjectBinding dla obiektu Person, który jest powiązany z ostatnim elementem na liście, dla którego właściwość jest opakowana. Zwróć uwagę na składnię. Metoda valueat () ma jako parametr indeks i zwraca ObjectBinding do obiektu na liście, do której odwołuje się indeks. Po dodaniu trzech obiektów do listy instrukcje drukowania pokazują: [Gudrun Jensen: Heks, Carlo Andersen: Skarpretter, Valborg Kristensen: Spåkone] Valborg Kristensen: Spåkone W taki sam sposób, jak pokazano w tej sekcji, istnieją również właściwości opakowania o nazwie SetProperty i MapProperty odpowiednio dla ObeservableSet i ObservableMap. ĆWICZENIE 2 Utwórz prostą aplikację konsoli, którą możesz wywołać BindingElements. Projekt FXProperties ma klasę Osoba. Skopiuj tę klasę do nowego projektu i dodaj następującą prostą metodę do klasy BindingElements: private static void print(list<person> list) for (Person p : list) System.out.println(p); System.out.println(); Następnie napisz następującą metodę main (): public static void main(string[] args) // Create an ObservableList<Person> to Person objects // Add 5 Person objects to list, what names and job titles do not matter

31 // Create a ListProperty for the above list // Define a binding for the element with index 1 // Define a binding for the element with index 3 // Print the list on the screen // Insert a new Person in list at position 1 // Insert a new Person in list at position 3 // Modify the name of the Person that is bound with p1 // Modify the job title of the Person that is bound with p3 // Print the list on the screen Przetestuj program. Czy otrzymujesz oczekiwany wynik? Czy są oczekiwane obiekty, które zostały zmienione? 2.4 WIĄZANIE OSÓB Jako ostatni przykład właściwości aplikacja PersonProgram otwiera następujące okno: w dwóch górnych polach wprowadzania możesz wpisać imię i nazwisko oraz stanowisko. Jeśli klikniesz OK, osoba zostanie dodana do ListView. W powyższym oknie utworzono / wprowadzono 6 osób. Po dwukrotnym kliknięciu osoby w kontrolce ListView dane tej osoby zostaną wstawione do pól wejściowych, a następnie można je edytować. Dwukrotne kliknięcie osoby można usunąć, klikając przycisk Usuń. Na koniec przycisk Wyczyść służy do usuwania pól wprowadzania i usuwania zaznaczenia w kontrolce ListView. Dolny przycisk służy do usuwania zawartości listy. Program korzysta z klasy Osoba z projektu FXProperties - rozwiniętej o dwie trywialne metody. Kod dla okna PersonProgram jest następujący: package personprogram;

32 import javafx.application.application; import javafx.event.*; import javafx.scene.scene; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.stage.stage; import javafx.geometry.*; import javafx.scene.input.*; import javafx.collections.*; import javafx.beans.property.*; public class PersonProgram extends Application private TextField txtname = new TextField(); private TextField txtjob = new TextField(); private ListView lstview = null; private Person person = new Person("", ""); private ObservableList<Person> persons = FXCollections.observableArrayList(); private IntegerProperty selected = new public void start(stage primarystage) lstview = new ListView(persons); lstview.setprefheight(300); lstview.setonmouseclicked(this::clickhandler); selected.bind(lstview.getselectionmodel().selectedindexproperty()); BorderPane root = new BorderPane(lstView, createtop(), null, createbottom(), null); root.setpadding(new Insets(20, 20, 20, 20));

33 BorderPane.setMargin(lstView, new Insets(20, 0, 20, 0)); Scene scene = new Scene(root); primarystage.settitle("persons"); primarystage.setscene(scene); primarystage.show(); private Pane createbottom() HBox pane = new HBox(createButton("Delete", this::deletehandler)); pane.setalignment(pos.center_right); return new BorderPane(null, null, pane, null, null); private Pane createtop() txtname.textproperty().bindbidirectional(person.nameproperty()); txtjob.textproperty().bindbidirectional(person.jobproperty()); txtname.setprefwidth(300); HBox commands = new HBox(10, createbutton("remove", this::removehandler), createbutton("clear", this::clearhandler), createbutton("ok", this::okhandler)); commands.setalignment(pos.center_right); GridPane pane = new GridPane(); pane.setvgap(10); pane.sethgap(20); pane.addrow(0, new Label("Name"), txtname); pane.addrow(1, new Label("Job"), txtjob); pane.add(commands, 1, 2); return pane; private Button createbutton(string text, EventHandler<ActionEvent> handler)

34 Button cmd = new Button(text); cmd.setonaction(handler); return cmd; private void okhandler(actionevent e) if (person.getname().trim().length() > 0 && person.getjob().trim().length() > 0) if (selected.get() < 0) persons.add(new Person(person.getName(), person.getjob())); else persons.set(selected.get(), new Person(person.getName(), person.getjob())); clearhandler(e); private void clearhandler(actionevent e) person.clear(); lstview.getselectionmodel().clearselection(); txtname.requestfocus(); private void removehandler(actionevent e) if (selected.get() >= 0) persons.remove(selected.get()); clearhandler(e); private void deletehandler(actionevent e) persons.clear(); clearhandler(e);

35 private void clickhandler(mouseevent e) if (e.getbutton() == MouseButton.PRIMARY && e.getclickcount() == 2) if (selected.get() >= 0) person.update(persons.get(selected.get())); else lstview.getselectionmodel().clearselection(); public static void main(string[] args) launch(args); Klasa ma 6 zmiennych instancji, z których pierwsze trzy dotyczą kontroli. Następny ma typ Osoba i reprezentuje obiekt, na którym pracujesz, a zatem obiekt, którego właściwości pojawiają się w dwóch polach wprowadzania. Kolejny raz to ObservableList i reprezentuje zawartość kontrolki ListView. Wreszcie ostatnia jest właściwością, która musi reprezentować indeks obiektu Person na wybranej liście. Badanie metody start () jest pierwszą rzeczą, która się dzieje, że obiekt lstview jest tworzony z osobami jako parametrem konstruktora. Oznacza to, że między komponentem ListView a osobami z listy jest tworzone powiązanie dwukierunkowe. Program obsługi zdarzeń jest również powiązany ze składnikiem ListView zdarzeń myszy. Na koniec wybrana właściwość jest powiązana z wybranym indeksem w komponencie ListView. Reszta metody start () nie zawiera niczego nowego i jest przeznaczona tylko do utworzenia wykresu sceny okna. Należy jednak zwrócić uwagę na metodę createtop (), która tworzy GridPane do wprowadzania osoby. W tym miejscu należy szczególnie zauważyć, w jaki sposób dwa formanty TextField wiążą się z właściwościami obiektu Person. Są też procedury obsługi zdarzeń, w których jest ich 5, ale generalnie są one proste. Pierwszy dotyczy przycisku OK i zaczyna się od sprawdzenia, czy coś zostało wprowadzone zarówno dla imienia, jak i stanowiska. W takim przypadku wybrana właściwość służy do ustalenia, czy dodać nowy obiekt, czy też istniejący obiekt ma zostać zmodyfikowany. Należy zauważyć, że w obu przypadkach tworzony jest instancja nowego obiektu Person. Jest to konieczne, ponieważ zmiennej person nie można ustawić tak, aby odwoływała się do innego obiektu, ponieważ jest on powiązany z polami wejściowymi. Niezależnie od tego, czy dodajesz obiekt, czy modyfikujesz obiekt, wywoływana jest procedura obsługi przycisku Wyczyść, która usuwa pola w obiekcie osoby i usuwa wszelkie zaznaczenia w składniku ListView. Procedury obsługi zdarzeń dla przycisków Usuń i Usuń są trywialne, a procedura obsługi zdarzeń dla kliknięć myszą w składniku ListView jest również prosta i należy szczególnie zwrócić uwagę na sposób

36 testowania podwójnego kliknięcia. Podczas testowania programu zwróć uwagę na automatyczną aktualizację interfejsu użytkownika z powodu powiązań. 2.5 EKRAN W tych i następnych dwóch sekcjach pokrótce wspomnę o tym, jak odnosić się do ekranu i trochę więcej uwag na temat obiektu Stage programu - chociaż nie ma on wiele wspólnego z właściwościami, ale przykłady są częścią projektu FXProperties. Następująca klasa otwiera okno, w którym drukowane są informacje o wielkości ekranu i komponentów na konsoli: public class ScreenView extends public void start(stage stage) BorderPane root = new BorderPane(new Label("Tekst")); root.setpadding(new Insets(30, 30, 30, 30)); Scene scene = new Scene(root, 300, 200); stage.setscene(scene); stage.settitle("screen"); stage.setwidth(500); stage.setheight(400); stage.show(); showsize(); showsize(stage); showsize(root); private void showsize() Screen screen = Screen.getPrimary(); Rectangle2D r1 = screen.getbounds(); Rectangle2D r2 = screen.getvisualbounds(); System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n", r1.getminx(), r1.getminy(), r1.getwidth(), r1.getheight());

37 System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n\n", r2.getminx(), r2.getminy(), r2.getwidth(), r2.getheight()); private void showsize(stage s) System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n\n", s.getx(), s.gety(), s.getwidth(), s.getheight()); private void showsize(pane p) Bounds b1 = p.getboundsinlocal(); Bounds b2 = p.getboundsinparent(); Bounds b3 = p.getlayoutbounds(); System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n", b1.getminx(), b1.getminy(), b1.getwidth(), b1.getheight()); System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n", b2.getminx(), b2.getminy(), b2.getwidth(), b2.getheight()); System.out.printf("(%1.0f, %1.0f) %1.0f x %1.0f\n\n", b3.getminx(), b3.getminy(), b3.getwidth(), b3.getheight()); Program jest prosty i nic nie robi, tylko tworzy okno z przyciskiem. Należy zauważyć, że gdy obiekt główny jest dodawany do obiektu sceny, rozmiar okna jest definiowany, a zatem staje się rozmiarem obiektu Stage, ale także wielkości obiektu głównego, czyli BorderPane. Rozmiar ten jest zmieniany później w metodzie start (), częściowo, aby pokazać, że jest to możliwe, a częściowo, aby pokazać, że nie zmienia rozmiaru obiektu głównego. Po wyświetleniu okna na ekranie wywoływane są trzy metody, które drukują tekst na konsoli. Należy zauważyć, że metody te należy wywołać po wyświetleniu obiektu Stage i renderowaniu komponentów wykresu sceny. Pierwsza metoda wykorzystuje klasę Screen, która ma metody statyczne, które zwracają obiekt Screen reprezentujący ekran i który jest używany do określania właściwości ekranu. W tym przypadku stosowane są dwie metody, które zwracają obiekt Rectangle2D z informacją o rozmiarze ekranu (mierzonym w pikselach, a zatem i rozdzielczości ekranu). Pierwszy zwraca fizyczny rozmiar ekranu, podczas gdy drugi zwraca rozmiar dostępny dla programu. Różnica zależy od bieżącej platformy, ale wynik może wyglądać następująco: (0, 0) 1920 x 1080 (0, 27) 1920 x 1020 Różnica polega na tym, że mój ekran u góry ma linię aktywności (Fedora) i poniżej paska zadań.

38 Następna metoda drukuje rozmiar i pozycję obiektu Stage na ekranie w lewym górnym rogu. W tym miejscu należy zauważyć, jakie właściwości ma klasa Stage pod względem pozycji i rozmiaru oraz że rozmiar jest określony przez wartości, które przypisałem w metodzie start (): (710, 234) 500 x 400 Ostatnia metoda wyświetla informacje o rozmiarze węzła, ponieważ tutaj jest root, a zatem BorderPane. Rozmiar jest określany za pomocą trzech metod, z których wszystkie zwracają powiązany obiekt: 1. getboundsinlocal (), jako pozycja i rozmiar przed każdą możliwą transformacją 2. getboundsinparent (), która jest pozycją i rozmiarem po możliwej transformacji 3. getlayoutbounds (), która jest pozycją i rozmiarem używanym przez panel układu do obliczeń i może różnić się od innych rozmiarów i jest używana, jeśli piszesz niestandardowe kontrolki W tym przypadku wszystkie są do siebie podobne. 2.6 DEKORACJE Typowe okno wygląda mniej więcej tak gdzie znajduje się pasek tytułu i ramka, dzięki czemu można zmienić rozmiar okna. Ponadto istnieje zawartość okna, jak tutaj wykres sceny z etykietą i przyciskiem. Sposób wyświetlania paska tytułowego i ramki zależy od bieżącej platformy, a dla paska tytułowego platforma określa, które przyciski są dostępne. Pasek tytułu okna służy również do przesuwania okna na ekranie poprzez przeciąganie go myszką i na przykład maksymalizowanie go poprzez dwukrotne kliknięcie paska tytułu. Pasek tytułu i obramowanie są nazywane dekoracjami lub stylami okna, a tutaj jest więcej opcji, które są zdefiniowane jako właściwości obiektu Stage. Chcę wspomnieć o trzech: 1. StageStyle.DECORATED, który jest ustawieniem domyślnym, w którym okno ma pasek tytułu i ramkę 2. StageStyle.UNDECORATED, w którym okno nie jest ozdobione, a zatem nie ma paska tytułu ani ramki 3. StageStyle.TRANSPARENT, w którym okno nie jest ozdobione, a także ma przezroczyste tło Na przykład, jeśli zdefiniujesz powyższe okno jako UNDECORATED, wynikiem będzie: a jeśli zdefiniujesz okno jako PRZEJRZYSTE, wynikiem będzie:

39 Powyższe można zilustrować następującą klasą: public class StyleView extends Application private Stage stage; private double xpos; private double ypos; public void start(stage stage) this.stage = stage; Label lbl = new Label(); Button cmd = new Button("Close"); cmd.setonaction(e -> stage.close()); VBox root = new VBox(10, lbl, cmd); root.setalignment(pos.center); Scene scene = new Scene(root, 200, 100); stage.setscene(scene); stage.settitle("styleview"); this.show(scene, lbl, StageStyle.DECORATED); // this.show(scene, lbl, StageStyle.UNDECORATED); // this.show(scene, lbl, StageStyle.TRANSPARENT); private void show(scene scene, Label lbl, StageStyle style) lbl.settext(style.tostring()); stage.initstyle(style); if (style == StageStyle.UNDECORATED style == StageStyle.TRANSPARENT) scene.setonmousepressed(e -> handlemousepressed(e)); scene.setonmousedragged(e -> handlemousedragged(e)); if (style == StageStyle.TRANSPARENT) stage.getscene().setfill(null);

40 stage.getscene().getroot().setstyle("-fx-background-color: transparent"); stage.show(); private void handlemousepressed(mouseevent e) xpos = e.getscreenx() stage.getx(); ypos = e.getscreeny() stage.gety(); private void handlemousedragged(mouseevent e) stage.setx(e.getscreenx() xpos); stage.sety(e.getscreeny() ypos); gdzie komentarze w metodzie start () wskazują, jak należy ozdobić okno. W metodzie start () niewiele się dzieje oprócz tworzenia wykresu sceny i inicjowania obiektu Stage. Zauważ, że tym razem klasa ma zmienną instancji, która odwołuje się do obiektu Stage, dzięki czemu można się do niej odwoływać z innych metod klasowych. Zwróć również uwagę na procedurę obsługi zdarzenia na przycisk Zamknij, który zamyka okno, tym samym kończąc program. Działa w tym przypadku, ale niekoniecznie w innych programach. Jeśli wykonasz close () na obiekcie Stage, spowoduje to ukrycie okna (wykonaj metodę hide () w klasie bazowej na stole montażowym o nazwie Window) i jeśli nie ma innych okien, program się zakończy. Jeśli chcesz zakończyć program natychmiast po kliknięciu przycisku, powinieneś zamiast tego uruchomić Platform.exit (). Okno otwiera się w metodzie show (), która ma trzy parametry: wykres sceny, sterowanie etykietą i styl okna, a tym samym sposób jego dekoracji. Metoda rozpoczyna się od ustawienia tekstu dla kontrolki Etykieta okna, a następnie sposobu dekorowania obiektu Stage. Tutaj należy zwrócić uwagę na składnię i że należy to zrobić, zanim obiekt Stage pojawi się na ekranie. Jeśli styl jest PRZEJRZYSTY, oznacza to, że nie ma tła. Jeśli okno jest NIEZDEKOROWANE, występuje problem, ponieważ nie można go przenieść myszką. Jeśli chcesz, możesz, jak pokazano powyżej, powiązać moduł obsługi zdarzeń myszy z obiektem Scene, a następnie kontrolować, czy okno można przenieść. 2.7 MODALNOŚĆ Jak wiadomo z Swing, okno dialogowe może być niemodalne lub modalne, i oczywiście dotyczy również JavaFX, gdzie jest własnością obiektu Stage. Istnieją trzy opcje: 1. Modality.ONE, gdzie wynikiem jest okno niemodalne, które jest domyślne dla stołu montażowego 2. Modality.WINDOW_MODAL, który jest modalny i blokuje wszystkie okna, które bezpośrednio lub pośrednio posiadają to okno

41 3. Modality.APPLICATION_MODEL, który jest modalny i blokuje wszystkie pozostałe okna aplikacji Poniższy przykład pokazuje składnię, ale pokazuje również, że aplikacja może mieć wiele obiektów Stage, a tym samym okna (lub okna dialogowe), które jawnie tworzą obiekt Stage. Klasa StageView otwiera następujące okno: gdzie każdy z czterech górnych przycisków otwiera okno dialogowe poprzez utworzenie nowego obiektu Stage, a różnica polega częściowo na modalności, a częściowo na tym, że okno dialogowe ma właściciela. Na przykład pierwszy to modelka i bez właściciela. Oznacza to, że kliknięcie górnego przycisku powoduje otwarcie okna dialogowego, a następnie kliknięcie krzyżyka na pasku tytułu okna głównego (i można to zrobić, ponieważ okno dialogowe jest niemodalne) zamyka okno główne, ale nie okno dialogowe, a program nie zakończyć przed zamknięciem drugiego okna. Ostatni przycisk jest dołączony, aby pokazać, że obiekt Stage ma również metodę, dzięki czemu można powiedzieć, że okno powinno być wyświetlane na pełnym ekranie. Zauważ, że wyświetlając okno pełnego ekranu, powracasz do normalnego oglądania, naciskając ESC - lub ponownie klikając przycisk Pełny ekran. Kod jest następujący: public class StagesView extends public void start(stage stage) VBox root = new VBox(20, createbutton("no owner, NONE", e -> showdialog(null, null)), createbutton("owner, NONE", e -> showdialog(stage, Modality.NONE)), createbutton("owner, WINDOW_MODAL", e -> showdialog(stage, Modality.WINDOW_MODAL)), createbutton("owner, APPLICATION_MODAL", e -> showdialog(stage, Modality.APPLICATION_MODAL)), createbutton("full screen", e -> stage.setfullscreen(!stage.isfullscreen()))); root.setalignment(pos.center);

42 root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root); stage.setscene(scene); stage.settitle("primary Stage"); stage.show(); private Button createbutton(string text, EventHandler<ActionEvent> handler) Button cmd = new Button(text); cmd.setonaction(handler); return cmd; private void showdialog(window owner, Modality modality) Stage stage = new Stage(); stage.initowner(owner); if (modality!= null) stage.initmodality(modality); VBox root = new VBox(20, new Label(owner == null? "Default" : "Parent Window"), new Label(modality == null? "Default" : modality.tostring()), createbutton("close", e -> stage.close())); root.setalignment(pos.center); root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root); stage.setscene(scene); stage.settitle("stage"); stage.show(); Nie ma wiele do wyjaśnienia, ale należy zwrócić uwagę na metodę showdialog (), która tworzy okno dialogowe poprzez utworzenie nowego obiektu Stage. PROBLEM 1

43 W tym zadaniu musisz napisać program, który działa w taki sam sposób, jak program z sekcji 2.4. Padata bazy danych ma tabelę z historią nazw, która zawiera informacje o osobach historycznych (patrz, jeśli dotyczy, książka Java 6). Tabela jest tworzona za pomocą następującego skryptu: use padata; drop table if exists history; create table history ( id int not null auto_increment primary key, name varchar(50) not null, title varchar(30), birth int, death int, country char(2), description text # autogenerated surrogat key # the person's name # the person's job title # birth, start of reign, or equivalent # the year of death, end of reign, or equivalent # the country the person comes from # a description ); Musisz napisać program, który może utrzymywać tę tabelę bazy danych. Na przykład możesz wywołać projekt dla Historii. Zadanie można rozwiązać na kilka sposobów, ale pomysł polega na użyciu powiązania danych, częściowo podczas wyświetlania danych, a częściowo podczas edycji informacji o jednej osobie. Kiedy program się uruchomi, musi wyświetlić listę wszystkich osób w bazie danych i może to być okno pokazane poniżej, z ListView pokazującym nazwiska wszystkich osób: Przycisku należy użyć do utworzenia nowej osoby, a jeśli dwukrotnie klikniesz nazwisko na liście, program musi pokazywać wszystkie informacje o osobie i musi istnieć możliwość edycji informacji. W obu przypadkach należy użyć tego samego okna dialogowego, które może być:

44 3. ZAAWANSOWANE KONTROLKI W tej części zilustruję użycie trzech elementów sterujących, które nie pasowały do poprzedniej książki: 1. TableView 2. TreeView 3. TreeTableView gdzie pierwszy odpowiada komponentowi JTable, drugi komponentowi JTree, podczas gdy ten drugi najlepiej scharakteryzować jako kombinację. Gdy te trzy elementy nie zostały uwzględnione w poprzedniej książce, jest to częściowo spowodowane tym, że są złożone z wieloma możliwościami, a częściowo dlatego, że ich sposób pracy można najlepiej opisać po wzmiankach w poprzednim rozdziale właściwości i wiązania. 3.1 WIDOK TABELI Chcę zacząć od komponentu TableView, który podobnie jak JTable, jest niezwykle złożonym układem sterowania, który układa dane w wierszach i kolumnach, a także jest najbardziej użytecznym z trzech elementów sterujących. Klasa komponentu nosi nazwę TableView, ale wraz ze składnikiem istnieje kilka klas pomocników: - TableColumn - TableRow

45 - TableCell - TablePosition - TableView.TableViewFocusModel - TableView.TableViewSelectionModel a imiona powinny powiedzieć trochę o celu poszczególnych klas. Klasa TableView jest używana trochę jak JTable, w której należy zdefiniować model danych, czyli dane, które powinien wyświetlić komponent i jakie kolumny powinny być. Zacznę od przykładu, który nazwałem ShowKingsProgram, który pokazuje przegląd królów duńskich reprezentowanych jako obiekty typu King : public class King private static final String DK = "DK"; private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name"); private IntegerProperty from = new SimpleIntegerProperty(this, "from", 0); private IntegerProperty to = new SimpleIntegerProperty(this, "to", 9999); private StringProperty country;perty to = new SimpleIntegerProperty (this, to, 9999); prywatny StringProperty country; Jeśli uruchomisz program, otworzy się okno poniżej, które pokazuje tabelę z 5 kolumnami, a zatem jedna kolumna większa niż typ Króla ma właściwości dla: Jeśli przestudiujesz kod programu (program główny), będzie on:

46 public class ShowKingsProgram extends Application private KingTableModel model = new public void start(stage stage) TableView<King> table = new TableView(model.getKings()); table.getcolumns().addall(model.getnamecol(), model.getperiodcol(), model.getcountrycol(), model.getyearscol()); BorderPane root = new BorderPane(table); root.setpadding(new Insets(10, 10, 10, 10)); Scene scene = new Scene(root, 500, 400); stage.setscene(scene); stage.settitle("show kings"); stage.show(); public static void main(string[] args) launch(args); co jest bardzo proste. Początkowo definiowany jest model (wyjaśniony poniżej). W przeciwnym razie nic się nie dzieje oprócz metody start (), w której dla obiektów King tworzony jest TableView i inicjalizowany za pomocą modelu danych, a na koniec dodawane są cztery kolumny przy użyciu metod w modelu danych. Odpowiedni widok tabeli jest wstawiany do wykresu programu w postaci enkapsulowanej w BorderPane. To klasa modeli KingTableModel zawiera najwięcej: public class KingTableModel private final ObservableList<King> kings = FXCollections.observableArrayList();

47 public KingTableModel() for (String[] arr : data) kings.add(new King(arr[0], Integer.parseInt(arr[1]), Integer.parseInt(arr[2]))); public ObservableList<King> getkings() return kings; public TableColumn<King, String> getnamecol() TableColumn<King, String> col = new TableColumn("Name"); col.setcellvaluefactory(new PropertyValueFactory("name")); return col; public TableColumn<King, Integer> getfromcol() TableColumn<King, Integer> col = new TableColumn("From"); col.setcellvaluefactory(new PropertyValueFactory("from")); return col; public TableColumn<King, Integer> gettocol() TableColumn<King, Integer> col = new TableColumn("To"); col.setcellvaluefactory(new PropertyValueFactory("to")); r eturn col; public TableColumn<King, String> getcountrycol()

48 TableColumn<King, String> col = new TableColumn("Country"); col.setcellvaluefactory(new PropertyValueFactory("country")); return col; public TableColumn<King, String> getperiodcol() TableColumn<King, String> col = new TableColumn<>("Period"); col.getcolumns().addall(getfromcol(), gettocol()); return col; public TableColumn<King, String> getyearscol() TableColumn<King, String> col = new TableColumn<>("Years"); col.setcellvaluefactory(c -> King king = c.getvalue(); int a = king.getfrom(); int b = king.getto(); if (a == 0 b == 9999) return new ReadOnlyStringWrapper("Unknown"); return new ReadOnlyStringWrapper("" + (b a)); ); return col; private static final String[][] data = "Gorm den Gamle", "0", "958", "Harald Blåtand", "958", "987", ;

49 Klasa zaczyna się od utworzenia ObservableList dla obiektów King, a lista jest inicjowana w konstruktorze przy użyciu danych zdefiniowanych w tablicy na końcu klasy. Zauważ, że listę jako alternatywę można zainicjować, czytając tabelę bazy danych. Klasa ma metodę, która może zwrócić listę, która została użyta w metodzie start () w konstruktorze komponentu TableView. Reszta klasy składa się z metod tworzących poszczególne kolumny. Pierwszy dotyczy właściwości name, a typ kolumny to TableColumn, z parametrami wskazującymi, które obiekty są (tutaj King) i typ danej właściwości (tutaj String). Parametrem konstruktora w TableColumn jest tekst pokazany w nagłówku kolumn. Następnie musisz określić, które wartości powinna zawierać każda komórka, i dzieje się tak w przypadku PropertyValueFactory, gdzie parametrem konstruktora jest nazwa właściwości klasy King, którą komórka musi zawierać, i tutaj jest nazwa. Dwie następne kolumny są zasadniczo zdefiniowane w ten sam sposób, tylko typ jest tym razem liczbą całkowitą. Jednak te kolumny nie są wstawiane bezpośrednio do tabeli, ale za pośrednictwem innej kolumny utworzonej przez metodę getperiodcol (). Kolumny można zagnieżdżać, a kolumna utworzona przez getperiodcol () to kolumna składająca się z dwóch innych kolumn. W interfejsie użytkownika odpowiada to, że kolumna z nagłówkiem Okres ma dwie podkolumny, odpowiednio z roku na rok. W tym przypadku nie ma żadnego konkretnego powodu oprócz tego, że jest to możliwe i jaka jest składnia. Klasa tworzy również kolumnę dla kraju własności, a tutaj nie ma nic nowego, a tak naprawdę metoda getcountrycol () nie jest używana. Celem jest pokazanie, że nie musisz wyświetlać wszystkich kolumn w interfejsie użytkownika. Wreszcie istnieje metoda getyearcol (), która pokazuje, ile lat rządził król, lub tekst Nieznany, jeśli nie znasz okresu rządowego. Ta kolumna jest inna, ponieważ nie ma odpowiedniej właściwości w klasie King, dlatego wartości, które mają być wyświetlane w komórkach, muszą zostać obliczone. Zdarza się to ponownie przy użyciu metody setcellvaluefactory (). Ma następujący prototyp: setcellvaluefactory(callback<tablecolumn.celldatafeatures<s,t>, ObservableValue<T> value) Callback<P, R> to interfejs sparametryzowany dwoma typami, a interfejs definiuje jedną metodę R call(p param) W tym przypadku dla kolumny Rok oznacza to, że komórki kolumny są inicjowane metodą formularza ObservableValue<String> call(tablecolumn.celldatafeatures<king, String> value) który służy do obliczania lat, w których król rządził. W ten sposób możesz dodać własne, zdefiniowane kolumny. Należy zauważyć, że wartość jest zwracana jako właściwość tylko do odczytu, ponieważ nie można edytować takiej wartości w interfejsie użytkownika. Jeśli uruchamiasz program, pamiętaj, że domyślnie możesz zmienić szerokość kolumn za pomocą myszy, zmienić kolejność kolumn i posortować je, klikając nagłówek. Następny przykład nazywa się MapKingProgram i jest zasadniczo tym samym programem i otwiera następujące okno:

50 i to znaczy, program pokazuje TableView z 3 kolumnami odpowiadającymi trzem właściwościom w klasie King. Różnica polega jednak na tym, że obiekty (wiersze), które pokazuje komponent, nie są obiektami King, lecz obiektami Map <String, Object>. W tym przykładzie nie ma szczególnego powodu, oprócz tego, że pokazuje składnię, ale w sytuacjach, w których wiersze w tabeli nie pasują do obiektu domeny, można użyć tej opcji. Klasa King jest taka sama jak w poprzednim przykładzie, ale klasa KingTableModel została zmieniona: public class KingTableModel private final ObservableList<Map<String, Object>> kings = FXCollections.observableArrayList(); public KingTableModel() int id = 0; for (String[] arr : data) King king = new King(arr[0], Integer.parseInt(arr[1]), Integer.parseInt(arr[2])); Map map = new HashMap<String, Object>(); map.put("id", String.format("%s%02d", king.getcountry(), ++id)); map.put("name", king.getname()); map.put("from", king.getfrom()); map.put("to", king.getto()); kings.add(map);

51 public ObservableList<Map<String, Object>> getkings() return kings; public TableColumn<Map, String> getidcol() TableColumn<Map, String> col = new TableColumn("Id"); col.setcellvaluefactory(new MapValueFactory("id")); return col; public TableColumn<Map, String> getnamecol() TableColumn<Map, String> col = new TableColumn("Name"); col.setcellvaluefactory(new MapValueFactory("name")); return col; public TableColumn<Map, Integer> getfromcol() TableColumn<Map, Integer> col = new TableColumn("From"); col.setcellvaluefactory(new MapValueFactory("from")); return col; public TableColumn<Map, Integer> gettocol() TableColumn<Map, Integer> col = new TableColumn("To"); col.setcellvaluefactory(new MapValueFactory("to")); return col;

52 private static final String[][] data = "Gorm den Gamle", "0", "958", "Harald Blåtand", "958", "987", ; Po pierwsze, osoby kolekcji są tym razem ObservableList z obiektami, którymi są Map <String, Object>. Lista jest tworzona w konstruktorze, z tą różnicą, że tym razem obiekty Map muszą zostać utworzone w postaci, w której kluczem jest Łańcuch, a wartością jest Obiekt (Łańcuch lub Liczba całkowita). Zwróć uwagę na klucze utworzone za pomocą ciągłego numeru, ale poprzedzające kod kraju. Są też metody, które tworzą kolumny. Obiekty kolumn są tworzone jak w pierwszym przykładzie, ale parametr setcellvaluefactory () jest obiektem MapValueFactory, w którym kluczem jest parametr konstruktora. W rezultacie kolumny są inicjowane wartościami w obiekcie Map, do którego odnosi się klucz. W metodzie start () (klasa MapKingsProgram) nie ma większych zmian i nie chcę tutaj pokazywać kodu, ale po utworzeniu tabeli i zainicjowaniu jej kolumnami dodano dwie instrukcje: table.settablemenubuttonvisible (true); idcol.setvisible (false); gdzie idcol to nazwa pierwszej kolumny z id wierszy (a tym samym kluczem). Pierwsza instrukcja wskazuje, że powinno być możliwe ukrycie kolumn, a druga instrukcja oznacza, że idcol powinien być ukryty przed uruchomieniem. Po uruchomieniu programu można zauważyć, że w wierszu nagłówka nad paskiem przewijania znajduje się niewielki plus. Po kliknięciu pojawi się wyskakujące okienko, w którym możesz kliknąć lub, czy poszczególne kolumny powinny być ukryte. Następny przykład nazywa się RenderKingsProgram i otwiera następujące okno:

53 i pokaże Ci, jak zdefiniować sposób wyświetlania zawartości poszczególnych komórek. W takim przypadku zawartość kolumny Od jest pusta, jeśli wartość wynosi 0, co oznacza, że nie znasz początku okresu rządów króla. To samo dotyczy kolumny Do, jeśli wartość wynosi Ponadto dwie kolumny są wyrównane do prawej. Wreszcie ostatnia kolumna pokazuje wartość kraju własności jako pole wyboru, które jest zaznaczone, jeśli wartością jest DK. Obiekty, które są ponownie wyświetlane, mają typ King, a najważniejsze zmiany są ponownie w klasie KingTableModel public class KingTableModel private final ObservableList<King> kings = FXCollections.observableArrayList(); public TableColumn<King, Integer> getfromcol() TableColumn<King, Integer> col = new TableColumn("From"); col.setcellvaluefactory(new PropertyValueFactory("from")); col.setcellfactory(c -> TableCell<King, Integer> cell = new TableCell<King, public void updateitem(integer item, boolean empty) super.updateitem(item, empty); this.settext(null); this.setgraphic(null); if (!empty && item!= 0) this.settext("" + item); ;

54 return cell; ); col.setprefwidth(60); col.setstyle("-fx-alignment: CENTER_RIGHT;"); col.getstyleclass().add("salary-header"); return col; public TableColumn<King, Boolean> getcountrycol() TableColumn<King, Boolean> col = new TableColumn<>("Danish"); col.setcellvaluefactory(cell -> King king = cell.getvalue(); return new ReadOnlyBooleanWrapper(king.getCountry().equals("DK")); ); col.setcellfactory(checkboxtablecell.<king>fortablecolumn(col)); return col; private static final String[][] data = "Gorm den Gamle", "0", "958", ; Początek klasy jest zasadniczo taki sam jak w pierwszym przykładzie. Oznacza to, że konstruktor, metoda getking () i metoda getnamcol () są niezmienione i dlatego nie zostały pokazane powyżej. Jednak metoda getfromcol () jest inna. Nadal musi zwrócić wartość TableColumn dla właściwości z, a ten obiekt kolumny jest tworzony jak poprzednio, ale następnie metoda setcellfactory () służy do określenia, jak powinna być wyświetlana wartość. Dzieje się tak w przypadku obiektu TableCell, który jest interfejsem definiującym metodę update (). Metoda ma jako parametr obiekt do renderowania i wartość logiczną, która informuje, czy komórka jest pusta. Jeśli zawartość jest niezerowa, służy do aktualizacji komórki. Po skojarzeniu z komórką TableCell kolumna ma preferowaną szerokość, a styl jest dołączany do kolumny i jej nagłówka, gdzie ostatni jest zdefiniowany w arkuszu stylów. Metoda gettocol () jest napisana w ten sam sposób i nie pojawia się tutaj. Następnie istnieje metoda getcountrycol (), która tworzy TableColumn dla obiektów boolowskich. CellValueFactory przypisuje się w celu określenia, jaką wartość powinien uzyskać obiekt King, w zależności od wartości właściwości

55 country. Czy to DK, wartość musi być prawdziwa, a poza tym fałszywa. Należy zauważyć, że opakowania wartości we właściwości tylko do odczytu, a przyczyną jest to, że składnik TableView powinien móc powiązać się z wartością. Wreszcie, CellFactory jest powiązany z kolumną informującą o sposobie wyświetlania wartości i że powinien być podobny do CheckBoxTableCell, który jest opakowaniem dla pola wyboru. Zauważ, że zaznaczasz również, że pole wyboru musi być zainicjowane wartością w bieżącej kolumnie. Następnie jest program główny z metodą start (): public void start(stage stage) TableView<King> table = new TableView(model.getKings()); table.getselectionmodel().setselectionmode(selectionmode.multiple); table.getselectionmodel().setcellselectionenabled(false); table.getselectionmodel().getselectedindices().addlistener( (ListChangeListener.Change<? extends Integer> change) -> String text = ""; List<Integer> list = table.getselectionmodel().getselectedindices(); for (Integer n : list) text += n + " "; System.out.println(text); ); table.getcolumns().addall(model.getnamecol(), model.getfromcol(), model.gettocol(), model.getcountrycol()); BorderPane root = new BorderPane(table); root.setpadding(new Insets(10, 10, 10, 10)); Scene scene = new Scene(root, 450, 300); scene.getstylesheets().add("resources/css/styles.css"); stage.setscene(scene); stage.settitle("show kings"); stage.show(); Wygląda jak powyższe przykłady, ale powinien pokazać trochę, jak wybierać wiersze. Po pierwsze, powinieneś być w stanie wybrać MULTIPLE i nie powinieneś być w stanie wybrać pojedynczych komórek. Następnie program obsługi zdarzeń, który uruchamia się za każdym razem, gdy zmieniasz wybór. Program obsługi jest trywialny i nie robi nic poza napisaniem na konsoli tekstu, który pokazuje indeksy wybranych wierszy. ĆWICZENIE 3 Bazy danych padata zawiera tabelowy kod pocztowy z duńskimi kodami pocztowymi. Musisz napisać program, który możesz wywołać PostProgram. Program powinien wyświetlać tylko zawartość tabeli

56 bazy danych w TableView i nie powinieneś nic robić w sprawie formatowania poszczególnych kolumn (są tylko dwie). 3.2 EDYCJA KOMÓREK W PRZEGLĄDIE TABELI Pokażę teraz przykład, który w zasadzie wygląda jak powyższe przykłady, ale w którym można edytować zawartość poszczególnych komórek. Chociaż może nie być zaskakująco nowy, wciąż jest stosunkowo prosty. Ogólnie rzecz biorąc, działa to w ten sposób, że dwukrotnie klikniesz komórkę, którą chcesz edytować, po czym komórka otwiera się z opcją zmiany wartości. Gdy komórka jest otwarta, dzieje się tak, gdy komórka pokazuje inną kontrolę, która w zasadzie może być 1. CheckBoxTableCell (patrz poprzedni przykład) 2. ChoiceBoxTableCell 3. ComboBoxTableCell 4. TextFieldTableCell i możesz także zdefiniować własne elementy sterujące. Aby pokazać, jak to działa, użyję tym razem innego typu obiektu, którym jest klasa reprezentująca osobę: public class Person implements Comparable<Person> private static int ID = 0; private final ReadOnlyIntegerWrapper id = new ReadOnlyIntegerWrapper(); private final StringProperty name = new SimpleStringProperty(); private final StringProperty job = new SimpleStringProperty(); private final StringProperty gender = new SimpleStringProperty(); private final IntegerProperty year = new SimpleIntegerProperty(); private final DoubleProperty salary = new SimpleDoubleProperty(); private final BooleanProperty weekly = new SimpleBooleanProperty(); private final ObjectProperty<LocalDate> date = new SimpleObjectProperty(); public Person(String name, String job, String gender, Integer year, Double salary, Boolean weekly, LocalDate date) id.set(++id); setname(name); Jest to standardowa klasa modelu JavaFX, więc nie pokazuję tutaj szczegółów. Klasa jest rozszerzeniem klasy Osoba, której użyłem w poprzednim rozdziale, ale tym razem do obiektów przypisano bieżący identyfikator reprezentowany jako właściwość tylko do odczytu. W rzeczywistości interpretacja

57 czterech ostatnich nieruchomości nie jest ważna, ale może to być rok urodzenia (na rok), wynagrodzenie (na wynagrodzenie) i to, czy wynagrodzenie jest tygodniowe, czy miesięczne (tygodniowe). Wreszcie ostatnia właściwość może być interpretowana jako data zatrudnienia. Ważna nie jest interpretacja, ale to, że parametry mają różne typy. Jeśli uruchomisz program, wynik może wyglądać tak, jak pokazano poniżej, gdzie można edytować wszystkie wartości oprócz kolumny Id. Przycisk Dodaj służy do dodawania nowego wiersza do tabeli, tworząc w ten sposób Osobę, natomiast przycisk Usuń służy do usuwania wybranego wiersza. Oprócz klasy Person projekt ma klasę o nazwie PersonModel: package editpersonsprogram; import java.time.localdate; import javafx.collections.fxcollections; import javafx.collections.observablelist; public class PersonsModel private final ObservableList<Person> persons = FXCollections.observableArrayList(); public PersonsModel() initialize(); public ObservableList<Person> getpersons() return persons;

58 public void add() persons.add(new Person("", "", "", null, null, true, null)); public void remove(int n) persons.remove(n); private void initialize() który reprezentuje osoby, nad którymi program pracuje. Metoda initialize () tworzy 6 osób, więc tabela nie jest pusta podczas uruchamiania programu. Zauważ, że obiekty są przechowywane w obiektach ObservableList to Person i zwróć uwagę, w jaki sposób metody add () i remove () utrzymują tę listę. W przeciwnym razie chcę zacząć od klasy EditPersonsProgram: public class EditPersonsProgram extends Application private PersonsModel model = new PersonsModel(); rivate TableView<Person> table = public void start(stage stage) table = new TableView(model.getPersons()); PersonTableModel cols = new PersonTableModel(); table.getcolumns().addall(cols.getidcol(), cols.getnamecol(), cols.getjobcol(), cols.getgendercol(), cols.getyearcol(), cols.getsalarycol(), cols.getweeklycol(), cols.getdatecol());

59 table.seteditable(true); BorderPane root = new BorderPane(table, null, null, createbottom(), null); root.setpadding(new Insets(10, 10, 10, 10)); Scene scene = new Scene(root); scene.getstylesheets().add("resources/css/styles.css"); stage.setscene(scene); stage.settitle("show kings"); stage.show(); private Pane createbottom() HBox pane = new HBox(20, createbutton("test", this::test), createbutton("remove", this::remove), createbutton("add", e -> model.add())); pane.setalignment(pos.center_right); pane.setpadding(new Insets(10, 0, 0, 0)); return pane; private Button createbutton(string text, EventHandler<ActionEvent> handler) Button cmd = new Button(text); cmd.setonaction(handler); return cmd; private void remove(actionevent e) int row = table.getselectionmodel().getselectedindex(); if (row >= 0) model.remove(row); table.getselectionmodel().clearselection();

60 private void test(actionevent e) for (Person p : model.getpersons()) System.out.println(p); System.out.println(); public static void main(string[] args) launch(args); Klasa rozpoczyna się od zdefiniowania obiektu modelu - nie dla składnika TableView, ale dla danych, które program musi utrzymywać. Ponadto zdefiniowane są obiekty TableView dla osób. W metodzie start (), w której tworzona jest tabela, nie ma wiele nowego do wyjaśnienia, ale należy zauważyć, że tym razem kolumny tabeli są tworzone metodami w klasie PersonTableModel. Na koniec zauważ, że tabela jest zdefiniowana jako edytowalna: table.seteditable (true); i to wszystko, czego potrzebujesz, aby edytować komórki, jeśli kolumna odnosi się do właściwości do odczytu / zapisu i jeśli kolumna ma funkcję CellFactory, która jest formantem TableCell. Należy również zwrócić uwagę na procedury obsługi zdarzeń, w których procedura obsługi przycisku Dodaj jest trywialna, podczas gdy procedura obsługi przycisku Usuń wymaga określenia indeksu dla wybranego wiersza. Na koniec znajduje się moduł obsługi przycisku Test, który w konsoli drukuje obiekty na liście. Celem tej procedury obsługi jest sprawdzenie, czy istnieje zgodność z tym, co pokazuje kontrolka TableView i co zawiera model. Ma to pokazać, że zmiany w kontrolce TableView automatycznie aktualizują model. Następnie jest klasa PersonTableModel, która ma metody tworzące kolumny tabeli. Z drugiej strony jest to także najbardziej złożona z klas programu. public class PersonTableModel public TableColumn<Person, Integer> getidcol() TableColumn<Person, Integer> idcol = new TableColumn("Id"); idcol.setcellvaluefactory(new PropertyValueFactory("id")); return idcol;

61 public TableColumn<Person, String> getnamecol() TableColumn<Person, String> col = new TableColumn("Name"); col.setcellvaluefactory(new PropertyValueFactory("name")); col.setcellfactory(textfieldtablecell.<person>fortablecolumn()); return col; public TableColumn<Person, String> getjobcol() public TableColumn<Person, String> getgendercol() TableColumn<Person, String> col = new TableColumn("Gender"); col.setcellvaluefactory(new PropertyValueFactory("gender")); col.setcellfactory(choiceboxtablecell.<person, String>forTableColumn("Male", "Female")); return col; public TableColumn<Person, Integer> getyearcol() TableColumn<Person, Integer> col = new TableColumn("Year"); col.setcellvaluefactory(new PropertyValueFactory("year")); col.setcellfactory(comboboxtablecell.<person, Integer>forTableColumn(new YearConverter(), getyears())); return col;

62 public TableColumn<Person, Double> getsalarycol() TableColumn<Person, Double> col = new TableColumn("Salary"); col.setcellvaluefactory(new PropertyValueFactory("salary")); col.setcellfactory(textfieldtablecell.<person, Double>forTableColumn(new SalaryConverter())); col.setoneditcommit((tablecolumn.celleditevent<person, Double> e) -> int row = e.gettableposition().getrow(); Person person = e.gettableview().getitems().get(row); if (Math.abs(e.getNewValue()) < 0.1) e.gettableview().getitems().set(row, person); else person.setsalary(e.getnewvalue()); ); col.setprefwidth(100); col.setstyle("-fx-alignment: CENTER_RIGHT;"); col.getstyleclass().add("salary-header"); return col; public TableColumn<Person, Boolean> getweeklycol() TableColumn<Person, Boolean> col = new TableColumn("Weekly"); col.setcellvaluefactory(new PropertyValueFactory("weekly")); col.setcellfactory(checkboxtablecell.<person>fortablecolumn(col)); eturn col; public TableColumn<Person, LocalDate> getdatecol() TableColumn<Person, LocalDate> col = new TableColumn("Date"); col.setcellvaluefactory(new PropertyValueFactory("date")); col.setcellfactory(datepickertablecell.<person>fortablecolumn());

63 return col; private ObservableList<Integer> getyears() int year = LocalDate.now().getYear() 10; ObservableList<Integer> list = FXCollections.observableArrayList(); for (int y = year 90; y < year; ++y) list.add(y); return list; lass YearConverter extends public Integer fromstring(string string) try return Integer.parseInt(string); catch (Exception ex) return public String tostring(integer value) return value == 0? "" : "" + value;

64 lass SalaryConverter extends public Double fromstring(string string) try return Double.parseDouble(string); catch (Exception ex) return public String tostring(double value) return Math.abs(value) < 0.1? "" : String.format("%1.2f", value.doublevalue()); Pierwsza metoda zwraca TableColumn dla identyfikatora kolumny i jest najprostsza, a jedyne, na co należy zwrócić uwagę, to że nie ma CellFactory, ponieważ kolumny nie można edytować. Dwie następne metody są w zasadzie identyczne, ponieważ w obu przypadkach jest to kolumna, której wartościami są tekst. Dlatego mają funkcję CellFactory, która jest TextFieldTableCell, a wynikiem jest dwukrotne kliknięcie komórki w tych kolumnach, otwiera formant TextField, w którym można edytować zawartość. Należy zauważyć, że to metoda fortablecolumn () otwiera pole wprowadzania. Następnie istnieje metoda getgendercol (), która zwraca TableColumn dla kolumny Gender. Ma CellFactory typu ChoiceBoxTableCell, a wynikiem jest dwukrotne kliknięcie komórki w ChoiseBox z dwiema wartościami: męską i żeńską, w których użytkownik może wybrać wartość. W tym miejscu należy zwrócić uwagę na metodę fortablecolumn (), która jako parametry ma wartości, dla których należy zainicjować formant ChoiceBox. Następna metoda dotyczy kolumny roku i wykorzystuje ComboBoxTableCell, w wyniku czego komórka otwiera ComboBox. Klasa ma prywatną metodę, która tworzy ObservableList z wybranymi latami. Jest używany jako parametr fortablecolumn (), ale istnieje

65 również parametr YearConverter (zdefiniowany na końcu pliku). Generalnie istnieje nadpisanie fortablecolumn (), w którym można określić konwerter jako parametr, a w tym przypadku ma to jedynie zapewnić, że brakujący rok nie będzie wyświetlany jako 0. Następnie jest metoda getsalarycol (), która zwraca TableColumn dla Double, i gdzie powinna być możliwa edycja Double. Oto kilka rzeczy, na które należy zwrócić uwagę. Po pierwsze, jako CellFactory, TextFieldTableCell służy do wprowadzania losowej liczby dziesiętnej. Aby wynik wyglądał ładnie, dołączony jest konwerter typu SalaryConveter, który pokazuje Double z dwoma miejscami dziesiętnymi - ale tylko jeśli liczba nie jest równa 0. Jeśli tak się stanie, wynik będzie wyświetlany jako pusty. Gdy użytkownik wprowadza liczbę, może oczywiście wprowadzić coś nielegalnego, a jeśli tak jest (wprowadzonego numeru nie można przekonwertować na liczbę), pojawia się wyjątek, w wyniku którego wartość jest ustawiona na 0. Jednak wartość Problem polega na tym, że model jest następnie aktualizowany o wartość 0, co nie jest tak naprawdę myślą, ale model powinien zachować starą wartość. Problem został rozwiązany przez dodanie modułu obsługi zdarzeń, który jest wykonywany po zakończeniu wprowadzania (a składnik TextField zamyka się). Jeśli wartość wynosi 0, ustawiona jest stara wartość, a w przeciwnym razie nowa wartość. Ponowne wprowadzenie starej wartości wygląda nieco dziwnie i w rzeczywistości model nadal ma starą wartość, ale interfejs użytkownika nie został zaktualizowany. Problem został rozwiązany przez przypisanie do siebie obiektu Person, co oznacza, że model uruchamia zdarzenie, które aktualizuje interfejs użytkownika. Na koniec zawartość kolumny jest wyrównana do prawej, ale jest taka sama jak w poprzednim przykładzie.. Następnie jest metoda getweeklycol (), która używa CheckBoxTableCell. Nie jest to nic nowego, ale zwróć uwagę na parametr fortablecolumn (), który jest kolumną do edycji. Na koniec jest getdatecol (), a celem tej metody jest pokazanie kolumny, która używa niestandardowego CellFactory. Jest to DatePickerTableCell, ponieważ pożądane jest, aby użytkownik otrzymał DatePicker, klikając dwukrotnie komórkę. Niestandardowy CellFactory to klasa, która implementuje interfejs TableCell i poza tym ma kontrolę pożądanego rodzaju (w tym przypadku DatePicker). Klasa musi mieć możliwość przesłonięcia następujących metod - startedit () - commitedit () - canceledit () - updateitem () ale poza tym klasa składa się z konstruktorów i statycznych metod fortablecolumn (). Poniżej pokazano tylko nadrzędne metody klasy: public class DatePickerTableCell<S, T> extends TableCell<S, LocalDate> private DatePicker datepicker; private StringConverter converter = null; private boolean editable = true;

66 @Override public void startedit() if (!iseditable()!gettableview().iseditable()!gettablecolumn().iseditable()) return; super.startedit(); if (datepicker == null) this.createdatepicker(); public void canceledit() super.canceledit(); settext(converter.tostring(getitem())); public void updateitem(localdate item, boolean empty) super.updateitem(item, empty); if (empty) settext(null); setgraphic(null); else if (this.isediting()) if (datepicker!= null) datepicker.setvalue((localdate)item);

67 settext(null); setgraphic(datepicker); else settext(converter.tostring(item)); setgraphic(null); Podczas uruchamiania programu, w tym edycji osób, a także tworzenia nowych i usuwania istniejących, należy pamiętać, że model jest automatycznie aktualizowany, a okno jest automatycznie aktualizowane (przyciski Dodaj i Usuń). Wszystko to dzieje się z powodu powiązań dwukierunkowych. Aby sprawdzić, czy okno ma przycisk Test. PROBLEM 2 Musisz rozwiązać to samo zadanie, co w przypadku problemu 1, ale interfejs użytkownika powinien wyglądać tak, jak pokazano w poniższym oknie, w którym poszczególne osoby pojawiają się jako wiersz w TableView. Kliknięcie przycisku Dodaj doda nową osobę (gdzie wszystkie pola są puste) do listy, a jeśli klikniesz przycisk Usuń, wybrana osoba musi zostać usunięta. Wszystkie komórki muszą być edytowalne, aw kolumnach Imię, Tytuł, Narodziny i Śmierć należy użyć TextFieldTableCell, natomiast w kolumnie Country należy użyć ComboBoxTableCell. Baza danych zawiera kraj tabeli, a pole combobox musi zostać zainicjowane wszystkimi kodami kraju z tej tabeli. Z tyłu jest kolumna Tekst, w której komórki muszą być edytowane za pomocą kontrolki TextArea, a tym samym niestandardowego elementu TableCell. Pamiętaj, że może to oznaczać, że musisz dodać taki czy inny sposób, aby zakończyć wprowadzanie tekstu, na przykład wpisując F12. Szczególnym problemem jest fizyczny zapis do bazy danych. Możesz oczywiście pisać za każdym razem, gdy tworzysz osobę i ją usuwać, ale pisanie przy każdej edycji komórki może być nieodpowiednie. Możesz zatem wybrać inną strategię, w której wszystkie zmiany zapisujesz po kliknięciu przycisku. Taki jest cel przycisku Zapisz. W moim rozwiązaniu po prostu aktualizuję wszystkie wiersze (za pomocą aktualizacji wsadowej) po kliknięciu przycisku

68 Zapisz. To prawdopodobnie łatwe rozwiązanie, ponieważ oznacza, że zaktualizuję wszystkie wiersze, niezależnie od tego, czy zostaną zmienione. Być może możesz znaleźć lepsze rozwiązanie? 3.3 FILTRY Jedną z ważnych cech JTable jest użycie filtrów, a dzięki TableView wszystko jest trochę łatwiejsze. TableView pokazuje zawartość ObersvableList i możesz ustawić filtr z opakowaniem filtra na listę i umieszczony w opakowaniu sortowania. Aby zakończyć tę recenzję składnika TableView, pokażę przykład, który ustawia filtr. Przykład nazywa się FilterKingsProgram i chcę ponownie użyć obiektów typu King, a model danych dla składnika TableView jest zasadniczo niezmieniony w stosunku do poprzedniego i nie zostanie tutaj pokazany. Kod okna to: public class FilterKingsProgram extends Application private TextField txtname = new public void start(stage stage) TableView<King> table = new TableView(); table.getcolumns().addall(kingtablemodel.getnamecol(), KingTableModel.getFromCol(), KingTableModel.getToCol()); initialize(table); BorderPane root = new BorderPane(table, null, null, createbottom(), null); root.setpadding(new Insets(10, 10, 10, 10));

69 Scene scene = new Scene(root, 500, 400); stage.setscene(scene); stage.settitle("show kings"); stage.show(); private void initialize(tableview table) FilteredList<King> filter = new FilteredList<>(KingTableModel.getKings(), king -> true); txtname.textproperty().addlistener((observable, oldvalue, newvalue) -> filter.setpredicate(king -> if (newvalue == null newvalue.isempty()) return true; String lowercasefilter = newvalue.tolowercase(); if (king.getname().tolowercase().contains(lowercasefilter)) return true; return false; ); ); SortedList<King> sorter = new SortedList<>(filter); sorter.comparatorproperty().bind(table.comparatorproperty()); table.setitems(sorter); private Pane createbottom() HBox pane = new HBox(10, new Label("Filter for name"), txtname); pane.setpadding(new Insets(10, 0, 0, 0)); return pane; public static void main(string[] args) launch(args);

70 W metodzie start () nie ma nic nowego, a nowa metoda jest wykonywana w metodzie initialize (), która inicjuje formant tabeli. Zauważ najpierw, że dla danych wyświetlanych w tabeli tworzony jest filtr, a typem filtru jest FilteredList. Następnie nasłuchiwanie musi być powiązane z polem wejściowym, które jest zdefiniowane jako predykat dla filtra, i jak w tym przypadku zwraca wartość true, jeśli właściwość nazwy obiektu King zawiera wartość pola wejściowego - bez rozróżniania wielkich i małych liter. Na koniec filtr jest wstawiany do opakowania jako SortedList, który ma komparator, który wiąże się z komparatorem tabeli. Wreszcie obiekt sortujący jest używany jako model danych dla tabeli. Jeśli uruchomisz program, pojawi się następujące okno, w którym u dołu znajduje się pole wprowadzania służące do wprowadzenia filtru w kolumnie Nazwa ĆWICZENIE 4 Utwórz kopię projektu PostProgram z ćwiczenia 3. Musisz rozwinąć program, aby zawierał on zarówno kod pocztowy, jak i nazwę miasta: 3.4 KONTROLKA TREEVIEW JavaFX ma również kontrolkę TreeView, która odpowiada JTree firmy Swing i jest formantem, który wizualizuje dane hierarchiczne. Zasadniczo istnieją dwie klasy TreeItem i TreeView, pierwsza reprezentuje element w drzewie, a druga jest rzeczywistym składnikiem. TreeItem jest kompozytem

71 lub liściem i zależy od tego, czy ma elementy potomne. Program ShowWorldProgram pokazuje niektóre kraje świata zorganizowane w hierarchii: Powyżej wszystkie kontynenty na tym świecie, które wszystkie Antarktyda są węzłami złożonymi, jak pokazano strzałką, pokazując, że jest to węzeł z węzłami potomnymi i że można go rozwinąć, klikając strzałkę myszą. Poniżej znajduje się to samo okno, w którym rozwinięto Azję i Amerykę Północną (należy pamiętać, że w razie potrzeby komponent automatycznie wyświetla pasek przewijania): TreeView pokazuje dane zdefiniowane w modelu, który jest hierarchią elementów TreeItem, aw tym przypadku model jest zdefiniowany w metodzie build (): package showworldprogram; import javafx.scene.control.*; public class TreeWorldModel private TreeItem<String> root = new TreeItem("This World"); public TreeWorldModel()

72 build(); public TreeItem<String> getdata() return root; private void build() TreeItem<String> af = new TreeItem("Africa"); af.getchildren().addall(new TreeItem("South Africa"), new TreeItem("Namibia"), new TreeItem("Botswana"), new TreeItem("Zimbabwe")); TreeItem<String> an = new TreeItem("Antarctica"); TreeItem<String> as = new TreeItem("Asia"); as.getchildren().addall(new TreeItem("China"), new TreeItem("India"), new TreeItem("Japan")); TreeItem<String> eu = new TreeItem("Europe"); eu.getchildren().addall(new TreeItem("Denmark"), new TreeItem("Norway"), new TreeItem("Sweden")); TreeItem<String> na = new TreeItem("North America"); na.getchildren().addall(new TreeItem("Canada"), new TreeItem("Mecico"), new TreeItem("United States")); TreeItem<String> oc = new TreeItem("Oceania"); oc.getchildren().addall(new TreeItem("Australia"), new TreeItem("New Zealand")); TreeItem<String> sa = new TreeItem("South Amaerica"); sa.getchildren().addall(new TreeItem("Argentina"), new TreeItem("Brazil"), new TreeItem("Chile"), new TreeItem("Bolivia"), new TreeItem("Peru")); root.getchildren().addall(af, an, as, eu, na, oc, sa); Kod programu jest dość prosty:

73 package showworldprogram; import javafx.application.application; import javafx.scene.scene; import javafx.scene.layout.*; import javafx.scene.control.*; import javafx.stage.stage; import javafx.geometry.*; public class ShowWorldProgram extends public void start(stage primarystage) TreeView view = new TreeView((new TreeWorldModel()).getData()); view.setshowroot(false); BorderPane root = new BorderPane(view); root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root, 300, 300); primarystage.settitle("the World!"); primarystage.setscene(scene); primarystage.show(); public static void main(string[] args) launch(args); nie ma nic do wyjaśnienia. Należy jednak zauważyć, że za pomocą setshowroot () zdefiniowałem, że korzeń drzewa nie powinien być wyświetlany. Możesz spróbować ustawić komentarz przed linią i zobaczyć, co się stanie. Następny przykład nazywa się MaintainWorldProgram i jest odmianą powyższego programu i otwiera następujące okno:

74 Jak pokazują przyciski, jedną z różnic jest to, że powinieneś być w stanie edytować zawartość drzewa: 1. Kliknięcie przycisku Dodaj dodaje węzeł do wybranego elementu - jeśli nie jest to węzeł liścia 2. Kliknięcie przycisku Usuń usuwa wybrany węzeł, ale tylko wtedy, gdy jest to węzeł liścia 3. Dwukrotne kliknięcie węzła liścia pozwoli ci zmienić nazwę Wreszcie, istnieje przycisk Test, który pozwala tylko wydrukować liczbę węzłów liści w drzewie, i należy pamiętać, że jest to liczba węzłów liści w modelu komponentu TreeView, a przycisk służy do testowania, czy powyższe operacje nie tylko aktualizują kontroler TreeView, ale także model. Program powinien również pokazać coś o tym, jakie zdarzenia występują podczas korzystania z TreeView. Model dla komponentu TreeView jest taki sam jak w poprzednim przykładzie i nie został tutaj pokazany. Z drugiej strony klasa MaintainWorldProgram wypełnia część, więc pokażę tylko kod najważniejszych metod. Metoda start () jest zasadniczo niezmieniona, ale istnieją teraz zmienne instancji zarówno dla kontrolki TreeView, jak i modelu: public class MaintainWorldProgram extends Application private TreeWorldModel model = new TreeWorldModel(); private TreeView<String> view; W przeciwnym razie najważniejsze w metodzie start () jest to, że wywołuje metodę addhandlers (), która dodaje procedury obsługi zdarzeń do komponentu TreeView: private void addhandlers() model.getdata().addeventhandler(treeitem.<str ing>branchexpandedevent(), e -> ); model.getdata().addeventhandler(treeitem.<stri ng>branchcollapsedevent(), e -> );

75 model.getdata().addeventhandler(treeitem.<str ing>childrenmodificationevent(), ); model.getdata().addeventhandler(treeitem.<st ring>valuechangedevent(), e -> ); view.setonmouseclicked(new public void handle(mouseevent mouseevent) if(mouseevent.getclickcount() == 2) TreeItem<String> item = view.getselectionmodel().getselecteditem(); if (item!= null) modify(item); ); Istnieje w sumie 5 programów obsługi zdarzeń. Nazwy informują o wykonywaniu procedur obsługi zdarzeń. Pierwsze trzy (gdzie pokazany jest tylko kod dla pierwszego) nie robi nic poza drukowaniem tekstu na konsoli. Czwarty używa metody printmodel (), która drukuje zawartość poddrzewa na konsoli i faktycznie drukuje całe drzewo. Jego celem jest pokazanie, że model jest zaktualizowany i zachęcamy do sprawdzenia kodu. Ostatni moduł obsługi zdarzeń dotyczy myszy i testuje dwukrotne kliknięcie myszą. Jeśli tak, wykonywana jest metoda modyfikacji (): private void modify(treeitem<string> item) if (item.isleaf()) TextInputDialog dialog = new TextInputDialog(item.getValue()); dialog.settitle("modify country"); dialog.setheadertext("change the country's name"); Optional<String> result = dialog.showandwait(); if (result.ispresent()) String name = result.get().trim(); if (name.length() > 0) item.setvalue(name);

76 view.getselectionmodel().clearselection(); W przypadku węzła liścia otwiera się prosty TextInputDialog, w którym można zmienić wartość. W ten sposób aktualizuje bieżący TreeItem i należy pamiętać, że aktualizuje również interfejs użytkownika. Moduł obsługi zdarzeń dla przycisku Dodaj jest w zasadzie identyczny, natomiast moduł obsługi zdarzeń dla przycisku Usuń to: private void remove (ActionEvent e) TreeItem <String> item = view.getselectionmodel (). GetSelectedItem (); if (item! = null && item.isleaf () && item.getparent ()! = null) TreeItem <String> parent = item.getparent (); parent.getchildren (). remove (item); view.getselectionmodel (). clearselection (); W tym miejscu należy zwrócić uwagę na to, jak odwołać się do wybranego elementu i ponownie, że interfejs użytkownika jest automatycznie aktualizowany. Ostatni moduł obsługi zdarzeń dotyczy przycisku Test, który zlicza liczbę węzłów liści, a następnie otwiera alert z wynikiem. W powyższym przykładzie zawartość elementu TreeItem jest edytowana przez otwarcie prostego TextInputDialog, ale można również edytować zawartość śródliniowo, jak TableView. Jest używany w przykładzie EditWorldProgram. Interfejs użytkownika jest taki sam jak powyżej, ale w modelu jest niewielka zmiana: public class TreeWorldModel private TreeItem<String> root = new TreeItem("This World"); public TreeWorldModel() build(); public TreeItem<String> getdata()

77 return root; public void add(treeitem<string> parent) parent.getchildren().add(new TreeItem("")); public void remove(treeitem item) item.getparent().getchildren().remove(item); Metoda start() to: public void start(stage primarystage) view = new TreeView(model.getData()); view.setshowroot(false); view.getselectionmodel().setselectionmode(selectionmode.single); view.seteditable(true); view.setcellfactory(textfieldtreecell.fortreeview()); view.setoneditstart(this::start); view.setoneditcommit(this::commit); view.setoneditcancel(this::cancel); BorderPane root = new BorderPane(view, null, null, createcommands(), null); root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root, 350, 400); primarystage.settitle("this World"); primarystage.setscene(scene); primarystage.show();

78 Należy tutaj zauważyć, że składnik TreeView musi być zdefiniowany jako edytowalny, a ponadto należy dołączyć CellFactory, ponieważ tutaj jest TextFieldTreeCell. Oznacza to, że po dwukrotnym kliknięciu TreeItem otworzy się TextField, umożliwiając edycję zawartości. Tak jak widzieliście to dla TableView, istnieją również inne typy CellFactory, których można użyć dla innych typów danych. Powiązane są również trzy programy obsługi zdarzeń, które nie robią nic poza drukowaniem tekstu na konsoli, aw tym przykładzie są one używane do pokazania, że pierwszy jest wykonywany po otwarciu pola wprowadzania, a pozostałe dwa są wykonywane w zależności od tego, czy wyjdziesz z Enter, czy WYJŚCIE. Metoda createcommands () tworzy panel z trzema przyciskami, a powiązane z nimi procedury obsługi zdarzeń są proste i wykorzystują dwie nowe metody w modelu. ĆWICZENIE 5 Baza danych ma cztery tabele, które zawierają informacje o duńskich regionach, duńskich gminach i duńskich kodach pocztowych, podczas gdy czwarta tabela przedstawia wiele relacji między gminą a kodem pocztowym. Cztery tabele zostały utworzone za pomocą następującego skryptu: create table region ( regnr int not null primary key, name varchar(30) not null ); create table municipality ( munnr int not null primary key, name varchar(30) not null, regnr int not null, area decimal(10, 2), number int, year int, foreign key (regnr) references region(regnr) ); create table zipcode ( code char(4) not null primary key, city varchar(30) not null ); create table post ( code char(4) not null, munnr int not null, primary key (code, munnr), foreign key (code) references zipcode(code), foreign key (munnr) references municipality(munnr) ); Musisz napisać program, który możesz nazwać Danią, który wyświetla te dane w TreeView. Pamiętaj, że program powinien wyświetlać tylko nazwy, ale nie powinieneś być w stanie edytować zawartości. Po uruchomieniu programu powinno zostać wyświetlone następujące okno:

79 i poniżej tego samego okna za korzeniem, region Midtjylland i gmina Holstebro zostają rozszerzone: 3.5 PRZEGLĄD Z OBIEKTAMI KRAJOWYMI Jako ostatni przykład użycia TreeView pokażę program o nazwie UpdateWorldProgram. Program jest podobny do poprzedniego, ale częściowo poszczególne obiekty są typu niestandardowego, a nie tylko ciągi znaków, a częściowo więcej danych. Baza danych zawiera trzy tabele zawierające dane o walutach, kontynentach i krajach: gdzie dwie ostatnie kolumny w tabeli kraju to klucze obce. Program wyświetli przegląd wszystkich krajów, ale zorganizowany w TreeView, i gdzie powinna być możliwa edycja informacji o kraju. Po otwarciu programu pojawi się następujące okno, w którym rozwinięta jest Ameryka Północna:

80 Na przykład dwukrotne kliknięcie Stanów Zjednoczonych spowoduje wyświetlenie następującego okna, w którym można edytować dane dotyczące Stanów Zjednoczonych: Te dwa kombinacje zawierają odpowiednio wszystkie kontynenty i wszystkie waluty i służą do zmiany waluty lub kontynentu danego kraju. Zauważ, że powyższe okno ze składnikiem TreeView nie pokazuje wszystkich krajów świata, ponieważ tylko kraje w bazie danych, w których znajduje się klucz obcy do świata tabel. Należy również pamiętać, że program nie zapewnia możliwości tworzenia nowych krajów ani usuwania istniejących krajów, ale oczywiście łatwo byłoby rozszerzyć program o te funkcje. Pierwszym krokiem jest zapisanie klas modeli w trzech tabelach bazy danych. Dla waluty jest to następująca klasa: public class Currency private ReadOnlyStringWrapper code = new ReadOnlyStringWrapper(this, "code"); private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name"); gdzie nie podałem kolumny kursów walut. Ponieważ informacji nie można zmienić, obie właściwości są definiowane tylko do odczytu. Świat tabel używa następującej klasy modelu:

81 public class World private ReadOnlyStringWrapper code = new ReadOnlyStringWrapper(this, "code"); private StringProperty name = new SimpleStringProperty(); W programie zmiana nazwy kontynentu nie powinna być możliwa, ale kiedy nazwa właściwości jest zdefiniowana jako odczyt / zapis, dzieje się tak, ponieważ klasa Country jest zdefiniowana jako klasa pochodna: public class Country extends World private IntegerProperty area = new SimpleIntegerProperty(); private IntegerProperty inhabitants = new SimpleIntegerProperty(); private StringProperty world = new SimpleStringProperty(); private StringProperty currency = new SimpleStringProperty(); Powodem jest przede wszystkim to, że TreeView zasadniczo zawiera elementy tego samego typu, tak jak w tym przypadku World, a zatem drzewo może również natychmiast zawierać obiekty Country. Po wdrożeniu tych klas modeli mam następujący model danych: public class Model private ObservableList<World> worlds = FXCollections.observableArrayList(); private ObservableList<Currency> currencies = FXCollections.observableArrayList(); private ObservableList<Country> countries = FXCollections.observableArrayList(); public Model() load(); public ObservableList<World> getworlds() return worlds; public ObservableList<Currency> getcurrencies()

82 return currencies; public ObservableList<Country> getcountries() return countries; public World getworld(string code) for (World world : worlds) if (world.getcode().equals(code)) return world; return null; public Currency getcurrency(string code) for (Currency currency : currencies) if (currency.getcode().equals(code)) return currency; return null; private void load() public void save()

83 Konstruktor wywołuje metodę load (), która inicjuje trzy listy, odczytując zawartość bazy danych. Nie wyświetliłem kodu load (), ale zawiera on jedynie proste operacje na bazie danych. Powinno być również możliwe zapisanie zmian, które program zwraca do bazy danych, a zatem metoda save (). Podczas sprawdzania kodu pamiętaj, że zapisuje wszystkie kraje z powrotem wraz z aktualizacją wsadową. Może to być trochę przesadzone i można to łatwo rozwiązać, rozszerzając klasę Country o dodatkową właściwość logiczną, którą można ustawić na true, gdy wartość zostanie zmieniona. W przypadku modelu klasy należy również zwrócić uwagę na public ObservableList<World> getworlds() return worlds; public ObservableList<Currency> getcurrencies() return currencies; public ObservableList<Country> getcountries() return countries; public World getworld(string code) for (World world : worlds) if (world.getcode().equals(code)) return world; return null; public Currency getcurrency(string code) for (Currency currency : currencies) if (currency.getcode().equals(code)) return currency; return null;

84 private void load() public void save() Konstruktor wywołuje metodę load (), która inicjuje trzy listy, odczytując zawartość bazy danych. Nie wyświetliłem kodu load (), ale zawiera on jedynie proste operacje na bazie danych. Powinno być również możliwe zapisanie zmian, które program zwraca do bazy danych, a zatem metoda save (). Podczas sprawdzania kodu pamiętaj, że zapisuje wszystkie kraje z powrotem wraz z aktualizacją wsadową. Może to być trochę przesadzone i można to łatwo rozwiązać, rozszerzając klasę Country o dodatkową właściwość logiczną, którą można ustawić na true, gdy wartość zostanie zmieniona. W przypadku modelu klasy należy również zwrócić uwagę na metody getworld () i getcurrency (), które odpowiednio z kodu kontynentu i kodu waluty zwracają odpowiedni obiekt. Służy do edycji danych dla kraju. Klasa TreeModel jest modelem danych dla komponentu TreeView i nie jest niczym więcej niż cienką warstwą między klasą Model a interfejsem użytkownika, ale jej zadaniem jest uporządkowanie danych z warstwy modelu w hierarchii obiektów TreeItem: public class TreeModel private Model model = new Model(); private TreeItem<World> root = new TreeItem(new World("", "This world")); public TreeModel() build(); public Model getmodel() return model;

85 public TreeItem getdata() return root; public void save() model.save(); private void build() List<TreeItem<World>> worlds = new ArrayList(); for (World world : model.getworlds()) worlds.add(new TreeItem(world)); for (Country country : model.getcountries()) for (TreeItem<World> item : worlds) if (item.getvalue().getcode().equals(country.getworld())) item.getchildren().add(new TreeItem(country)); break; root.getchildren().addall(worlds); Po wdrożeniu tych 5 klas można pisać klasy programu w oknie głównym i oknie dialogowym. Ponieważ żadna z tych klas w zasadzie nie zawiera czegoś nowego, nie chcę tutaj pokazywać kodu, ale zachęcamy do studiowania kodu, a okno dialogowe nie jest dość proste i, między innymi, komplikuje zadanie zmiany kontynentu dla kraj, ponieważ oznacza to, że kraj musi zostać przeniesiony na drzewo. Aby rozwiązać ten problem, usuń stary węzeł i utwórz nowy w innym miejscu i zwróć uwagę, że wizualna reprezentacja drzewa jest automatycznie aktualizowana. 3.6 WIDOK TREETABLE

86 JavaFX ma również kontrolkę TreeTableView, którą można scharakteryzować jako połączenie TableView i TreeView. Odpowiada to temu, że komponent jako TreeView pokazuje hierarchię obiektów, ale gdzie każdy obiekt pojawia się jako rząd wielu wartości, a tym samym więcej kolumn. Brzmi to skomplikowanie i tak też jest, ale komponent działa w zasadzie tak jak poprzednie, choć programowanie jest bardziej skomplikowane. Zacznę od prostego przykładu, który pokaże podstawową składnię i ponownie użyję danych o krajach zorganizowanych w hierarchii, w której rodzicem kraju jest kontynent, do którego należy ten kraj (i zakłada się - nie do końca poprawnie - że kraje należą do jednego konkretnego kontynentu). Jeśli uruchomisz program, zobaczysz poniższe okno, w którym rozszerza się This World i Azja. Oznacza to, że dla każdego kraju wyświetlana jest nazwa, obszar i liczba mieszkańców. Należy tutaj zauważyć, że jeśli wiersz jest kontynentem, wyświetlana jest liczba obszaru i mieszkańców, która jest sumą odpowiednich liczb dla węzłów potomnych. Program nazywa się ShowCountriesProgram i wykorzystuje dwie klasy modeli o nazwie Świat i Kraj. Klasy te są identyczne z odpowiednimi klasami w programie UpdateWorldProgram i nie należy ich wymieniać dalej. Klasa KrajeModel definiuje model dla komponentu TreeTableView: public class CountriesModel private TreeItem<Country> root = new TreeItem(new Country("", "This World", null, null, null, null)); public CountriesModel() build(); public TreeItem<Country> getdata() return root;

87 public TreeTableColumn<Country, String> getnamecol() TreeTableColumn<Country, String> col = new TreeTableColumn<>("Name"); col.setcellvaluefactory(new TreeItemPropertyValueFactory("name")); col.setprefwidth(200); return col; public TreeTableColumn<Country, Long> getareacol() TreeTableColumn<Country, Long> col = new TreeTableColumn<>("Area"); col.setcellvaluefactory(new SumValueFactory("area")); col.setcellfactory(new LongFactory()); col.setstyle("-fx-alignment: CENTER_RIGHT;"); col.getstyleclass().add("salary-header"); col.setprefwidth(100); return col; public TreeTableColumn<Country, Long> getinhabitantscol() TreeTableColumn<Country, Long> col = new TreeTableColumn<>("Inhabitants"); col.setcellvaluefactory(new SumValueFactory("inhabitants")); col.setcellfactory(new LongFactory()); col.setstyle("-fx-alignment: CENTER_RIGHT;"); col.getstyleclass().add("salary-header"); col.setprefwidth(100); return col; private void build()

88 TreeItem<Country> af = new TreeItem(new Country("AF", "Africa", null, null, null, null)); af.getchildren().add(new TreeItem(new Country("ZAF", "South Africa", L, L, "AF", "ZAR"))); root.getchildren().addall(af, an, as, eu, na, oc, sa); class LongFactory implements Callback<TreeTableColumn<Country, Long>, TreeTableCell<Country, public TreeTableCell<Country, Long> call(treetablecolumn<country, Long> col) return new TreeTableCell<Country, public void updateitem(long item, boolean empty) super.updateitem(item, empty); this.settext(null); this.setgraphic(null); if (!empty && item!= 0) this.settext("" + item); ; class SumValueFactory implements Callback<TreeTableColumn.CellDataFeatures<Country, Long>, ObservableValue<Long>> private String field;

89 private long sum = 0; public SumValueFactory(String field) this.field = public ObservableValue<Long> call(treetablecolumn.celldatafeatures<country, Long> celldata) TreeItem<Country> item = celldata.getvalue(); if (item.getvalue().getworld() == null item.getvalue().getworld().length() == 0) sum = 0; calculate(item); return new SimpleLongProperty(sum).asObject(); return new TreeItemPropertyValueFactory<Cou ntry, Long>(field).call(cellData); private void calculate(treeitem<country> item) sum += getvalue(item.getvalue()); for (TreeItem<Country> child : item.getchildren()) if (child.getvalue().getcode().length() == 3) sum += getvalue(child.getvalue()); else calculate(child); private long getvalue(country country) if (field.equals("area")) return country.getarea() == null? 0 : country.getarea();

90 return country.getinhabitants() == null? 0 : country.getinhabitants(); Klasa od razu wygląda jak klasy modelu dla innych komponentów (TableView i TreeView) i składa się głównie z metod zwracających obiekty kolumnowe. Konstruktor wywołuje metodę load (), w której pokazałem tylko kilka instrukcji, ale metoda organizuje dane dla 20 krajów w hierarchii na kontynentach. Oprócz tego, że kod wypełnia część, nie ma w tym nic tajemniczego. Należy zauważyć, że kraj składa się z większej ilości danych niż pokazuje program, a klasa CountryModel faktycznie ma metodę kolumnową dla każdej kolumny danych. Powyżej podałem tylko metody dla trzech kolumn używanych przez program. Nie ma szczególnego powodu, aby nie pokazywać wszystkich kolumn oprócz zaznaczenia, że oczywiście nie jest to konieczne. Podobnie jak w kolumnach dla dwóch poprzednich kontrolek, powiązany jest obiekt CellValueFactory, z którym parametr określa, gdzie należy pobrać poszczególne wartości. Nazwa właściwości jest określana przez konkretne obiekty, które mają być wyświetlane, a tutaj są obiekty Country. CellValueFactory wskazuje zatem, które wartości powinny być wyświetlane w komórkach kolumn. W kolumnie nazwy TreeItemPropertyValueFactory wykorzystuje zawartość poszczególnych komórek jako Etykietę zawierającą wartość. Typ TreeItemPropertyValueFactory implementuje interfejs wywołania zwrotnego, dlatego możesz wpisać własny CellValueFactory jako klasę, która implementuje ten interfejs, jeśli chcesz określić wartość komórek kolumn. Zrobiłem to dla następnych dwóch kolumn w postaci klasy SumValueFactory. Klasa ma pole parametru, które jest nazwą właściwości w klasie Kraj, do której odnosi się kolumna. Celem jest, aby ta sama klasa miała zastosowanie do obu kolumn Obszar i Mieszkańcy. W przeciwnym razie klasa polega przede wszystkim na przesłonięciu metody call (), która jest zdefiniowana przez interfejs zwrotny Callback. Metoda ma parametry reprezentujące bieżącą komórkę w modelu i sprawdza, czy komórka zawiera kraj, który nie ma odniesienia do kontynentu. W takim przypadku sam obiekt reprezentuje kontynent, a metoda wywołuje metodę wyliczenia (), która określa sumę wszystkich wartości obiektów podrzędnych dla bieżącej właściwości. Należy zauważyć, że metoda obliczania () jest rekurencyjna i dlatego rdzeń drzewa pokazuje sumę wszystkich krajów świata. Jeśli bieżąca komórka nie jest dla kontynentu, wartość jest określana przez domyślną TreeItemPropertyValueFactory. Kolumna może być również powiązana z CellFactory i podczas gdy CellValueFactory określa wartość, CellFactory określa, który obiekt wyświetli tę wartość. Celem CellFactory jest powiązanie obiektu, który jest używany do edycji zawartości komórki (co ma być traktowane w następnym przykładzie), ale w tym przypadku CellFactory służy po prostu do wyświetlenia 0 wartości jako pustych (co mogłoby można również osiągnąć za pomocą konwertera). Zawartość komórki to obiekt TreeTableCell, a klasa LongFactory to CellFactory (implementujący interfejs wywołania zwrotnego), który implementuje metodę call (), więc zwraca TreeTableCell, w którym metoda updateitem () jest przesłonięta. Powyższy kod może być trudny do zrozumienia, ale jeśli się nad tym zastanowić, TreeTableView, podobnie jak TableView i TreeView, opiera się na modelu danych, a model (dla TreeTableView) powinien organizować dane w hierarchii i definiować poszczególne kolumny, to już nie jest tak źle i na szczęście dzieje się tak samo za każdym razem. Czy najpierw napisałeś model, a potem reszta idzie sama, a w tym przykładzie jest kod metody start(): public void start(stage primarystage) TreeTableView<Country> view = new TreeTableView<>(model.getData());

91 view.getcolumns().addall(model.getnamecol(), model.getareacol(), model.getinhabitantscol()); BorderPane root = new BorderPane(view); root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root, 500, 300); scene.getstylesheets().add("resources/css/styles.css"); primarystage.settitle("this World"); primarystage.setscene(scene); primarystage.show(); 3.7 WIDOK TREETABLE, ROZSZERZONY PRZYKŁAD Aby zakończyć tę część, pokażę przykład podobny do powyższego, ale pokazujący kraje utworzone w bazie danych, w których możesz również edytować kraje, tworzyć nowe i usuwać kraj, ale taki, że kraj jest edytowany w linii, jak pokazano dla TableView w przykładzie EditPersonProgram. Obecny przykład nazywa się UpdateCountriesProgram i otwiera powyższe okno, w którym wiersz o nazwie Inne kraje zawiera kraje z bazy danych, które nie znajdują się na kontynencie, i gdzie kolumna Kod wyświetla kod kraju z trzema znakami. W rzeczywistości model ma jeszcze dwie kolumny, pokazując kod kraju jako dwa znaki i częściowo pokazując kod waluty. Aby nieco uprościć program, nie załadowałem tabeli walut z bazy danych, dlatego program nie sprawdza poprawności wprowadzonych kodów. Jeśli wprowadzisz nieistniejący kod waluty, nie można zaktualizować tego kraju z powodu klucza obcego w bazie danych. Dlatego kolumna jest domyślnie ukryta. W rzeczywistości nie jest łatwo edytować zawartość poszczególnych komórek w TreeTableView, a wynik łatwo prowadzi do wielu wierszy kodu, jak ma to miejsce w obecnym przykładzie - i nawet nie działa nigdzie. Nie chcę tutaj pokazywać kodu, ale po prostu podkreślaj kluczowe rzeczy, o których powinieneś wiedzieć podczas studiowania kodu. Świat klasy pozostaje niezmieniony w porównaniu do poprzednich przykładów. Z drugiej strony klasa Kraj

92 nieco się zmieniła, polegając na dodaniu kodu własności3 do kodu kraju złożonego z trzech znaków, a następnie typ obszaru dwóch nieruchomości i mieszkańców zmienia się na Długi. Powodem jest to, że populacja świata jest tak duża, że nie może być reprezentowana przez liczbę wewnętrzną. Model klasy jest podobny do odpowiedniej klasy w programie UpdateWorldProgram, ale z tą różnicą, że, jak wspomniano, tym razem nie zawierał waluty tabeli bazy danych. Klasa ma jak poprzednio metodę load (), która ładuje dwie tabele świat i kraj i tworzy dla tych danych dwa obiekty ObservableList. Tym razem ListChangeListener został dodany do listy krajów, które obserwują wydarzenia dotyczące zmian w zawartości listy: public class Model private Callback<Country, Observable[]> cb = (Country c) -> new Observable[] c.nameproperty(), c.areaproperty(), c.inhabitantsproperty(),c. worldproperty(), c.currencyproperty() ; private ObservableList<World> worlds = FXCollections.observableArrayList(); private ObservableList<Country> countries = FXCollections.observableArrayList(cb); public Model() load(); countries.addlistener(new CountryChangeListener()); Ta procedura obsługi zdarzeń służy do aktualizacji bazy danych, a zatem, podobnie jak w programie UpdateWorldProgram, aktualizacja wsadowa nie jest używana, co bezkrytycznie zapisuje wszystkie kraje z powrotem do bazy danych. W bieżącej aplikacji dane fizyczne są zapisywane w bazie danych za każdym razem, gdy dodawany jest nowy kraj, za każdym razem, gdy kraj jest usuwany i przy każdej zmianie komórki. Jeśli jest wiele zmian, niekoniecznie jest to dobre rozwiązanie, ponieważ może prowadzić do wielu zapisów w bazie danych. Następnie jest klasa Kraje Modelu, która jest najbardziej złożoną, a następnie największą z klas programu. Zasadniczo klasa wygląda jak odpowiednia klasa w ShowCountriesProgram i tworzy model danych dla komponentu TreeTableView, a tym samym metod tworzących poszczególne kolumny. Pierwsze dwa nie dodają nic nowego, ale metoda getnamecol () ma tym razem funkcję CellFactory, ponieważ można edytować zawartość komórki. Zasadniczo jest to TextFieldTreeTableCell, a efektem jest otwarcie TextField po dwukrotnym kliknięciu komórki. W tym przypadku istnieje jednak dodatkowe wyzwanie, ponieważ nie wszystkie nazwy można edytować i dlatego zdefiniowałem własny CellFactory: class StringFactory implements Callback<TreeTableColumn<Country, String>, TreeTableCell<Country, String>> private Callback<TreeTableColumn<Country, String>, TreeTableCell<Country, String>> cellfactory = TextFieldTreeTableCell.<Country>forTreeTableColumn();

93 public TreeTableCell<Country, String> call(treetablecolumn<country, String> col) TreeTableCell<Country, String> cell = cellfactory.call(col); cell.itemproperty().addlistener((obs, oldvalue, newvalue) -> TreeTableRow<Country> row = cell.gettreetablerow(); if (row == null) cell.seteditable(false); else Country item = cell.gettreetablerow().getitem(); if (item!= null) ); if (item.getcode3() == null item.getcode3().length() < 3) cell.seteditable(false); else cell.seteditable(true); return cell ; Składnia nie wygląda ładnie, ale tutaj musisz pamiętać, że CellFactory to tylko klasa, która implementuje interfejs wywołania zwrotnego, a tym samym metodę call (). Klasa tworzy TextFieldTreeTableCell służący do edycji treści. Metoda call () jest wykonywana za każdym razem, gdy komórka staje się wizualna i ma moduł obsługi, który przede wszystkim sprawdza, czy komórka reprezentuje obiekt dla kraju, a nie kontynentu, i odpowiednio ustawia właściwość edytowalną komórki. Następna metoda getareacol () działa prawie tak samo jak getnamecol (), ale używana jest inna metoda TextFieldTreeTableCell, ponieważ tym razem musisz edytować Long. Typ to LongFactory i jest zasadniczo identyczny jak StringFactory. Wreszcie metoda wykorzystuje również CellValueFactory (z powodu sumy wszystkich obszarów węzłów potomnych), ale jest to ta sama klasa, co w poprzednim programie. Metoda getinhabitantscol () jest w zasadzie identyczna. Metoda getcurrencycol () działa w taki sam sposób jak getnamecol (), ale metoda getworldcol () jest nieco inna. Kiedy klikniesz komórkę, musisz mieć pole combobox, aby móc wybrać kontynent. Ma typ ComboBoxTreeTableCell i musi jako

94 parametr mieć elementy do wyświetlenia. Obiekt CellFactory ma teraz typ WorldFactory i oprócz tego, że zamiast TextFieldTreeTableCell jest używana ComboBoxTreeTableCell, klasa jest zasadniczo identyczna z pozostałymi dwiema klasami CellFactory. Jednak metoda getworldcol () wymaga dodatkowej akcji, ponieważ zmiana kontynentu oznacza, że odpowiedni węzeł musi zostać przesunięty w drzewie (patrz, jeśli dotyczy, program UpdateWorldProgram). Aby rozwiązać ten problem, należy powiązać moduł obsługi zdarzeń dla editsubmit, który zajmuje się tym, co jest potrzebne. Z powrotem są dwie klasy, chociaż obie są stosunkowo proste i przynajmniej nie dodają niczego nowego. Klasa CountryDialog jest oknem dialogowym i jest używana podczas tworzenia nowego kraju. Powodem jest to, że należy utworzyć kraj o nazwie i legalnych kodach krajów. Powrót to główny program, który nie zawiera niczego nowego, ale należy pamiętać, że aby utworzyć kraj (a tym samym kliknąć przycisk Dodaj), należy wybrać kontynent, dla którego kraj ma zostać utworzony. Podobnie, aby usunąć kraj, musisz wybrać kraj, który chcesz usunąć. Pamiętaj, że nie otrzymujesz żadnego ostrzeżenia o tym, co powinno być stosowane w praktyce. 4. PRZECIĄGNIJ I UPUŚĆ JavaFX obsługuje bezpośrednie przeciąganie i upuszczanie, taka funkcjonalność jest wbudowana zarówno w obiekt Scene, jak i obiekt Node. Mówisz o operacji jako o geście naciśnij i przeciągnijzwolnij, co oznacza, że użytkownik przytrzymuje jeden z przycisków myszy, przeciąga mysz i ponownie ją zwalnia, aby ukończyć operację. Gest można rozpocząć od obiektu Scena lub dowolnego obiektu Węzła, zwanego obiektem źródłowym, a gest może obejmować kilka obiektów (Węzły). Podczas operacji wystrzeliwuje się kilka zdarzeń, a ich użycie zależy od celu bieżącego gestu, którym może być: 1. Że chcesz zmienić kształt węzła, przeciągając jego obwód lub przeciągając go w inne miejsce. W tym geście bierze udział tylko węzeł, który inicjuje operację. 2. Że chcesz narysować źródło i upuścić je w innym węźle (celu) i w taki czy inny sposób połączyć dwa węzły. Po upuszczeniu węzła źródłowego na węźle docelowym wykonuje się pewne działanie. 3. Aby przeciągnąć węzeł i upuścić go na innym węźle, aby przenieść dane z węzła źródłowego do węzła docelowego, a faktyczny transfer danych nastąpi po upuszczeniu węzła źródłowego. Aby opisać te gesty, dokumentacja dzieli je na trzy typy: 1. prosty gest naciśnij i przeciągnij 2. gest pełnego naciśnięcia i przeciągnięcia 3. gest przeciągania i upuszczania a tutaj jest ostatni z najciekawszych (o których należy pamiętać) i podzielę ten rozdział na części odpowiadające temu podziałowi. 4.1 PROSTY GEST ZWOLNIENIA

95 Jak sama nazwa wskazuje, jest to najprostsza forma przeciągania i upuszczania i jest używana tam, gdzie operacja dotyczy tylko jednego węzła i który jest węzłem rozpoczynającym gest. Podczas operacji uruchamiane są wszystkie typy MouseDragEvent: - MouseDragEntered - MouseDragOver - MouseDragExited - MouseDragReleased ale są wysyłane tylko do węzła źródłowego. Jako przykład pokazano poniżej aplikację, która otwiera okno z dwoma kontrolkami Label. Wskazując na górę i przytrzymując przycisk myszy, tło okna zmienia się na jasnozielone, i porusza myszą, tekst zmienia kolor na żółty, a tło zmienia kolor na jasnoniebieski. Jeśli przeciągniesz myszką po oknie, nic się nie stanie - nawet jeśli przeciągniesz myszą nad dolną etykietą. Jednak zwolnienie myszy powoduje, że kolor tła i kolor tekstu górnej etykiety zostaną odpowiednio zmienione na biały i czarny. Przykład powinien pokazywać, co się stanie, jeśli rozpoczniesz gest przeciągania dla etykiety (a więc i dowolnego innego węzła) i przeciągniesz myszą po oknie. package simplednd; import javafx.application.application; import javafx.scene.scene; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.stage.stage; import javafx.geometry.*; import javafx.scene.text.font; import javafx.scene.paint.*; import javafx.beans.property.*;

96 public class SimpleDnD extends Application private Label lbl1 = new Label("Source"); private Label lbl2 = new Label("Target"); private final ObjectProperty<Color> fg = new SimpleObjectProperty<Color>(Color.BLACK); private final ObjectProperty<Background> bg = new public void start(stage stage) Scene scene = new Scene(getRoot()); lbl1.setonmousepressed(e -> bg.set(background(color.lightgreen))); lbl1.setonmousedragged(e -> bg.set(background(color.lightblue))); lbl1.setondragdetected(e -> fg.set(color.yellow)); lbl1.setonmousereleased( e -> bg.set(background(color.white)); fg.set(color.black); ); lbl2.setonmousedragentered(e -> bg.set(background(color.darkgreen))); lbl2.setonmousedragover(e -> bg.set(background(color.darkblue))); lbl2.setonmousedragreleased(e -> fg.set(color.red)); lbl2.setonmousedragexited(e -> bg.set(background(color.magenta))); stage.setscene(scene); stage.settitle("simple DnD"); stage.show(); private Pane getroot() lbl1.setfont(font.font(24));

97 lbl2.setfont(font.font(24)); lbl1.textfillproperty().bind(fg); VBox pane = new VBox(50, lbl1, lbl2); pane.setpadding(new Insets(50, 50, 50, 50)); pane.backgroundproperty().bind(bg); return pane; private static Background background(color color) return new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)); public static void main(string[] args) launch(args); Program ma dwa elementy sterujące Etykieta zdefiniowane jako zmienne instancji, a także dwie właściwości odpowiednio Koloru i Tła. W tym miejscu należy szczególnie zauważyć, że obiekt Background jest tworzony dla określonego koloru metody statycznej background (). Metoda getroot () inicjalizuje komponenty i tworzy widok programu. Należy tutaj zauważyć, że root to VBox i że metoda wiąże swoje tło z właściwością bg, i podobnie kolor tekstu górnej etykiety wiąże się z właściwością fg. Na koniec metoda start (), a tutaj najważniejsze jest to, że 4 procedury obsługi zdarzeń są powiązane z każdym z kontrolek Label. Do góry przypisane są dwa moduły obsługi zdarzeń przeciągania, a wszystkie 4 moduły obsługi zdarzeń przeciągania są przypisane do dołu. Oznacza to, że należy zauważyć, że te procedury obsługi zdarzeń nigdy nie są wykonywane. Prosty gest naciśnij i przeciągnij-puść odnosi się tylko do węzła - i wysyła tylko powiadomienia dotyczące przeciągania do tego węzła - który inicjuje operację, czyli węzeł wskazany myszą po naciśnięciu przycisku. Operacja kończy się po zwolnieniu myszy i w dowolnym miejscu, w którym wskazuje mysz. 4.2 PEŁNY GEST OPUSZCZENIA Gdy węzeł źródłowy otrzyma powiadomienie o zdarzeniu MouseDragDetected, może uruchomić gest pełnego wciśnięcia i zwolnienia, wywołując metodę startfulldrag (). Ponadto dla właściwości mousetransparent należy ustawić wartość false dla węzła źródłowego, aby ten węzeł nie odbierał

98 wszystkich powiadomień przeciągania. Przykład FullpressDnD jest prawie identyczny z powyższym programem i otwiera to samo okno. Kliknięcie górnej etykiety rozpoczyna operację przeciągania, która natychmiast skutkuje tym samym, co powyżej, ale gdy mysz jest przeciągnięta nad dolną etykietą, tło okna zmienia się na ciemnoniebieskie, a tekst na dolnej etykiecie staje się biały. Jeśli mysz zostanie ponownie odsunięta od dolnej etykiety, kolor tekstu w górnej etykiecie zmieni się na fioletowy. Po zwolnieniu mysz wraca do ustawień domyślnych. Tym razem kod jest następujący: public class FullpressDnD extends Application private Label lbl1 = new Label("Source"); private Label lbl2 = new Label("Target"); private final ObjectProperty<Color> fg1 = new SimpleObjectProperty<Color>(Color.BLACK); private final ObjectProperty<Color> fg2 = new SimpleObjectProperty<Color>(Color.BLACK); private final ObjectProperty<Background> bg = new public void start(stage stage) Scene scene = new Scene(getRoot()); lbl1.setonmousepressed(e -> startdrag()); lbl1.setonmousedragged(e -> bg.set(background(color.lightblue))); lbl1.setondragdetected(e -> lbl1.startfulldrag(); fg1.set(color.yellow); ); lbl1.setonmousereleased(e -> enddrag()); lbl2.setonmousedragentered(e -> fg2.set(color.white)); lbl2.setonmousedragover(e -> bg.set(background(color.darkblue))); lbl2.setonmousedragreleased(e -> lbl1.settext("end")); lbl2.setonmousedragexited( e -> if (lbl1.ismousetransparent()) fg1.set(color.magenta); ); stage.setscene(scene); stage.settitle("full Press DnD"); stage.show();

99 private void startdrag() lbl1.settext("source"); lbl1.setmousetransparent(true); bg.set(background(color.lightgreen)); private void enddrag() lbl1.setmousetransparent(false); bg.set(background(color.white)); fg1.set(color.black); fg2.set(color.black); private Pane getroot() lbl1.setfont(font.font(24)); lbl2.setfont(font.font(24)); lbl1.textfillproperty().bind(fg1); lbl2.textfillproperty().bind(fg2); VBox pane = new VBox(50, lbl1, lbl2); pane.setpadding(new Insets(50, 50, 50, 50)); pane.backgroundproperty().bind(bg); return pane; private static Background background(color color) return new

100 Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)); public static void main(string[] args) launch(args); Tym razem zdefiniowane są dwie właściwości obiektów Color, dzięki czemu kolor tekstu dla obu kontrolek Label jest powiązany z właściwością (metoda getroot ()). W przeciwnym razie należy najpierw zauważyć, które procedury obsługi zdarzeń są powiązane z dwiema etykietami. Kiedy mysz wskazuje na górną etykietę, a mysz jest naciskana, mousetransparent jest ustawiany na true (metoda startdrag ()), a po zwolnieniu myszy jest ponownie ustawiany na false (metoda enddrag ()). Na koniec zauważ, że lbl1 w dragdetected wykonuje startfulldrag (), w wyniku czego komponent lbl2 tym razem otrzymuje powiadomienia przeciągania. Zachęcamy do eksperymentowania z programem i obserwowania, kiedy ma miejsce każde zdarzenie. W tym miejscu należy szczególnie zwrócić uwagę na dragreleased dla dolnej etykiety, która zmienia tekst w górnej etykiecie. Jest wykonywany, gdy zwolnisz mysz, gdy wskazuje ona dolną etykietę. 4.3 Gest przeciągania i opuszczania Ostatnia forma gestu przeciągnij i upuść jest głównie ogólna i prawdopodobnie również najczęściej używana i używana do wydobywania danych z węzła źródłowego i upuszczania ich na węźle docelowym. Omawiany gest może dotyczyć węzłów w tej samej aplikacji, ale może również dotyczyć węzłów w dwóch różnych aplikacjach Java. W rzeczywistości obie aplikacje niekoniecznie muszą być aplikacjami Java. Zasadniczo operacja przeciągania i upuszczania obejmuje następujące działania: - Wskaż węzeł i przytrzymaj jeden z przycisków myszy - Przeciągnij mysz, gdy przycisk jest wciśnięty, a węzeł otrzymuje zdarzenie DragDetected i musi wykonać startdraganddrop (), po czym węzeł jest węzłem źródłowym gestu, a bieżące dane są umieszczane w schowku - Po rozpoczęciu gestu przeciągania i upuszczania system nie wysyła już MouseEvent, ale zamiast tego DragEvents - Jeśli źródło gestu zostanie przeciągnięte nad celem gestu, sprawdzi, czy zaakceptuje dane ze schowka i zrobi to, a DragEvent wskaże, że dane są akceptowane. - Jeśli użytkownik upuści mysz, wskazując na cel gestu, zastosuje bieżące dane i wyśle zdarzenie DragDropped - Gdy źródłowy gest węzła odbierze zdarzenie DragDone, informuje, że operacja została zakończona

101 W tej sekcji zilustruję przeciąganie i upuszczanie trzema przykładami, a pierwszy nazywa się TextDnd i otwiera następujące okno: Jest to w zasadzie to samo okno, co w dwóch poprzednich przykładach, z tym wyjątkiem, że poza dwoma kontrolkami Etykieta znajduje się ramka i dodano przycisk. W tym przykładzie gesty przeciągnij i upuść przeciągnij tekst z górnej etykiety i upuść go na dolnej etykiecie. Gest przeciągnij i upuść można wykonać na dwa sposoby (właściwie trzy i można przeczytać o przeciąganiu i upuszczaniu w książce Java 10), gdzie upuszcza się kopię elementu danych (tutaj tekst w górnej etykiecie ), podczas gdy inny go przenosi. Tutaj tekst Kopiuj w przycisku oznacza, że jest to wykonywany gest kopiowania. Kliknięcie przycisku zmienia tekst i jest gestem ruchu podczas resetowania okna. Kod jest następujący: public class TextDnD extends Application private Button cmd = new Button("Copy"); private Label lbl1 = new Label("Source"); private Label lbl2 = new Label("Target"); private TransferMode mode = public void start(stage stage) Scene scene = new Scene(getRoot()); lbl1.setondragdetected(this::dragdetected); lbl2.setondragover(this::dragover); lbl2.setondragdropped(this::dragdropped); lbl1.setondragdone(this::dragdone); stage.setscene(scene);

102 stage.settitle("text DnD"); stage.show(); private Pane getroot() lbl1.setfont(font.font(24)); lbl2.setfont(font.font(24)); lbl1.setpadding(new Insets(5, 10, 5, 10)); lbl2.setpadding(new Insets(5, 10, 5, 10)); lbl1.setstyle("-fx-border-color: black;"); lbl2.setstyle("-fx-border-color: black;"); cmd.setonaction(e -> reset()); VBox pane = new VBox(50, lbl1, lbl2, cmd); pane.setpadding(new Insets(50, 50, 50, 50)); pane.setalignment(pos.center); return pane; private void dragdetected(mouseevent e) String text = lbl1.gettext(); if (text == null) e.consume(); return; Dragboard dragboard = lbl1.startdraganddrop(mode); ClipboardContent content = new ClipboardContent(); content.putstring(text); dragboard.setcontent(content); e.consume();

103 private void dragover(dragevent e) Dragboard dragboard = e.getdragboard(); if (dragboard.hasstring()) e.accepttransfermodes(mode); e.consume(); private void dragdropped(dragevent e) Dragboard dragboard = e.getdragboard(); if (dragboard.hasstring()) String text = dragboard.getstring(); lbl2.settext(text); e.setdropcompleted(true); else e.setdropcompleted(false); e.consume(); private void dragdone(dragevent e) TransferMode mode = e.gettransfermode(); if (mode == TransferMode.MOVE) lbl1.settext(""); e.consume(); private void reset() lbl1.settext("source"); lbl2.settext("target"); if (mode == TransferMode.COPY)

104 cmd.settext("move"); mode = TransferMode.MOVE; else cmd.settext("copy"); mode = TransferMode.COPY; public static void main(string[] args) launch(args); Jeśli chodzi o zmienne instancji, są one zrozumiałe, ale zwróć uwagę na ostatni używany do wskazania, czy gestem powinien być Kopiuj, czy Przenieś. Typ jest wyliczeniem. Metoda getroot (), która tworzy widok programu, nie zawiera niczego nowego, ale w metodzie start () należy zauważyć, które procedury obsługi zdarzeń są powiązane. Zasadniczo do węzła źródłowego należy przypisać dwie procedury obsługi zdarzeń: DragDetected i DragDone. Węzeł docelowy musi odpowiednio powiązać dwie procedury obsługi zdarzeń: DragOver i DragDropped. Odpowiadające temu jest najważniejsze w przykładzie tych 4 traderów zdarzeń. Metoda dragdetected () rozpoczyna się od sprawdzenia, czy rzeczywiście istnieje element danych, a tutaj, jeśli górna etykieta pokazuje tekst. Jeśli tak, startdraganddrop () jest wykonywany w węźle źródłowym, a parametr określa, którą operację należy wykonać. Metoda zwraca obiekt DragBoard i jest obiektem reprezentującym schowek. W rzeczywistości DragBoard jest podklasą Schowka klasy. Metoda dragdetected () ma również odniesienie do schowka i zapisuje tekst w schowku, a na koniec aktualizuje obiekt DragBoard bieżącą zawartością w schowku. Metoda dragdone () nie zawsze jest konieczna, ale w tym przypadku sprawdza, czy jest to operacja Move, a jeśli tak, to metoda jest odpowiedzialna za usunięcie tekstu w komponencie Label - to znaczy w węźle źródłowym. Metoda dragover () jest wykonywana, gdy użytkownik przesuwa mysz nad docelowym węzłem. Metoda sprawdza, czy coś znajduje się w schowku, a jeśli tak jest, jest wykonywana accepttransfermodes (), co oznacza, że zmienia się kursor, dzięki czemu można wizualnie zobaczyć, że znajdujesz się nad węzłem, w którym można upuścić. Wreszcie istnieje metoda dragdropped (). W takim przypadku rozpoczyna się sprawdzanie, czy w schowku znajduje się ciąg znaków, a jeśli tak, tekst jest pobierany, a tekst komponentu etykiety aktualizowany. Następnie wykonywana jest metoda dropcompleted (), która wysyła powiadomienie do węzła źródłowego, że operacja została zakończona. Przed opuszczeniem programu należy również

105 zwrócić uwagę na metodę reset () używaną jako moduł obsługi zdarzeń dla przycisku i między innymi trybami przełączników, więc następną operacją przeciągania i upuszczania może być gest Move. W następnym przykładzie pokażę, jak przeciągnąć obraz do aplikacji, w której może to być obraz lub plik zawierający obraz. Oznacza to, że możesz przeciągnąć element danych reprezentujący obraz z innego programu (takiego jak program Pliki) do bieżącej aplikacji. Przykład nazywa się ImageDnD i otwiera następujące okno, które zawiera przycisk i kontrolkę ImageView: public class ImageDnD extends Application private ImageView view = new public void start(stage stage) Scene scene = new Scene(createRoot()); scene.setondragover(this::dragover); scene.setondragdropped(this::dragdropped); stage.setscene(scene); stage.settitle("image DnD"); stage.show(); private Pane createroot() view.setfitwidth(300); view.setfitheight(200); view.setsmooth(true);

106 view.setpreserveratio(true); HBox pane = new HBox(10, createbutton("clear", e -> view.setimage(null))); pane.setalignment(pos.center_right); VBox root = new VBox(20, view, pane); root.setpadding(new Insets(20, 20, 20, 20)); return root; private Button createbutton(string text, EventHandler<ActionEvent> handler) Button cmd = new Button(text); cmd.setonaction(handler); return cmd; private void dragover(dragevent e) Dragboard dragboard = e.getdragboard(); if (dragboard.hasimage() dragboard.hasfiles()) e.accepttransfermodes(transfermode.any); e.consume(); private void dragdropped(dragevent e) boolean completed = false; Dragboard dragboard = e.getdragboard(); if (dragboard.hasimage()) completed = transferimage(dragboard.getimage()); else if (dragboard.hasfiles()) completed = transferimagefile(dragboard.getfiles()); else System.out.println("Error Illegal format: Image, File, URL"); e.setdropcompleted(completed); e.consume();

107 private boolean transferimage(image image) view.setimage(image); return true; private boolean transferimagefile(list<file> files) for(file file : files) try String mimetype = Files.probeContentType(file.toPath()); if (mimetype!= null && mimetype.startswith("image/")) view.setimage(new Image(file.toURI().toURL().toExternalForm())); return true; catch (IOException ex) System.out.println(ex.getMessage()); return false; public static void main(string[] args) launch(args);

108 Metoda createroot (), która tworzy widok programu, nie zawiera niczego nowego, a zadaniem jest zainicjowanie kontrolera ImageView. Należy jednak pamiętać, że do przycisku przypisana jest procedura obsługi zdarzeń, która usuwa zawartość komponentu ImageView. Metoda start () przypisuje dwie procedury obsługi zdarzeń do obiektu Scene, co oznacza, że możesz upuścić obiekt w oknie aplikacji. Istnieją tylko dwa moduły obsługi, ponieważ tym razem program nie ma źródłowego węzła gestu - operacja przeciągania i upuszczania jest inicjowana w innej aplikacji. dragover () ma taką samą funkcję jak w poprzednim przykładzie i powinieneś zwrócić uwagę, jak sprawdzić, czy schowek zawiera element danych, który program może zaakceptować. Jest również testowany w metodzie dragdropped () iw zależności od tego, jaki element danych to (obraz lub plik), wywoływana jest metoda, która wykonuje transfer danych. W tym miejscu należy szczególnie zwrócić uwagę na metodę transferimagefile () i zastosowaną składnię. Oznacza to, że nazwa pliku musi zostać użyta do załadowania tego obrazu z pliku. Jako ostatni przykład przeciągania i upuszczania pokażę program, w którym możesz przeciągnąć obiekt typu niestandardowego. Przykład nazywa się PersonDnD i otwiera następujące okno: Okno zawiera dwie kontrolki ListView. Każda linia w lewym ListView reprezentuje obiekt Person, a obiekty te można upchnąć w prawo gestem przeciągania i upuszczania. Podobnie możesz przeciągać obiekty od prawej do lewej. Obiekt Person jest bardzo popularną klasą modelu reprezentującą osobę o dwóch cechach: public class Person implements Serializable private String name = ""; private String job = ""; public Person(String name, String job) this.name = name; this.job = job;

109 public String getname() return name; public void setname(string name) this.name = name; public String getjob() return job; public void setjob(string job) this.job = public String tostring() return getname() + ": " + getjob(); Jest tylko jedna rzecz do zaobserwowania, mianowicie, że klasa jest serializowalna. Jest to warunek wstępny używania obiektów w geście przeciągania i upuszczania. Należy również pamiętać, że oznacza to, że typy zmiennych instancji klasy muszą być możliwe do serializacji. Klasa PersonDnD to: public class PersonDnD extends Application private static final DataFormat PERSON_LIST = new DataFormat("persons/personlist"); ListView<Person> view1 = new ListView();

110 ListView<Person> view2 = new public void start(stage stage) Scene scene = new Scene(getRoot()); stage.setscene(scene); stage.settitle("person DnD"); stage.show(); private Pane getroot() view1.setprefsize(300, 300); view2.setprefsize(300, 300); view1.getitems().addall(getpersons()); view1.getselectionmodel().setselectionmode(selectionmode.multiple); view2.getselectionmodel().setselectionmode(selectionmode.multiple); view1.setondragdetected(e -> dragdetected(e, view1)); view2.setondragdetected(e -> dragdetected(e, view2)); view1.setondragover(e -> dragover(e, view1)); view2.setondragover(e -> dragover(e, view2)); view1.setondragdropped(e -> dragdropped(e, view1)); view2.setondragdropped(e -> dragdropped(e, view2)); view1.setondragdone(e -> dragdone(e, view1)); view2.setondragdone(e -> dragdone(e, view2)); GridPane pane = new GridPane(); pane.setpadding(new Insets(20, 20, 20, 20)); pane.sethgap(20); pane.addrow(0, view1, view2); return pane; private ObservableList<Person> getpersons() private void dragdetected(mouseevent e, ListView<Person> view) int selectedcount = view.getselectionmodel().getselectedindices().size();

111 if (selectedcount == 0) e.consume(); return; Dragboard dragboard = view.startdraganddrop(transfermode.copy_or_move); List<Person> items = getselecteditems(view); ClipboardContent content = new ClipboardContent(); content.put(person_list, items); dragboard.setcontent(content); e.consume(); private void dragover(dragevent e, ListView<Person> view) Dragboard dragboard = e.getdragboard(); if (e.getgesturesource()!= view && dragboard.hascontent(person_list)) e.accepttransfermodes(transfermode.copy_or_move); e.consume(); private void dragdropped(dragevent e, ListView<Person> view) boolean completed = false; Dragboard dragboard = e.getdragboard(); if(dragboard.hascontent(person_list)) view.getitems().addall((arraylist<person>) dragboard.getcontent(person_list)); completed = true; e.setdropcompleted(completed); e.consume(); private void dragdone(dragevent e, ListView<Person> view)

112 TransferMode mode = e.gettransfermode(); if (mode == TransferMode.MOVE) removeselecteditems(view); e.consume(); private List<Person> getselecteditems(listview<person> listview) return new ArrayList(listView.getSelectionModel().getSelectedItems()); private void removeselecteditems(listview<person> view) List<Person> list = new ArrayList(); for(person pers : view.getselectionmodel().getselecteditems()) list.add(pers); view.getselectionmodel().clearselection(); view.getitems().removeall(list); public static void main(string[] args) launch(args); Typów niestandardowych nie można natychmiast przenosić za pośrednictwem schowka, ponieważ nie mają znanego typu MIME. Oznacza to, że węzeł docelowy nie może przetestować tego, co znajduje się w schowku. Dlatego program rozpoczyna się od utworzenia obiektu statycznego typu DataFormat o nazwie PERSON_LIST. Wartość nie jest ważna. Musi to być unikalna nazwa (dla programu). Metoda getroot () wypełnia część i musi między innymi zainicjować lewy ListView obiektami 6-osobowymi. Te obiekty są tworzone w metodzie getpersons (), ale nie pokazałem kodu. Zauważ, że dla obu kontrolek ListView, wybierz MULTIPLE, aby użytkownik mógł wybrać więcej obiektów Person. Najważniejsze jest jednak to, że do każdej kontrolki są przypisane 4 procedury obsługi zdarzeń, ponieważ obie kontrolki mogą być zarówno źródłowymi, jak i docelowymi węzłami gestów. Metody (są 4) wywoływane przez procedury obsługi zdarzeń mają parametr określający, co to jest dla ListView, którego dotyczy zdarzenie. dragdetected () inicjuje operację przeciągania i należy pamiętać, że jej tryb to COPY_OR_MOVE, aby można było z niej korzystać w obu przypadkach. Operację KOPIUJ rozpoczynasz naciskając przycisk myszy, jeśli przytrzymasz również klawisz SHIFT, jest to operacja PRZESUŃ.

113 dragdetected () używa metody getselecteditems (), aby zwrócić wybrane obiekty Person, a ta lista obiektów jest umieszczona w schowku. DragOver () i dragdropped () wyglądają tak, jak pokazano w dwóch poprzednich przykładach, ale zwróć uwagę, jak dragdropped () za pomocą obiektu PERSON_LIST sprawdza, czy są to dane właściwego rodzaju, które znajdują się w schowku przed skopiowaniem listy do docelowego komponentu ListView. Wreszcie istnieje metoda dragdone (), która sprawdza, czy jest to operacja MOVE, a jeśli to konieczne, przenoszone obiekty zostaną usunięte z węzła źródłowego. 5 MVC Wcześniej wziąłem pod uwagę wzorzec moduł-widok-kontroler, który jest wzorzec projektowy programu GUI, w tym kontekście wspomniałem również, że istnieje kilka wersji wzorca, częściowo z powodu użytych narzędzi programistycznych, ale także na ze względu na użyty interfejs API. Jeśli w pełni używasz JavaFX, powinieneś użyć wzorca Module-View-Presenter, który czasami widzisz skrócony jako MVP. Wzór można naszkicować w następujący sposób: Zasadą jest, że widok programu zajmuje się wszystkim, co dotyczy grafiki, ale może zwrócić się do modelu, aby uzyskać dane do wyświetlenia. Interakcja użytkownika ma miejsce w warstwie widoku, ale gdy zachodzi interakcja użytkownika, jest ona wysyłana do modułu prezentera, który się tym zajmuje. Oznacza to, że procedury obsługi zdarzeń okna są umieszczane w klasie prezentera, która w rezultacie może aktualizować komponent widoku i wykonywać polecenia na modelu. Gdy model jest aktualizowany w wyniku poleceń z teraźniejszości, może wysyłać powiadomienia, których komponent widoku może słuchać i ewentualnie czytać model, aby zapewnić synchronizację między modelem a widokiem. Jest to zasada, a tak naprawdę nie różni się bardzo od tej, którą wspomniałem wcześniej o MVC, tylko wzór jest rysowany w sposób, który bezpośrednio obsługuje JavaFX. Inną rzeczą jest to, co oznacza w kodzie, a zwłaszcza kto jest odpowiedzialny za tworzenie poszczególnych obiektów, i najlepiej to ilustruje przykład. Patrząc na poprzednie przykłady w tej książce, mieli w zasadzie dwuwarstwową architekturę składającą się z warstwy widoku i warstwy modelu. Najważniejszą rzeczą w powyższym wzorze jest właściwie dokładniejszy podział warstwy widoku w komponencie widoku i komponencie prezentera. Celem wszystkiego jest uczynienie kodu tak prostym i łatwym w zarządzaniu, jak to tylko możliwe, a nawet najważniejsza rzecz, aby zapewnić, że programy GUI są pisane w znormalizowany sposób, a jednym z wzorców zalecanych przez JavaFX jest systematyczne używanie powiązania danych. W poprzednich książkach korzystałem z adresów baz danych, które mają tylko jedną tabelę: use mysql; create database addresses; use addresses;

114 create table address ( id int not null auto_increment, firstname varchar(50), lastname varchar(30), address varchar(50), code varchar(4), city varchar(30), mail varchar(50), date varchar(10), title varchar(50), primary key (id) ); Jako przykład przyjrzę się programowi, który może utrzymywać tę bazę danych, a kiedy program się uruchomi, musi otworzyć następujące okno: jak w TableView pokazuje przegląd adresów bazy danych. Jeśli klikniesz przycisk, zobaczysz poniższe okno, w którym możesz utworzyć nowy adres, a otrzymasz to samo okno (ale zainicjowane danymi), jeśli dwukrotnie klikniesz adres w tabeli: Program składa się z 9 klas:

115 a poniżej wymienię najważniejsze z tych klas i dokonane wybory. Jeśli zaczynasz od warstwy modelu, ma ona dwie klasy, a jednym z celów jest to, że model poprzez powiązanie danych powinien wysyłać powiadomienia do interfejsu użytkownika, ale także klasy komponentów widoku powinny automatycznie aktualizować model. Na przykład adres klasy musi mieć właściwości JavaFX odpowiadające wszystkim kolumnom w tabeli bazy danych, a ponieważ adresy klasy powinny reprezentować tabelę w powyższym oknie, musi mieć ObservableList <Address> i mieć metody, które mogą zwrócić kolumny do składnik TableView. Klasy modeli Adres i Adresy muszą zatem zostać zapisane zgodnie z dokładnym wzorcem obsługującym JavaFX. W ten czy inny sposób nie pasuje do tego, że chcesz oddzielić model i jego klasy od reszty kodu, aby można je było pisać niezależnie od tego, jak model ma być używany. Dlatego warstwa DAL jest zdefiniowana w warstwie modelu, która składa się z dwóch klas Osoba i Osoby, z których pierwsza jest zwykłą klasą modelu, reprezentującą jednostkę w bazie danych i zawierającą wyłącznie zmienne instancji przy użyciu konwencjonalnych metod get i set. Klasa Persons składa się głównie z 4 metod, z których jedna zwraca List <Person> z zawartością bazy danych, a pozostałe trzy dotyczą operacji SQL INSERT, UPDATE i DELETE. Po udostępnieniu tej warstwy DAL adres klasy nie jest niczym więcej niż cienkim opakowaniem klasy Person, w którym każda właściwość jest kapsułkowana we właściwości JavaFX. Jednak klasa ma metodę validate (), ponieważ w odniesieniu do bieżącego zadania może sprawdzić, czy obiekt Address jest legalny. Klasa Adresy ma instancję klasy Osoby, zaczynając od odczytu danych w bazie danych, w celu utworzenia ObservableList <Address>. W taki sam sposób, jak pokazano w przykładach, klasa definiuje metody dla poszczególnych kolumn, aby mogły one łączyć się z TableView. Oprócz tego klasa ma metody save () i remove () służące do zapisywania i usuwania danych. Zasadniczo metody te są trywialne i nie są niczym innym, jak wywoływaniem odpowiednich metod w klasie Persons, ale jednocześnie muszą zapewnić synchronizację listy ObservableList klasy z bazą danych. Z tyłu są prezentacje i komponenty widoku. Klasa AddressesView musi definiować okno główne: import addressesprogram.models.*; public class AddressesView extends BorderPane

116 Button cmdadd = new Button("Create address"); TableView<Address> tableview; public AddressesView(Addresses model) tableview = new TableView(model.getAddresses()); createform(model); a oprócz tego istnieje tylko createform (), który inicjuje BorderPane za pomocą TableView i Button. Dlatego nie ma logiki obejmującej procedury obsługi zdarzeń w tej klasie. Należy zauważyć, że klasa dziedziczy BorderPane. Zwróć także uwagę na parametr konstruktora, którym jest model, a zatem obiekt typu Adresy. Na koniec zwróć uwagę na dwie zmienne instancji, które są odniesieniami do kontrolek okna. Zmienne te nie mają widoczności, a zatem mają widoczność pakietu, dzięki czemu można ich używać z klasy prezentera, której zadaniem jest tworzenie procedur obsługi zdarzeń. Dlatego konieczne jest, aby widok i prezenter znajdowały się w tym samym pakiecie. Klasa prezentera jest następująca: public class AddressesPresenter private final Addresses model; private final AddressesView view; private final Stage owner; public AddressesPresenter(Stage owner, Addresses model) this.owner = owner; this.model = model; view = new AddressesView(model); addhandlers(); public Pane getview() return view;

117 private void addhandlers() view.cmdadd.setonaction(e -> add(new Address())); view.tableview.setonmousepressed(new public void handle(mouseevent event) if (event.isprimarybuttondown() && event.getclickcount() == 2) add(view.tableview.getselectionmodel().getselecteditem()); ); private void add(address address) AddressPresenter presenter = new AddressPresenter(model, address); Scene scene = new Scene(presenter.getView()); Stage stage = new Stage(); stage.initowner(owner); stage.initmodality(modality.application_modal); stage.setresizable(false); stage.settitle("address"); stage.setscene(scene); stage.show(); Zatem klasa prezentująca tworzy klasę widoku. W rzeczywistości nie jest to w pełni zgodne ze wzorcem, ponieważ większość ludzi decyduje się na utworzenie obiektów widoku poza klasą, a następnie przesłanie go jako parametru do konstruktora. Ponieważ tego nie zrobiłem, konieczne jest dodanie metody getview (), aby można było odwoływać się do obiektu widoku w klasie głównej. Konstruktor klasy ma jako parametry model i odniesienie do podstawowego obiektu Stage. Służy jako właściciel okna dialogowego do edycji adresu. Należy zwrócić uwagę na metodę addhandlers () jako metodę, która wiąże procedury obsługi zdarzeń z węzłami zdefiniowanymi w komponencie widoku. W takim

118 przypadku należy dołączyć moduł obsługi zdarzeń, aby dwukrotnie kliknąć wiersz w komponencie TableView, a także moduł obsługi przycisku. W obu przypadkach metoda add (), która otwiera okno dialogowe, aby utworzyć nowy adres (gdzie bieżącym parametrem jest nowy obiekt Address) lub edytować istniejący adres (gdzie adres jest obiektem podwójnej linii -kliknął). Okno dialogowe jest zdefiniowane jako następująca klasa, która jest GridPane: public class AddressView extends GridPane private final Address model; TextField txtfirstname = new TextField(); TextField txtlastname = new TextField(); TextField txtaddress = new TextField(); TextField txtcode = new TextField(); TextField txtcity = new TextField(); TextField txtmail = new TextField(); TextField txttitle = new TextField(); DatePicker datepicker = new DatePicker(); Button cmddel = new Button("Remove"); Button cmdok = new Button("OK"); Button cmdcancel = new Button("Cancel"); public AddressView(Address model) this.model = model; createform(); bindfields(); public Address getmodel() return model; private void createform()

119 public void bindfields() txtfirstname.textproperty().bindbidirectional(model.firstnameproperty()); txtlastname.textproperty().bindbidirectional(model.lastnameproperty()); txtaddress.textproperty().bindbidirectional(model.addressproperty()); txtcode.textproperty().bindbidirectional(model.codeproperty()); txtcity.textproperty().bindbidirectional(model.cityproperty()); txtmail.textproperty().bindbidirectional(model.mailproperty()); txttitle.textproperty().bindbidirectional(model.titleproperty()); datepicker.valueproperty().bindbidirectional(model.dateproperty()); W tym miejscu należy zauważyć, że wszystkie węzły, do których mogą odwoływać się procedury obsługi zdarzeń, mają zdefiniowaną widoczność pakietu, aby można było do nich odwoływać się w klasie prezentera. W pewnym sensie narusza zasady obiektowe, w których zmienne muszą być prywatne, ale jeśli chce się przestrzegać tych zasad, konieczne jest napisanie metod get dla wszystkich zmiennych instancji. Dlatego zazwyczaj implementujesz wzorzec MVP jak wyżej i wymagasz, aby widok i prezenter znajdowały się w tym samym pakiecie. Należy zwrócić uwagę na metodę bindfields (), która tworzy powiązania dwukierunkowe dla wszystkich plików fi. W tym przypadku konieczne jest, aby adres klasy modelu miał właściwości JavaFX, z którymi można się połączyć. Oznacza to, że wprowadzone wartości automatycznie aktualizują klasę modelu i na odwrót, że modyfikacja modelu automatycznie aktualizuje interfejs użytkownika. Odpowiednia klasa prezentera jest w zasadzie prosta i przypomina poprzednią klasę prezentera, a między innymi ta klasa tworzy komponent widoku. Istnieje jednak problem do rozwiązania. Jeśli edytujesz adres, model jest automatycznie aktualizowany z powodu powiązań, co jest niepoprawne, jeśli zamkniesz okno dialogowe za pomocą anulowania. W takim przypadku wprowadzone dane muszą zostać anulowane. Dlatego okno dialogowe musi działać na kopii, więc tylko w przypadku kliknięcia OK aktualizuje oryginalny obiekt Adres. Potem jest wreszcie główny program, który zaczyna wszystko: public class AddressesProgram extends public void start(stage stage) AddressesPresenter presenter = new AddressesPresenter(stage, createmodel()); Scene scene = new Scene(presenter.getView()); scene.getstylesheets().add("resources/css/styles.css");

120 stage.settitle("addresses"); stage.setscene(scene); stage.show(); private Addresses createmodel() try return new Addresses(); catch (Exception ex) System.out.println(ex); Platform.exit(); return null; public static void main(string[] args) launch(args); Nie ma wiele do wyjaśnienia, ale należy zauważyć, jak metoda start () tworzy prezentera i model, który jest wysyłany jako parametr do konstruktora klasy prezentera. Architekturę ukończonego programu można zilustrować następująco:

121 6. ZDEFINIOWANE KONTROLKI UŻYTKOWNIKA Rzadko konieczne jest pisanie własnych kontrolek, ale istnieje taka możliwość, a nawet opracowanie własnych kontrolek zdefiniowanych przez użytkownika może być zadaniem. Kontrolka musi pochodzić bezpośrednio lub pośrednio z Węzła klasy, aby mogła być częścią grafu scen w taki sam sposób, jak wszystkie inne kontrolki, ale w większości przypadków zapisuje się kontrolkę zdefiniowaną przez użytkownika jako klasę dziedziczącą istniejącą kontrolkę, ponieważ został stworzony z myślą o najważniejszej funkcjonalności. Jeśli otworzysz program UserControlProgram, pojawi się następujące okno:

122 który zawiera 7 niestandardowych elementów sterujących. Trzy górne mają typ LabelField, składający się z Label i TextField, a 4 dolne mają typ Spinner. Jest to rodzaj przycisku i kliknięcie koła spowoduje obrócenie linii (domyślnie 2 sekundy), po czym składnik uruchomi ActionEvent. Metoda start () programu jest następująca: public void start(stage primarystage) Spinner spin1 = new Spinner(50, Color.RED, Color.WHITE); spin1.setonaction(e -> System.out.println("ok 1")); Spinner spin2 = new Spinner(70); spin2.setonaction(e -> System.out.println("ok 2")); Spinner spin3 = new Spinner(50, Color.RED, Color.WHITE, 1000); spin3.setonaction(e -> System.out.println("ok 3")); spin3.setbackground(color.darkgreen); spin3.setforeground(color.lightgreen); Spinner spin4 = new Spinner(50, Color.DARKBLUE, Color.LIGHTBLUE); spin4.setonaction(e -> System.out.println("ok 4")); spin4.settime(5000); HBox pane = new HBox(20, spin1, spin2, spin3, spin4); LabelField field = null; VBox root = new VBox(20, field = new LabelField("Svend", "", 100), new LabelField("Knud", "", 100, 300), new LabelField("Valdemar", "", 100, 400), pane); field.textproperty().addlistener((ob, ov, nv) -> System.out.println(nv)); field.setfont(font.font("arial", 18)); root.setpadding(new Insets(20, 20, 20, 20));

123 Scene scene = new Scene(root); primarystage.settitle("hello controls"); primarystage.setscene(scene); primarystage.show(); Początkowo tworzone są 4 kontrolki Spinnera, a celem jest pokazanie, jak utworzyć Spinnera (które parametry może określić konstruktor) i jakie właściwości można następnie ustawić. W tym miejscu należy szczególnie zwrócić uwagę na sposób powiązania modułu obsługi zdarzeń. Wszystkie są trywialne, a ich celem jest sprawdzenie (z tekstem na konsoli), kiedy wystąpią dane zdarzenia. 4 kontrolki Spinnera są umieszczone w HBox, który można dodać do katalogu głównego okna. Kiedy tworzony jest root (jako VBox), dodawane są pierwsze 3 kontrolki LabelField, w których przede wszystkim będziesz obserwował parametry konstruktora. Pierwsza kontrola jest związana z obsługą zdarzeń - co jest trywialne, a celem jest pokazanie, że można powiązać procedury obsługi zdarzeń z polem wprowadzania. Oba elementy sterujące użytkownika są proste i nie mają większego praktycznego zainteresowania, ale pokazują coś o tym, jak utworzyć element sterujący użytkownika. W dalszej części tego rozdziału opiszę, w jaki sposób napisane są dwie kontrolki ETYKIETA Podczas pisania niestandardowego kontrolki dwie główne decyzje określają, jakie właściwości powinny być i jakie zdarzenia kontrolka ma uruchomić. Rzeczywista kontrola to w rzeczywistości niewiele więcej niż HBox z etykietą i TextField, a oprócz właściwości HBox, powinieneś być w stanie ustawić odpowiednio szerokość Label i TextField i ustawić tekst dla obu składników. Na koniec musi istnieć właściwość czcionki, która powinna mieć zastosowanie zarówno do Label, jak i TextField, aby oba składniki zawsze używały tej samej czcionki. Gdy wpisujesz tekst lub w inny sposób zmieniasz tekst dla składnika TextField, powinien on wysłać ChangeEvents, a aby te zdarzenia zostały przechwycone z aplikacji, składnik zwraca właściwość textfield składnika TextField. Następnie można napisać komponent, w którym zawarłem tylko jeden z 5 konstruktorów i dwie właściwości: public class LabelField extends HBox private Label label; private TextField field = new TextField(); public LabelField(String caption, String text, double captionwidth, double fieldwidth) setalignment(pos.baseline_left); label = caption == null? new Label() : new Label(caption); if (text!= null) field.settext(text);

124 if (captionwidth > 0) label.setprefwidth(captionwidth); if (fieldwidth > 0) field.setprefwidth(fieldwidth); label.setalignment(pos.center_right); this.setspacing(10); getchildren().addall(label, field); public double getcaptionwidth() return captionwidthproperty().get(); public void setcaptionwidth(double captionwidth) captionwidthproperty().set(captionwidth); public DoubleProperty captionwidthproperty() return label.prefwidthproperty(); public String gettext() return textproperty().get(); public void settext(string text) textproperty().set(text); public StringProperty textproperty()

125 return field.textproperty(); W rzeczywistości nie ma wiele do zauważenia, a najważniejszy kod znajduje się w konstruktorze, w którym dwa komponenty powinny być ładnie umieszczone w kontenerze. Zauważ, że klasa dziedziczy HBox i dlatego jest komponentem, który można wstawić do wykresu sceny, tak jak wszystkie inne kontrolki. 6.2 PŁÓTNO Zanim spojrzę na ostatnią kontrolkę, wspomnę o kanwie, której nie wspomniałem wcześniej. Płótno to kontrolka składająca się z powierzchni do rysowania, w której można rysować figury geometryczne i tekst oraz wstawiać obrazy za pomocą funkcji rysowania. Możesz także manipulować poszczególnymi pikselami za pomocą PixelWriter. Do Canvas dołączono klasę GraphicsContext, która reprezentuje treść graficzną i udostępnia dostępne funkcje rysowania. Zachęcamy do sprawdzenia, jakie metody udostępnia GraphicsContext. Na przykład program CanvasProgram otwiera następujące okno: gdzie rysowany jest kwadrat, okrąg, tekst i krzywa (przypowieść) i dodawane jest zdjęcie. Nowością jest to, że zostało to zrobione przy użyciu Canvas: package canvasprogram; import javafx.application.application; import javafx.scene.scene; import javafx.scene.canvas.*; import javafx.scene.image.*; import javafx.scene.layout.*; import javafx.scene.paint.*; import javafx.scene.text.*; import javafx.stage.stage; public class CanvasProgram extends Application

126 @Override public void start(stage stage) Canvas canvas = new Canvas(520, 160); GraphicsContext gc = canvas.getgraphicscontext2d(); gc.setlinewidth(2.0); gc.strokerect(20, 20, 120, 120); gc.setfill(color.red); gc.filloval(40, 40, 80, 80); gc.setfont(font.font(24)); gc.stroketext("hello", 50, 90); Image image = new Image("resources/images/stone.jpg"); gc.drawimage(image, 160, 20, 120, 120); writegraph(gc); Pane root = new Pane(); root.getchildren().add(canvas); Scene scene = new Scene(root); stage.setscene(scene); stage.settitle("drawing on a Canvas"); stage.show(); private void writegraph(graphicscontext gc) PixelWriter writer = gc.getpixelwriter(); for (double x = 300; x < 500; x += 0.25) writer.setcolor((int)x, (int)y(x), Color.BLUE); private double y(double x) return x * x / * x ;

127 public static void main(string[] args) launch(args); Kod jest wystarczająco łatwy do zrozumienia. W metodzie start () tworzony jest obiekt Canvas o określonym rozmiarze i tworzone jest odwołanie do GraphicsContext obiektu Canvas. Dla tego obiektu określa się szerokość pisaka, który ma zostać narysowany, a następnie do rysowania prostokąta służy narzędzie do rysowania. Ponieważ nic nie mówi się o kolorze, rysuje się go czarnym długopisem. Następnie dodaje się czerwony kolor wypełnienia i rysuje wypełnione koło. Zwróć szczególną uwagę na to, jak nazywa się funkcja rysowania. Kolejnym krokiem jest zdefiniowanie czcionki i narysowanie tekstu. Na koniec wstawiany jest obraz i na koniec wywoływana jest metoda writegraph (), która rysuje krzywą. Krzywa nie jest zbyt ładna - jest pikselowana - i istnieją inne, lepsze sposoby rysowania takiego wykresu, ale przykład powinien pokazywać, jak manipulować poszczególnymi pikselami na kanwie. Na koniec zauważ, że Canvas jest węzłem i dlatego można go dodać do wykresu sceny. 6.3 SPINNER Formant niestandardowy można zapisać w następujący sposób, w którym nie pokazałem kodu trzech właściwości ustawiania prędkości obrotowej i koloru oraz tylko jednego konstruktora: public class Spinner extends Canvas private ObjectProperty<EventHandler<ActionEvent>> onactionproperty = new SimpleObjectProperty<EventHandler<ActionEvent>>(); private IntegerProperty time = new SimpleIntegerProperty(); private ObjectProperty<Color> background = new SimpleObjectProperty(); private ObjectProperty<Color> foreground = new SimpleObjectProperty(); private double size; private Circle circle; private Transition transition; public Spinner(double size, Color background, Color foreground, int time) super(size, size); this.size = size; setbackground(background);

128 setforeground(foreground); settime(time); circle = new Circle(size / 2, size / 2, size / 2); transition = createtransition(); transition.setonfinished(this::clicked); draw(); this.addeventhandler(mouseevent.mouse_clicked, new ClickHandler()); this.seteffect(new Lighting()); private void draw() GraphicsContext gc = getgraphicscontext2d(); gc.setfill(getbackground()); gc.filloval(0, 0, size, size); gc.setfill(getforeground()); double s = size / 2; gc.fillrect(s, s 1, s, 3); gc.fillrect(s 2, s 2, 5, 5); public EventHandler<ActionEvent> getonaction() return onactionproperty.get(); public void setonaction(eventhandler<actionevent> handler) onactionproperty.set(handler);

129 public ObjectProperty<EventHandler<ActionEvent>> onactionproperty() return onactionproperty; private void clicked(actionevent e) if (getonaction()!= null) getonaction().handle(new ActionEvent()); private RotateTransition createtransition() RotateTransition trans = new RotateTransition(Duration.millis(getTime()), this); trans.setbyangle(360); trans.setcyclecount(1); return trans; class ClickHandler implements EventHandler<MouseEvent> public void handle(mouseevent e) if (circle.contains(e.getx(), e.gety())) transition.play(); Najczęściej dzieje się to w konstruktorze, który początkowo inicjuje właściwości i tworzy przejście dla obrotu. Należy zauważyć, że procedura obsługi zdarzeń jest powiązana ze zdarzeniem, które ma miejsce po zakończeniu rotacji i że uruchamia ActionEvent, jeśli istnieje detektor. Konstruktor

130 wywołuje również metodę draw (), która rysuje komponent na kanwie. Zauważ, że nie ma szczególnego powodu do używania Canvas (oprócz pokazania jak), ponieważ możesz to zrobić z istniejącymi klasami Node. Klasa ma również obiekt Circle, który ułatwia testowanie w module obsługi zdarzeń kliknięcia myszą i kliknięcia kółka. 7 JAVAFX I WSPÓŁBIEŻNOŚĆ Aplikacje Java GUI są na ogół wielowątkowe i podobnie jak Swing JavaFX używa specjalnego wątku do aktualizacji interfejsu użytkownika. Wątek ten nosi nazwę JavaFX Application Thread. Ponieważ wszystkie węzły na wykresie programu nie są bezpieczne dla wątków (ze względu na wydajność), istnieją takie same wyzwania, które znasz od Swinga, że nie możesz bezpośrednio zaktualizować ich z innego wątku, ale należy to zrobić przez wywołanie metoda wykonywana w wątku aplikacji JavaFX. W tym rozdziale chcę pokazać, co JavaFX udostępnia, aby było to proste. Program UpdateGUI otwiera następujące okno: Po kliknięciu przycisku Start program uruchamia metodę, która zajmuje dużo czasu, a po jej zakończeniu górna etykieta jest aktualizowana. Kliknięcie przycisku Wyczyść usuwa zawartość górnej etykiety. Jeśli spróbujesz programu, gdy górny przycisk jest wciśnięty, przekonasz się, że nic się nie dzieje po kliknięciu przycisku Wyczyść - przynajmniej nie przed zakończeniem metody uruchamiania przycisku Start. Użytkownik doświadcza, że program zawiesza się. Rozwiązaniami są oczywiście metody wymagające czasu we własnym wątku. Naciśnięcie środkowego przycisku opcji i ponowne kliknięcie przycisku Start powoduje wykonanie metody w wątku w tle, ale teraz pojawia się wyjątek. Powodem jest to, że wątek w tle próbuje zaktualizować wątek aplikacji JavaFX, co powoduje wyjątek. Z drugiej strony naciśnij dolny przycisk radiowy i ponownie kliknij przycisk Start, aby działał tak, jak powinien, a górna etykieta jest regularnie aktualizowana, a tę etykietę można usunąć w dowolnym momencie, klikając przycisk Wyczyść. public class UpdateGUI extends Application private Label label = new Label("Not running "); private RadioButton cmd1 = new RadioButton("Application thread"); private RadioButton cmd2 = new RadioButton("Background thread"); private RadioButton cmd3 = new RadioButton("Platform.runLater()"); private Button

131 public void start(stage stage) ToggleGroup group = new ToggleGroup(); cmd1.settogglegroup(group); cmd2.settogglegroup(group); cmd3.settogglegroup(group); cmd1.setselected(true); HBox commands = new HBox(10, createbutton("clear", e -> label.settext("")), cmdok = createbutton("start", this::work)); commands.setalignment(pos.center); VBox root = new VBox(20, label, cmd1, cmd2, cmd3, commands); root.setpadding(new Insets(20, 20, 20, 20)); root.setalignment(pos.center); Scene scene = new Scene(root, 300, 250); stage.setscene(scene); stage.settitle("update GUI"); stage.show(); private void work(actionevent e) cmdok.setdisable(true); if (cmd1.isselected()) work1(); else Thread th = cmd2.isselected()? new Thread(() -> work1()) : new Thread(() -> work2()); th.setdaemon(true); th.start(); private void work1()

132 for(int i = 1; i <= 10; i++) label.settext("start calulation " + i); calculate(); label.settext("all calulations terminated"); cmdok.setdisable(false); private void work2() for(int i = 1; i <= 10; i++) String text = "Start calulation " + i; Platform.runLater(() -> label.settext(text)); calculate(); String text = "All calulations terminated"; Platform.runLater(() -> label.settext(text)); Platform.runLater(() -> cmdok.setdisable(false)); private Button createbutton(string text, EventHandler<ActionEvent> handler) Button cmd = new Button(text); cmd.setonaction(handler); return cmd; private void calculate()

133 double y = 0; for (int i = 0; i < 10; ++i) for (int j = 0; j < ; ++j) y = Math.sin(Math.sqrt(2)); public static void main(string[] args) launch(args); Metodę, która ma zostać wykonana, wykonuje się w work1 () i work2 (), które to metody wykonują obliczenie () odpowiednio 10 razy. Przed każdym wykonaniem metody etykieta programu jest aktualizowana. Funkcja calc () nie wykonuje nic interesującego, ale wykonuje wiele obliczeń, które zajmują dużo czasu, a celem jest to, że jest to metoda, która stale wykorzystuje procesor maszyny. Jeśli chodzi o interfejs użytkownika, nie ma wiele do wyjaśnienia i należy przede wszystkim zwrócić uwagę na metodę work (), która jest procedurą obsługi zdarzeń dla przycisku Start. Metoda sprawdza, który przycisk opcji jest kliknięty, a jeśli jest na górze, nic innego się nie dzieje, niż wywoływana jest metoda work1 (). Oznacza to, że jest wykonywany w wątku aplikacji JavaFX, w wyniku czego program zawiesza się aż do zakończenia pracy work1 (). Na przykład nie zobaczysz, że przycisk Start jest wyłączony. Czy jest wciśnięty środkowy przycisk opcji, metoda work1 () jest wykonywana ponownie, ale tym razem we własnym wątku. Wynikiem jest wyjątek, gdy metoda próbuje zaktualizować składnik Label. Z drugiej strony, jeśli zostanie naciśnięty dolny przycisk opcji, wykonywana jest metoda work2 (), co w zasadzie jest identyczne z work1 (), ale kiedy interfejs użytkownika ma zostać zaktualizowany, dzieje się to z instrukcją: Platform.runLater (() -> label.settext (text)); co oznacza po prostu, że etykieta komponentu jest aktualizowana w wątku aplikacji JavaFX. Klasa Platform ma dwie metody statyczne, które odnoszą się do wątku aplikacji JavaFX: - public static boolean isfxapplicationthread () - public static void runlater (Runnable runnable) gdzie pierwszy zwraca wartość true, jeśli wątek wywołujący jest Wątkiem aplikacji JavaFX, a drugi tworzy Obiekt Runnable do wykonania przez Wątek aplikacji JavaFX w pewnym momencie, gdy wątek jest uruchomiony. 7.1 ZADANIE Aby obsługiwać wątki w programowaniu GUI, JavaFX oferuje bardzo prostą strukturę współbieżności opartą na istniejącej strukturze Java dla współbieżności. Struktura składa się z pojedynczego wyliczenia, zwanego Stan, który definiuje stany, w których może znajdować się wątek, a także z jednego typu zdarzenia o nazwie WorkerStateEvent, a wątek uruchamia takie zdarzenie po przełączeniu stanu. W przeciwnym razie istnieją tylko cztery typy wątków, które tworzą prostą hierarchię, przy czym jeden z nich to interfejs, a pozostałe trzy są specyficznymi typami:

134 Dokładnie, wystąpienie interfejsu roboczego jest zadaniem do wykonania przez jeden lub więcej wątków w tle. Stan zadania można zaobserwować na podstawie wątku aplikacji JavaFX. Klasy Task, Service i ScheduledService to klasy abstrakcyjne, które implementują interfejs Worker i reprezentują różne rodzaje zadań. Klasa Task reprezentuje zadanie, które można wykonać raz i nie można ponownie użyć obiektu Task. Klasa Service reprezentuje zadanie, które można powtórzyć, a klasa ScheduledService reprezentuje usługę, którą można wykonać w określonym czasie. Poniżej pokażę przykład zadania i usługi. Zadaniem musi być ustalenie liczb pierwszych - zadanie, które widziałeś wiele razy w poprzednich książkach. Celem jest zadanie, które dla dużych liczb zajmuje dużo czasu. Uruchomienie programu PrimesProgram1 otwiera następujące okno: Po kliknięciu przycisku Start program określa wszystkie liczby pierwsze we wprowadzonym interwale i wstawia je w sposób ciągły do pola listy. Dzieje się tak oczywiście w wątku dodatkowym, a gdy wątek jest uruchomiony, przycisk Wyczyść może usunąć zawartość pola listy, a przyciskiem Anuluj możesz zatrzymać wątek, a na koniec przyciskiem Wyjdź możesz zakończyć program. Aby wykonać zadanie, zdefiniuję obiekt Task. Powinieneś zbadać dokumentację zarówno dla Pracownika <V>, jak i Zadania <V>, ale w niniejszej sprawie zdefiniowałem następujące Zadanie, w którym nie pokazałem kodu dotyczącego właściwości klasy: public class PrimesTask extends Task<ObservableList<Long>>

135 private final ObservableList<Long> list = FXCollections.<Long>observableArrayList(); private final LongProperty from = new SimpleLongProperty(); private final LongProperty to = new SimpleLongProperty(); public void clear() protected ObservableList<Long> call() list.clear(); long count = getto() getfrom() + 1; long counter = 1; for (long i = getfrom(); i <= getto(); ++i, ++counter) if (this.iscancelled()) break; updatemessage("check " + i + " as a prime number"); if (isprime(i)) long n = i; Platform.runLater(() -> list.add(n)); updatevalue(fxcollections.<long>unmodifiableobservablelist(list)); updateprogress(counter, count); return list;

136 @Override protected void cancelled() super.cancelled(); updatemessage("the task was protected void failed() super.failed(); updatemessage("the task public void succeeded() super.succeeded(); updatemessage("the task finished successfully"); private boolean isprime(long number) if (number == 2 number == 3 number == 5 number == 7) return true; if (number < 11 number % 2 == 0) return false; for (int t = 3, m = (int)math.sqrt(number) + 1; t <= m; t += 2) if (number % t == 0) return false; return true;

137 Zauważ najpierw, że klasa dziedziczy Task <ObservableList <Long >>. W tym przypadku typ parametru określa typ zwracany przez abstrakcyjne wywołanie metody (). Dwie właściwości zi określają zakres, w którym należy określić liczby pierwsze. Są one zdefiniowane jako właściwości JavaFX, ponieważ muszą mieć możliwość powiązania z interfejsem użytkownika. Następnie jest metoda call (), która jest metodą wykonywaną we własnym wątku. Zaczyna się od usunięcia zawartości listy (co nie jest konieczne w tym przykładzie), a następnie wykonywana jest pętla iterująca w bieżącym zakresie. Dla każdej iteracji wywoływane są metody updatemessage (), updatevalue () (tylko jeśli znaleziono liczbę pierwszą) i updateprogress (). Są to metody zdefiniowane w klasie Zadanie, reprezentujące obserwowalne właściwości, z którymi interfejs użytkownika może się połączyć. Na koniec trzy metody są nadpisywane, które są wykonywane zgodnie ze zmianą stanu, a jedyne, co się dzieje, to aktualizacja właściwości obiektu Task. Następnie jest klasa PrimesProgram1: public class PrimesProgram1 extends Application private PrimesTask task = new PrimesTask(); private LabelField txtfrom = new LabelField("From"); private LabelField txtto = new LabelField("To"); private ListView<Long> lstprimes = new ListView(); private TextField txtmessages = new TextField(); private ProgressBar progressbar = new ProgressBar(0); Klasa ma instancję PrimesTask, która reprezentuje zadanie do wykonania. Zauważ, że klasa korzysta z niestandardowego elementu sterującego z poprzedniego rozdziału. Nie chcę tutaj wyświetlać pełnego kodu, ale następująca metoda jest wywoływana z metody start (): public void bind(worker<observablelist<long>> worker) txtfrom.textproperty().bindbidirectional(task.fromproperty(), new NumberStringConverter()); txtto.textproperty().bindbidirectional(task.toproperty(), new NumberStringConverter()); progressbar.progressproperty().bind(worker.progressproperty()); progressbar.visibleproperty().bind(worker.progressproperty().isnotequalto( SimpleDoubleProperty(ProgressBar.INDETERMINATE_PROGRESS))); new lstprimes.itemsproperty().bind(worker.valueproperty()); txtmessages.textproperty().bind(worker.messageproperty()); który wiąże komponenty w interfejsie użytkownika z właściwościami w obiekcie zadania. Na koniec jest moduł obsługi zdarzenia dla przycisku Start, który tworzy wątek w tle w celu wykonania obiektu Task: private void start(actionevent e)

138 Thread th = new Thread(task); th.setdaemon(true); th.start(); Gdy wątek wykonuje metodę start (), wykonywana jest metoda call () w klasie PrimesTask. Gdy wypróbujesz program, powinien on działać zgodnie z opisem, ale ponownie kliknij przycisk Start, a zobaczysz, że nic się nie dzieje. Jak wspomniano powyżej, obiekt Zadanie można wykonać tylko raz, i tutaj obiekt Usługi jest opcją. 7.2 USŁUGA Program PrimesProgram2 otwiera to samo okno, co pokazano w poprzednim przykładzie, a klasa PrimesTask pozostaje niezmieniona. Z drugiej strony dodano następującą klasę, która reprezentuje usługę, i ponownie nie pokazałem kodu dla dwóch właściwości: public class PrimesService extends Service<ObservableList<Long>> private PrimesTask task; private final LongProperty from = new SimpleLongProperty(); private final LongProperty to = new SimpleLongProperty(); public void clear() if (task!= null ) protected Task<ObservableList<Long>> createtask() task = new PrimesTask(); task.setfrom(from.get()); task.setto(to.get()); return task; Usługa nie jest niczym więcej niż opakowaniem dla zadania, a następnie należy zastąpić metodę createtask (), która jest wykonywana za każdym razem, gdy usługa jest wykonywana. Tworzy zadanie do wykonania, w tym przypadku zainicjowane przy użyciu bieżących wartości. Klasa PrimesProgram2

139 jest również prawie identyczna z PrimesProgram1, a główna różnica polega na tym, że obiekt Task został zastąpiony przez obiekt Service i że zmieniono obsługę zdarzeń dla przycisku Start. 8 KSZTAŁTY 3D JavaFX obsługuje także w pewnym stopniu grafikę 3D, chociaż możliwości są nieco ograniczone, ale przyszłe wersje języka niewątpliwie będą oferować więcej. Poniżej znajduje się krótkie wprowadzenie do tego, o co w tym wszystkim chodzi, a nie przykłady praktycznych zastosowań. Istnieje tylko kilka klas, wszystkie wywodzące się z Shape3D, i w rzeczywistości istnieją tylko cztery przykłady określonych kształtów: Box, Kula, Cylinder i MeshView, gdzie ten ostatni reprezentuje niestandardowy kształt. Wizualizację 3D można osiągnąć za pomocą światła i kamer, które są również węzłami i mogą być uwzględnione na wykresie sceny, a sposób wyświetlania kształtu zależy od położenia źródeł światła i kamer na wykresie sceny. Jednak Java3D niekoniecznie jest obsługiwana, ale będzie działać na większości nowoczesnych komputerów. Możesz przetestować, gdzie tak jest, w przypadku następującego programu (lub równoważnych instrukcji): package check3d; import javafx.application.*; public class Check3D public static void main(string[] args) if (Platform.isSupported(ConditionalFeature.SCENE3D)) System.out.println("3D is supported"); else System.out.println("No 3D support"); Punktem wyjścia dla grafiki 3D jest układ współrzędnych 3D z punktem początkowym w lewym górnym rogu ekranu i osią az, która wskazuje na ekran, podczas gdy oś x i oś y są zorientowane w taki sam sposób jak w grafice 2D. Jeśli w 2D dodasz do wykresu sceny dwa zachodzące na siebie kształty, to ostatnia dodana postać nakłada się na pierwszą, ale niekoniecznie jest to 3D, ponieważ głębokość również odgrywa rolę. Można to zilustrować programem Rectangles3D, który otwiera następujące okno:

140 gdzie należy zauważyć, że zielony prostokąt jest narysowany przed czerwonym. Kod programu to: public class Rectangles3D extends public void start(stage stage) Rectangle red = new Rectangle(100, 100); red.setfill(color.red); red.settranslatex(100); red.settranslatey(100); red.settranslatez(400); Rectangle green = new Rectangle(100, 100); green.setfill(color.green); green.settranslatex(150); green.settranslatey(150); green.settranslatez(300); Group center = new Group(green, red); CheckBox check = new CheckBox("DepthTest for Rectangles"); check.setselected(true); check.selectedproperty().addlistener((prop, oldvalue, newvalue) -> if (newvalue) red.setdepthtest(depthtest.enable); green.setdepthtest(depthtest.enable); else red.setdepthtest(depthtest.disable); green.setdepthtest(depthtest.disable); ); BorderPane root = new BorderPane(center, check, null, null, null);

141 root.setstyle("-fx-background-color: transparent;"); root.setpadding(new Insets(20, 20, 20, 20)); Scene scene = new Scene(root, 300, 200, true); scene.setcamera(new PerspectiveCamera()); stage.setscene(scene); stage.settitle("depth Test"); stage.show(); public static void main(string[] args) launch(args); W metodzie start () zdefiniowano dwa prostokąty, czerwony i zielony, a nowy polega na tym, że tym razem zdefiniowano również wartość dla osi z, która odpowiednio wynosi 400 i 300. Oznacza to, że czerwony prostokąt leży dalej niż zielony. Dwa prostokąty są dodawane do węzła Grupy, ale tak, aby czerwony był dodawany jako ostatni, i dlatego powinien pokrywać się z zielonym, ale to odwrotnie, jak to się dzieje, gdy czerwony jest dalej. Aby tak się stało, należy zrobić jeszcze dwie rzeczy. Podczas tworzenia obiektu Scena należy określić inny parametr, który mówi o użyciu grafiki 3D, a także dołączyć kamerę do wykresu scen. Okno ma również pole wyboru, co oznacza, że możesz włączyć lub wyłączyć efekt 3D, a odznaczenie pola wyświetla grafikę jako zwykłą grafikę 2D, w której czerwony prostokąt zachodzi na zielony. 8.1 SKRZYNKA, KULA I CYLINDER Jak wspomniano, JavaFX rodzi się z trzema klasami kształtów, reprezentującymi odpowiednio pudełko, kulę i cylinder. Wszystkie klasy dziedziczą po Shape3D i dziedziczą tutaj trzy kluczowe właściwości: 1. materiał 2. tryb rysowania 3. cull face jak wszystko wyjaśniono później, ale pierwszy jest użyty w poniższym przykładzie i, jak sama nazwa wskazuje, mówi, w jaki sposób należy narysować powierzchnię. Podczas tworzenia Shape3D ma on środek w punkcie początkowym układu współrzędnych, ale ponieważ jest węzłem, można go przesuwać za pomocą transformacji. Wreszcie, jego położenie i rozmiar są określane przez kamerę używaną do wyświetlania figury oraz gdzie kamera znajduje się w układzie współrzędnych. Obecna

142 lokalizacja kamery może utrudnić przewidzenie rzeczywistej pozycji figury. Aplikacja ShapesProgram otwiera następujące okno i pokazuje przykłady trzech kształtów: Obiekt ramki został przeniesiony z tłumaczeniem, ale również został obrócony. Pozostałe są przenoszone z translacją, ale gdy cylinder jest lekko zdeformowany, dzieje się tak, ponieważ kamera znajduje się po lewej stronie. Należy również pamiętać, że kolor zależy od położenia światła. Kod jest następujący: package shapesprogram; import javafx.application.application; import javafx.scene.*; import javafx.scene.shape.*; import javafx.stage.stage; import javafx.scene.paint.*; public class ShapesProgram extends public void start(stage stage) Scene scene = new Scene(new Group(createBox(), createsphere(), createcylinder(), createlight()), 500, 200, true); scene.setcamera(createcamera()); stage.setscene(scene); stage.settitle("3d Shapes"); stage.show(); private Box createbox() Box b = new Box(100, 150, 300);

143 b.settranslatex(150); b.settranslatey(200); b.settranslatez(200); b.setrotate(20); b.setmaterial(new PhongMaterial(Color.GREEN)); return b; private Sphere createsphere() Sphere s = new Sphere(100); s.settranslatex(400); s.settranslatey(150); s.settranslatez(300); s.setmaterial(new PhongMaterial(Color.RED)); return s; private Cylinder createcylinder() Cylinder c = new Cylinder(100, 300); c.settranslatex(650); c.settranslatey(200); c.settranslatez(400); c.setmaterial(new PhongMaterial(Color.BLUE)); return c; private PointLight createlight() PointLight p = new PointLight();

144 p.settranslatex(100); p.settranslatey(100); p.settranslatez(-100); return p; private PerspectiveCamera createcamera() PerspectiveCamera c = new PerspectiveCamera(false); c.settranslatex(100); c.settranslatey(100); c.settranslatez(-100); return c; public static void main(string[] args) launch(args); Kod jest właściwie dość prosty i wystarczająco łatwy do zrozumienia, ale z drugiej strony efekt jest trudniejszy do zrozumienia. Wszystko to dzieje się w ostatnich 5 metodach, które tworzą odpowiednio obiekt Box, obiekt Sphere, obiekt Cylinder, obiekt PointLight i obiekt PerspectiveCamera. W odniesieniu do obiektu Box jest to pudełko o wymiarach , które jest przesunięte w punkcie (150, 200, 200), a zatem w punkcie, który znajduje się 200 za ekranem. Następnie pudełko jest obracane o 20 stopni. W szczególności należy pamiętać, jak przypisać kolor do powierzchni za pomocą obiektu Materiał. Sfera i cylinder są w zasadzie tworzone w taki sam sposób, że dla obiektu Sfera należy zdefiniować promień, natomiast dla cylindra konieczne jest wskazanie zarówno promienia, jak i wysokości. Kamera znajduje się w punkcie (100, 100, -100), a więc po lewej i nieco przed ekranem. Pamiętaj, że źródło światła znajduje się w tym samym miejscu, co oczywiście nie musi tak być. W metodzie start () źródło światła jest dołączane do wykresu sceny w taki sam sposób, jak każdy inny węzeł, podczas gdy kamera jest bezpośrednio podłączona do obiektu Stage. Zachęcamy do eksperymentowania z programem i zauważenia, co się stanie, jeśli zmienisz wartości w 5 metodach. W szczególności spróbuj zmienić rozmiar okna. Następnie kształty również się zmieniają. 8.2 MATERIAŁ

145 Obiekt Shape3D ma właściwość typu Materiał, która określa wygląd powierzchni, i może być kolorem, ale może również być obrazem. Program BoxProgram otwiera okno z dwoma obiektami Box: gdzie jeden ma kolor (Color.BEIGE), a powierzchnia drugiego jest obrazem. public class BoxProgram extends public void start(stage stage) PhongMaterial m1 = new PhongMaterial(); m1.setdiffusecolor(color.tan); PhongMaterial m2 = new PhongMaterial(); m2.setdiffusemap(new Image("resources/images/stone.jpg")); Scene scene = new Scene(new Group(createBox(500, 310, 500, m1), createbox(800, 310, 500, m2), createlight()), 600, 300, true); scene.setcamera(createcamera()); stage.setscene(scene); stage.settitle("material"); stage.show(); private Box createbox(double x, double y, double z, Material m)

146 Box box = new Box(200, 200, 200); box.setmaterial(m); box.settranslatex(x); box.settranslatey(y); box.settranslatez(z); return box; private PointLight createlight() PointLight p = new PointLight(); p.settranslatex(200); p.settranslatey(200); p.settranslatez(-600); return p; private PerspectiveCamera createcamera() PerspectiveCamera c = new PerspectiveCamera(false); c.settranslatex(600); c.settranslatey(300); c.settranslatez(-50); return c; public static void main(string[] args) launch(args); Dwa obiekty Box są tworzone za pomocą metody createbox (), gdzie parametrami są współrzędne tłumaczenia, a także Materiał. Jest to klasa abstrakcyjna i istnieje tylko jedna konkretna klasa o nazwie PhongMaterial. Ma wiele metod (takich jak setdiffusecolor () i setdiffusemap ()) używanych do

147 określenia, jak powinna wyglądać powierzchnia, i zachęcamy do sprawdzenia dokumentacji. W takim przypadku w metodzie start () tworzone są dwa obiekty PhongMaterial, przy czym ostatni odnosi się do obrazu. 8.3 TRYB RYSOWANIA Powierzchnia obiektów Shape3D jest w rzeczywistości rysowana jako liczba trójkątów i za pomocą parametru możesz wskazać konstruktorowi, ile ich ma być. Domyślnie trójkąty te są rysowane jako wypełnione, ale możesz również wskazać, że musisz tylko narysować obwód. Istnieją zatem dwa tryby rysowania: 1. DrawMode.FILL (domyślnie) 2. DrawMode.LINE Program SphereProgram otwiera następujące okno, które pokazuje trzy obiekty Sphere, a ostatni jest rysowany za pomocą DrawMode.LINE: Kod jest zasadniczo taki sam, jak w poprzednim programie, i istnieje tylko jedna znacząca zmiana, w której metoda createbox () jest zastąpiona następującą metodą; private Sphere createsphere(double x, double y, double z, Material m, DrawMode mode) Sphere sphere = new Sphere(150); sphere.setmaterial(m); sphere.setdrawmode(mode); sphere.settranslatex(x); sphere.settranslatey(y); sphere.settranslatez(z); return sphere; 8.4 CULL FACE Kiedy na ekranie pojawia się postać 3D, z naturalnych powodów, nie widać całej postaci - nie widać, co się za nią kryje. To, co widzisz, zależy od tego, gdzie znajduje się kamera. Jak wspomniano w

148 poprzedniej sekcji, kształt 3D jest rysowany jako liczba trójkątów. Trójkąt ma dwa boki w formie zewnętrznej i wewnętrznej, i domyślnie widać zewnętrzną stronę. Na ogół świtają tylko trójkąty, które są widoczne, podczas gdy inne są uporządkowane. Istnieje właściwość o nazwie CullFace, która może przyjmować trzy wartości: 1. CullFace.BACK 2. CullFace.FRONT 3. CullFace.NONE gdzie pierwszy jest domyślny i oznacza, że widzisz tylko trójkąty, w których widać na zewnątrz, podczas gdy drugi oznacza, że możesz zobaczyć tylko wnętrze trójkątów, które zwykle nie są widoczne. Ostatnia opcja oznacza, że możesz zobaczyć oba rodzaje trójkątów, a tym samym narysować wszystkie trójkąty. Program CylinderProgram otwiera następujące okno: który pokazuje cylinder narysowany za pomocą DrawMode.LINE i CullFace.BACK. Kliknięcie cylindra myszą powoduje przejście do CullFace.FRONT: i kliknij raz, aby przejść do CullFace.NONE, a następnie wszystko się powtarza. Kod jest prosty i podobny do powyższych programów, po prostu dodaje się zdarzenie dla myszy:

149 public class CylinderProgram extends Application private CullFace[] culls = CullFace.BACK, CullFace.FRONT, CullFace.NONE ; private int pos = 0; private Cylinder public void start(stage stage) Group root = new Group(cylinder = createcylinder(500, 450, 600), createlight()); root.addeventhandler(mouseevent.mouse_clicked, new EventHandler<MouseEvent>() public void handle(mouseevent e) pos = (pos + 1) % culls.length; cylinder.setcullface(culls[pos]); ; ); Scene scene = new Scene(root, 600, 300, true); scene.setcamera(createcamera()); stage.setscene(scene); stage.settitle("cull"); stage.show(); private Cylinder createcylinder(double x, double y, double z) Cylinder c = new Cylinder(200, 400); c.setmaterial(new PhongMaterial(Color.BLUE)); c.setdrawmode(drawmode.line); c.settranslatex(x); c.settranslatey(y); c.settranslatez(z); return c;

150 8.5 KAMERA I ŚWIATŁO Aparat jest najważniejszym czynnikiem w grafice 3D, ale przewidywanie efektu może być trudne. Na przykład, jeśli utworzysz kulę o promieniu 50 i bez jakiegokolwiek przejścia i utworzysz kamerę w ten sam sposób, pojawi się następujące okno: Odpowiada to punktowi początkowemu układu współrzędnych (0, 0, 0), który jest środkiem kuli. Kamera znajduje się w tej samej pozycji, a jeśli zmieniłeś rozmiar okna, z kształtem nic się nie dzieje. Ponieważ kula nie ma materiału, podana jest wartość domyślna, czyli jasnoszary kolor, a ponieważ nie ma wzmianki o świetle, używane jest źródło światła o tej samej pozycji co kamera. Jeśli przesuniesz kształt 100 w każdym kierunku, środek kuli zostanie przesunięty do (100, 100, 100), a wynik będzie taki, jak pokazano poniżej, gdzie powinieneś zauważyć, że liczba stała się mniejsza, ponieważ pokazano 100 w ekran, a zatem z dala od widza: Pamiętaj, że kamera i światło są nadal włączone (0, 0, 0). Jeśli następnie zmienisz kamerę -200 w kierunku osi Z, zobaczysz, że liczba staje się jeszcze mniejsza, ponieważ kamera przesunęła się bardziej w kierunku widza, a zatem między kamerą a figurą jest więcej: Aparat użyty powyżej ma typ PerspectiveCamera, ale jest też kamera ParallelCamera, a różnica polega na tym, jaki rodzaj projekcji jest używany. Kamera PerspectiveCamera jest najczęściej używana, ponieważ, jak sama nazwa wskazuje, widzi obiekt w perspektywie. Obszar klipu jest przymocowany do kamery, dzięki czemu kształty znajdujące się blisko kamery nie pojawiają się i są takie same dla kształtów znajdujących się daleko. Aby pokazać trochę o działaniu niektórych z wielu opcji i do eksperymentów, przyjrzę się programowi, który nazwałem Options3D:

151 Okno ma obiekt Kula, który znajduje się w lewym górnym rogu i chociaż nie jest widoczny, powierzchnia ma czerwony Materiał. Po prawej stronie znajduje się 10 suwaków, których można użyć do zmiany następujących ustawień: 1. pozycja współrzędnej figury - (x, y, x) 2. promień 3. lokalizacja źródła światła - współrzędna (x, y, x) 4. lokalizacja kamery - współrzędne (x, y, x) Poniżej pokazałem wynik po przesunięciu figury i źródła światła: Kod programu jest następujący: public class Options3D extends Application

152 private Shape3D shape; private PointLight light = new PointLight(); private PerspectiveCamera camera = new PerspectiveCamera(false); private Label tx = new Label(); private Label ty = new Label(); private Label tz = new Label(); private Label lx = new Label(); private Label ly = new Label(); private Label lz = new Label(); private Label cx = new Label(); private Label cy = new Label(); private Label cz = new Label(); private Label ra = new public void start(stage stage) HBox root = new HBox(20, create3d(), create2d()); Scene scene = new Scene(root, 800, 400, true); stage.setscene(scene); stage.settitle("3d Options"); stage.show(); private SubScene create3d() Group root = new Group(shape = new Sphere(100), light); shape.setmaterial(new PhongMaterial(Color.RED)); SubScene subscene = new SubScene(root, 500, 400, true, SceneAntialiasing.BALANCED); subscene.setcamera(camera);

153 return subscene; private SubScene create2d() VBox commands = new VBox(10, createlabel("translation", tx, ty, tz, shape.translatexproperty(), shape.translateyproperty(), shape.translatezproperty()), createlabel("radius", ra, ((Sphere)shape).radiusProperty()), createslider(0, 250, 100, ((Sphere)shape).radiusProperty()), createlabel("light", lx, ly, lz, light.translatexproperty(), light.translateyproperty(), light.translatezproperty()), createslider(-500, 500, 0, light.translatexproperty()), createslider(-500, 500, 0, light.translateyproperty()), createslider(-500, 500, 0, light.translatezproperty()), createlabel("camera", cx, cy, cz, camera.translatexproperty(), camera.translateyproperty(), camera.translatezproperty()), createslider(-500, 500, 0, camera.translatexproperty()), createslider(-500, 500, 0, camera.translateyproperty()), createslider(-500, 500, 0, camera.translatezproperty())); commands.setprefwidth(280); HBox root = new HBox(10, commands); root.setpadding(new Insets(10, 10, 10, 0)); return new SubScene(root, 300, 380); private Pane createlabel(string text, Label r, DoubleProperty p) r.textproperty().bind(p.asstring("%3.1f")); HBox box = new HBox(new Label(text + ": ("), r, new Label(")"));

154 return box; private Pane createlabel(string text, Label x, Label y, Label z, DoubleProperty px, DoubleProperty py, DoubleProperty pz) x.textproperty().bind(px.asstring("%3.1f")); y.textproperty().bind(py.asstring("%3.1f")); z.textproperty().bind(pz.asstring("%3.1f")); HBox box = new HBox(new Label(text + ": ("), x, new Label(", "), y, new Label(", "), z, new Label(")")); return box; private Slider createslider(double min, double max, double val, DoubleProperty property) Slider slider = new Slider(min, max, val); slider.valueproperty().bindbidirectional(property); return slider; public static void main(string[] args) launch(args); W kodzie jest wiele do zapamiętania. Istnieje nie mniej niż 13 zmiennych instancji, z których pierwsza jest odniesieniem do obiektu Sphere. Dwa następne to odpowiednio źródło światła i kamera. Na koniec 10 elementów sterujących Etykieta, które są używane do wyświetlania wartości 10 elementów sterujących Suwak. Jeśli chodzi o metodę start (), nie ma wiele do powiedzenia poza umieszczeniem dwóch składników w HBox. Istnieje jednak wyzwanie. Obiekt Scena zasadniczo nie może wyświetlać zarówno obiektów 3D, jak i 2D - przynajmniej nie jest poprawny. Aby rozwiązać ten problem, wprowadziłem koncepcję SubScene, która pozwala mieć więcej obiektów Scene w oknie. Może być używany do wielu celów (głównie w grafice 3D) i może służyć do podziału okna na część dla obiektów 3D i część dla zwykłych obiektów 2D. Dokładnie tak jest w tym przykładzie. Metoda create3d () tworzy Scenę 3D, w której pierwiastek jest Grupą z Kulą o promieniu 100. Zauważ, że Materiał jest również zdefiniowany. Dla tego katalogu głównego tworzony jest SubScene. W szczególności zwróć uwagę, jak

155 określić, czy ma być stosowane wygładzanie i czy kamera jest dołączona do obiektu SubScene. Dlatego obraz od początku jest wyświetlany na czarno, ponieważ położenie kamery w (0,0,0) odpowiada temu wewnątrz kuli. Metoda create2d () tworzy również obiekt SubScene, ale tym razem jest to zwykła scena 2D. Kod jest częścią, ale dzieje się tak dlatego, że należy utworzyć wiele formantów, a kod nie zawiera w zasadzie niczego nowego. Po utworzeniu poszczególnych elementów sterujących (suwaków) zwróć uwagę, w jaki sposób właściwość value wiąże się z właściwością przez obiekt Shape, obiekt Light lub obiekt Camera. Podobnie 10 elementów sterujących Etykieta jest powiązanych z tymi samymi właściwościami, ale tutaj z jednokierunkowym wiązaniem, ponieważ wartości mają zostać przekonwertowane na ciągi. Zachęcamy do eksperymentowania z programem. ĆWICZENIE 6 Musisz napisać program podobny do programu Options3D, ale zamiast czerwonej Kuli program musi pokazywać niebieskie pole, które jest początkowo (100, 100, 100). Poniższe okno pokazuje rysunek po przesunięciu kilku elementów sterujących suwaka. Zwróć uwagę, że dodano dwa nowe elementy sterujące Slider (i powiązane elementy sterujące Etykieta), dzięki czemu można ustawić szerokość, wysokość i głębokość pola. Najłatwiej jest zacząć od kopii programu Options3D. ĆWICZENIE 7 Napisz inną wersję powyższego programu, ale jeśli obiekt Shape3D tym razem jest zielonym Cylindrem, rozmiar od początku wynosi (50, 100). Musisz mieć możliwość ustawienia zarówno promienia, jak i wysokości, a wynik może wyglądać tak, jak pokazano poniżej:

156 8.6 OSTATNIA UWAGA Jak wspomniano, kształt Shape3D jest konstruowany przy użyciu trójkątów i możliwe jest - ale nie jest łatwe - zdefiniowanie niestandardowych kształtów poprzez zdefiniowanie poszczególnych trójkątów. Nie chcę się do tego zbliżać w tej książce i zajmuje to dużo praktyki, ale konstruowanie niestandardowych kształtów odbywa się za pomocą klasy MeshView. 9 WYKRESY W ostatnim przykładzie książki Java 10 spojrzałem na bibliotekę, w której znajdują się klasy do przedstawiania grafów. Jak wspomniano, istnieje wiele takich bibliotek, a tak naprawdę istnieje wiele przykładów zadań, w których istnieje potrzeba graficznej prezentacji danych, ale w JavaFX jest to łatwe, ponieważ istnieje bezpośrednio interfejs API do tego celu, który ma najczęstsze wykresy. Poniżej przedstawiono wprowadzenie do tego interfejsu API, który zasadniczo obejmuje następujące klasy:

157 Ponadto istnieją inne klasy pomocnicze, w tym klasy reprezentujące dane, które powinny zilustrować poszczególne wykresy, oraz klasy do osi układu współrzędnych. Interfejs API może więc pokazywać 8 różnych wykresów podzielonych na dwie kategorie składające się z PieChart, a następnie pozostałych. Wykres jest graficzną ilustracją sekwencji liczb, a PieChart może pokazywać pojedynczą sekwencję liczb, podczas gdy inne mogą wyświetlać bardziej nazywane serie w układzie współrzędnych xy. W poniższym przykładzie chciałbym zilustrować następujące wykresy: - PieChart - BarChart (dwie wersje) - StackedBarChart Dla wszystkich wykresów jest oczywiście wiele ustawień, a przykład nie pokazuje wszystkich ustawień, a celem jest samodzielne przeszukanie dokumentacji i eksperymentowanie z różnymi ustawieniami, w tym rozszerzenie programu o nowe wykresy przy użyciu innych typów niż trzy wymienione powyżej. Należy od razu wspomnieć, że praca z interfejsem API jest dość łatwa i podobnie niezależna od typu wykresu. Program nazywa się ChartProgram i otwiera następujące okno:

158 Podczas wyświetlania wykresu (wykresu) musisz mieć pewne dane w postaci materiału liczbowego. To, co oznaczają liczby, nie jest tak ważne w obecnym przykładzie, ale liczby można interpretować jako przychód w ciągu 12 miesięcy w roku, a powyższe liczby pokazują przychody za lata Aby eksperymentować z wykresami, liczby są generowane losowo i możesz w dowolnym momencie utworzyć nowe liczby, klikając link Generuj dane: Możesz także zmienić dane, edytując bezpośrednio tabelę. Powyższe okno dialogowe nazywa się ModelView, ale nie chcę tutaj pokazywać kodu, ponieważ nie zawiera on nic nowego. Sama tabela pokazuje obiekty typu Rok, a model danych tabeli nosi nazwę DataModel, a klasy te nie są wyświetlane z tych samych powodów. Po kliknięciu łącza A Chart chart wykonywane są następujące zdarzenia: private void pie(actionevent e) int n = table.getselectionmodel().getselectedindex(); if (n >= 0) Year year = model.getyears().get(n);

159 ObservableList<PieChart.Data> data = FXCollections. observablearraylist(); for (int i = 0; i < DataModel.names.length; ++i) data.add(new PieChart.Data(DataModel.names[i], year.getvalue(i))); new PieView(parent, year.getyear(), data); Metoda określa indeks dla pierwszego wybranego wiersza, a dla tego wiersza tworzona jest lista typu ObservableList <PieChart.Data>. Należy zwrócić uwagę na sposób inicjowania tej listy za pomocą obiektów PieChart.Data, składających się z par, w których pierwszą wartością jest nazwa miesiąca (nad nazwami tablic), a drugą wartością są wartości liczbowe z wybranego wiersza. Następnie pojawi się okno pokazujące PieChart: Wykres używa domyślnie do 8 kolorów, po których są one powtarzane, ale możesz określić więcej kolorów, jeśli chcesz. Kod okna jest następujący: public class PieView public PieView(Window owner, int year, ObservableList<PieChart.Data> data) Stage stage = new Stage(); stage.initowner(owner); stage.initmodality(modality.application_modal) PieChart chart = new PieChart(); chart.settitle("sales for " + year); chart.setlegendside(side.left);

160 chart.setdata(data); addtooltips(chart); StackPane root = new StackPane(chart); Scene scene = new Scene(root); stage.setscene(scene); stage.settitle("a Pie chart"); stage.show(); private void addtooltips(piechart chart) double sum = 0; for (PieChart.Data data : chart.getdata()) sum += data.getpievalue(); for (PieChart.Data data : chart.getdata()) double value = data.getpievalue(); Tooltip tip = new Tooltip(data.getName() + "= " + String.format("%.2f", value) + " (" + String.format("%.2f", value / sum * 100) + "%)"); tip.setstyle("-fx-background-color: yellow; -fx-text-fill: black;"); Tooltip.install(data.getNode(), tip); i nie ma wiele do wyjaśnienia. Najtrudniejsza jest w rzeczywistości ostatnia metoda, która łączy podpowiedzi ze składnikiem PieChart. Należy zwrócić uwagę, jak utworzyć komponent PieChart, a w szczególności jakie właściwości są zdefiniowane. Jeśli w oknie głównym zaznaczysz liczby na trzy lata i klikniesz link Pionowy wykres słupkowy, pojawi się następujące okno:

161 który jest przykładem XYChart. Procedura obsługi zdarzeń jest następująca: private void bar1(actionevent e) ObservableList<Integer> indices = table. getselectionmodel().getselectedindices(); if (indices.size() > 0) ObservableList<XYChart.Series<String, Number>> data = getxydata(indices); new VBarView(parent, data); Najpierw indeksy są określane na wybranych wierszach i na tej podstawie dane wykresu są tworzone jako obiekt typu ObservableList <XHChart.Series <String, Number >>, który ma być używany do wyświetlania wykresu. Obiekt jest określany przy użyciu następującej metody: private ObservableList<XYChart.Series<String, Number>> getxydata(observablelist<integer> indices) ObservableList<XYChart.Series<String, Number>> data = FXCollections.<XYChart.Series<String, Number>>observableArrayList(); for (int i = 0; i < indices.size(); ++i) Year year = model.getyears().get(indices.get(i)); XYChart.Series<String, Number> series = new XYChart.Series<>(); series.setname("" + year.getyear());

162 for (int j = 0; j < DataModel.names.length; ++j) series.getdata().add(new XYChart.Data(DataModel. names[j], year.getvalue(j))); data.add(series); return data; Metoda jest w zasadzie prosta, ale należy zauważyć, w jaki sposób model danych wykresu jest budowany jako obiekty typu XYChart.Series <Ciąg, Liczba>, przy czym każdy obiekt jest parą składającą się z nazwy i wartości. Kod okna wykresu jest następujący: public class VBarView public VBarView(Window owner, ObservableList<XYChart. Series<String,Number>> data) Stage stage = new Stage(); stage.initowner(owner); stage.initmodality(modality.application_modal); CategoryAxis xaxis = new CategoryAxis(); xaxis.setlabel("month"); NumberAxis yaxis = new NumberAxis(); yaxis.setlabel("turnover"); BarChart<String, Number> chart = new BarChart<>(xAxis, yaxis); chart.settitle("sales for several years"); chart.setdata(data); StackPane root = new StackPane(chart); Scene scene = new Scene(root); stage.setscene(scene); stage.settitle("a vertical bar chart"); stage.show(); W tym miejscu należy zwrócić uwagę na sposób tworzenia osi i tworzenia samego obiektu wykresu jako węzła. W rzeczywistości po utworzeniu modelu danych wyświetlenie wykresu jako węzła na wykresie sceny jest proste. Jak wspomniano we wstępie do tego rozdziału, powinieneś sprawdzić, jakie inne opcje istnieją dla BarChart. Program ma dwa inne łącza, z których pierwszy pokazuje poziomy wykres słupkowy, a drugi pokazuje skumulowany wykres słupkowy. Jeśli chodzi o to drugie, odbywa się to w taki sam sposób jak powyżej, a model danych jest tworzony w ten sam sposób, tylko typ to StackedBarChart. Jeśli chodzi o pierwszy, jest to BarChart, a jedyną różnicą jest to, że dwie osie muszą zostać wymienione, co z kolei oznacza, że dane modelu również muszą zostać zamienione. Nie chcę

163 tutaj pokazywać kodu dla dwóch ostatnich wykresów, ponieważ są one w zasadzie identyczne z kodem dla pierwszego wykresu BarChart. 10 PRZYKŁAD KOŃCOWY Ostatnim przykładem tej książki jest program, w którym użytkownik może na przykład wprowadzić wyrażenie funkcji matematycznej w jednej zmiennej rzeczywistej, na przykład y = 3x 2 2x = 1 a program musi być w stanie narysować wykres funkcji. Wymagania są formułowane w sposób ciągły, ponieważ program ma być opracowywany poprzez prototypowanie, ale głównym celem programu jest to, że po narysowaniu wykresu dla jednej lub więcej funkcji w układzie współrzędnych musisz być w stanie skopiować na przykład wykres do schowka i wklej go do edytora tekstu. Możesz więc pomyśleć o programie jako narzędziu, które może być używane przez autora notatek matematycznych lub książek, ale być może nawet przez uczniów w codziennej edukacji matematycznej. Jeśli chodzi o inne funkcje programu, istnieją przede wszystkim wymagania dotyczące elastyczności i łatwości użytkowania, a także rodzajów wykresów, które program może narysować. Niektóre funkcje programu będą odnosić się do ustawień, w tym na przykład ustawienia koloru i grubości linii, a także ustawienia układu współrzędnych będą ważne. Oczekuje się więcej wyzwań w zakresie rozwoju, a największe wyzwanie dotyczy wydajności, ponieważ narysowanie pełnego wykresu może zająć trochę czasu. W szczególności można się spodziewać, że zmieni rozmiar okna, ponieważ oznacza to zarówno korekty (przerysowanie) samego wykresu, jak i układ współrzędnych. Kolejnym wyzwaniem jest zapisanie wykresu do pliku, w którym wykresy nie są zapisywane przez prostą serializację obiektu, ponieważ jest wrażliwy na nowe wersje programu. Jeśli chodzi o to, czym musi być funkcja (wyrażenia prawne), jest to problem, który wcześniej rozwiązałem, więc do analizy i oceny wyrażenia można użyć mniej więcej bezpośredniego kodu, który został wcześniej opracowany i przetestowany. Program jest uważany za pierwszą wersję i należy oczekiwać częstych zmian / rozszerzeń programu przez pewien okres czasu ROZWÓJ Jak wspomniano powyżej, program ma zostać opracowany w formie proptotypu, w którym planowane są następujące iteracje: 1. Prosty prototyp 2. Rysowanie osi x 3. Rysowanie osi y 4. Rysowanie układu współrzędnych 5. Rysowanie funkcji z formalnego 6. Rysowanie fabuły 7. Wdrażanie innych funkcji 8. Opcje wdrażania 9. Stylizacja programu 10. Refaktoryzacja

164 ale może wystąpić kilka i wiele innych iteracji. Rezultatem każdej iteracji jest kompletny program, którego można używać z funkcjami, które są realizowane, a każda iteracja stanowi rozszerzenie poprzedniej iteracji PROSTY PROTOTYP Pierwszy prototyp programu otwiera następujące okno: Funkcje programu są zdefiniowane jako pozycje menu, ale na razie wyświetlane są tylko nagłówki, z których wszystkie są menu, które w pobliżu pierwszego są puste. Pierwsze menu definiuje 5 funkcji (jako przykład), ale nie ma akcji. 8 funkcji ma skróty na pasku narzędzi i są od lewej: 1. Nowy rysunek 2. Otwórz istniejący rysunek 3. Zapisz rysunek 4. Ustawienia układu współrzędnych 5. Wstaw nową funkcję do rysunku 6. Pomniejsz 7. Powiększ 8. Taki sam podział na obie osie Pasek narzędzi zawiera również wyrażenie, a idea polega na tym, że rysowane funkcje powinny być wyświetlane jako łącze na pasku narzędzi, aby można było zmienić dane funkcji, klikając łącze. Krzyż z ramką dookoła powinien ilustrować rysunek narysowany metodą showimage (), a figura składa się z trzech obiektów Shape dołączonych do obiektu Group. Jest to wstępne rozwiązanie, które można później zmienić. Liczba jest wstępnie zakodowana na stałe, ale zmienia się wraz z rozmiarem okna. Po dwukrotnym kliknięciu paska tytułu występuje mały problem z wyświetlaniem okna na pełnym ekranie.

165 Oto problem z prawidłowym rysowaniem okna. Najwyraźniej jest to problem JavaFX (pod Linuksem) i został rozwiązany za pomocą timera, który aktualizuje okno po und selund RYSUNEK OSI Ta iteracja polega na narysowaniu obu osi układu współrzędnych, a zatem dwie kolejne iteracje są łączone w jedną iterację. Zasadniczo istnieje wiele wyzwań związanych z rysowaniem osi układu współrzędnych, między innymi dlatego, że muszą one ładnie zachowywać się, gdy zmienia się rozmiar okna, ale JavaFX ma w klasach API interfejsu Chart reprezentujących osie do układu współrzędnych, a zwłaszcza klasa NumberAxis. Ponieważ ta klasa jest w rzeczywistości ogólna (ma wystarczającą liczbę ustawień), zdecydowano się jej użyć zamiast pisać niestandardowy komponent. Dlatego dwie iteracje osi układu współrzędnych są połączone w jedną iterację. Dodano jedną klasę modelu, która reprezentuje oś: public class AxisModel private DoubleProperty min = new SimpleDoubleProperty(-10); private DoubleProperty max = new SimpleDoubleProperty(10); private IntegerProperty dec = new SimpleIntegerProperty(0); private IntegerProperty ticks = new SimpleIntegerProperty(1); private StringProperty label = new SimpleStringProperty(""); private DoubleProperty cross = new SimpleDoubleProperty(0); private BooleanProperty autoticks = new SimpleBooleanProperty(false); private BooleanProperty showticks = new SimpleBooleanProperty(true); private BooleanProperty showgitter = new SimpleBooleanProperty(false); private BooleanProperty shownumbers = new SimpleBooleanProperty(true); gdzie znaczenie jest następujące: - najniższa wartość na osi - największa wartość na osi - liczba miejsc po przecinku, które zostaną użyte do wyświetlenia wartości liczbowych osi - długość odstępu między punktami osi - etykieta (nazwa) do osi - gdzie oś powinna przecinać drugą oś - jeżeli oś automatycznie określa podział od najmniejszej i największej wartości - jeśli mają być wyświetlane podziały osi

166 - czy powinny pojawić się linie siatki - jeśli liczby mają być wyświetlane Klasa FDrawer ma dwa obiekty dla osi: private AxisModel xmodel = new AxisModel (); private AxisModel ymodel = new AxisModel (); Ponadto klasa jest rozszerzana o dwie klasy wewnętrzne, z których jedna tworzy i rysuje osie, a druga rysuje wykres jako obiekt Ścieżka. Te dwie klasy będą kluczowe i zostaną kilkakrotnie rozszerzone. Po uruchomieniu programu pojawi się następujące okno, w którym funkcja jest wciąż zakodowana: Ważne jest to, że układ współrzędnych i wykres odpowiadają rozmiarowi okna. Nadal brakuje części dotyczącej układu współrzędnych, w tym zastosowania powyższych ustawień, które do tej pory były używane tylko częściowo. Jest to temat następnej iteracji USTAWIENIA DLA SYSTEMU WSPÓŁRZĘDNYCH W tej iteracji zacząłem od małej architektury. W poprzedniej iteracji dodałem pakiet fdrawer.models, który zawiera klasę AxisModel. Rozszerzyłem model o następującą klasę: package fdrawer.models; public class AxesModel private AxisModel xaxis = new AxisModel();

167 private AxisModel yaxis = new AxisModel(); public AxesModel() public AxesModel(AxesModel axes) xaxis = new AxisModel(axes.getXaxis()); yaxis = new AxisModel(axes.getYaxis()); public AxisModel getxaxis() return xaxis; public AxisModel getyaxis() return yaxis; który jest niczym innym jak obudową dwóch osi. Ponadto dodałem następującą klasę package fdrawer.models; public class Model private AxesModel axes = new AxesModel(); public AxesModel getaxes() return axes;

168 że na razie jest to trywialne, ale w przyszłości powinien być głównym modelem programu. Najważniejsze w iteracji jest następujące okno dialogowe, które służy do ustawiania opcji dla osi układu współrzędnych: Jest to stosunkowo złożone okno dialogowe, które zasadniczo składa się z HBox z dwoma węzłami GridPane. Kod jest umieszczony w pakiecie fdrawer.views i składa się z dwóch klas AxesPresenter i AxesView. Oprócz okna dialogowego należy zaktualizować okno główne, aby zastosować również bieżące ustawienia, co jest stosunkowo dużą pracą i odnosi się do dwóch wewnętrznych klas Osie i Wykres, ale potem układ współrzędnych jest również zasadniczo kompletny. Istnieją jednak dwie opcje, które nie zostały zaimplementowane: 1. Liczba miejsc po przecinku, ponieważ sam JavaFX decyduje o liczbie miejsc po przecinku, a ponieważ wydaje się to rozsądne, funkcję można później usunąć. 2. Oś automatycznego podziału. Ta funkcja jest zaimplementowana przez klasę NumberAxis, ale ponieważ nie zawsze wydaje się to właściwe, implementacja tej funkcji jest odłożona na później i być może należy ją usunąć. Aby otworzyć okno dialogowe, przycisk obsługi zdarzeń został dodany do przycisku na pasku narzędzi, ale ten sam moduł obsługi zdarzeń zostanie później dodany do menu WYKONYWANIE FUNKCJI FORMALNYCH

169 Tę iterację można uznać za najważniejszą iterację, a po jej zakończeniu masz w zasadzie gotową szufladę, w której możesz narysować typową funkcję matematyczną. Punktem wyjścia jest rozszerzenie modelu o dwie klasy Wyrażenie i Tokeny, które reprezentują wyrażenie matematyczne. Klasy są zaimplementowane w końcowym przykładzie książki Java 3 i obsługują wyrażenie matematyczne, w którym można użyć czterech operacji arytmetycznych i następujących funkcji: 0.25x x 2 1.5x 2 Funkcja zależy od jednej zmiennej, na przykład od funkcji można wprowadzić jako 0,25 * pow (x, 3) + 0,75 * sqr (x) - 1,5 * x - 2 Klasy Expression i Tokens z Java 3 mogą być prawie niezmienione, ale obsługują użycie wielu zmiennych, a ponieważ nie powinna być opcją w tym programie, klasy są modyfikowane, dzięki czemu można używać tylko jednej zmiennej o nazwie x. W celu utworzenia funkcji program jest rozszerzany o następujące okno dialogowe (klasy ExpressionView og ExpressionPresenter): gdzie możesz wprowadzić funkcję (powyżej prostej funkcji liniowej). Możesz także określić kolor w oparciu o kilka stałych wartości iw ten sam sposób możesz określić grubość linii (również w oparciu o kilka stałych wartości). Poniżej znajduje się okno programu, w którym rysowane są dwie funkcje:

170 Ponieważ musisz być w stanie narysować więcej funkcji w tym samym układzie współrzędnych (w praktyce kilka), pasek narzędzi jest zmieniany tam, gdzie dodaje się kombinację pokazującą funkcje wykresu. Dodatkowo dodano przycisk, który otwiera powyższe okno dialogowe, ale dla funkcji wybranej w combobox. Następnie program może wyświetlać wykresy dla jednej lub więcej funkcji w układzie współrzędnych ARCHITEKTURA PROGRAMU Wynikiem powyższego jest to, że bardzo duża część kodu programu znajduje się w klasie FDrawer, a dalszy rozwój, przy niezmienionej architekturze, oznacza, że klasa staje się jeszcze większa i prawdopodobnie nawet znacznie większa. Oznacza to, że klasa staje się nieodwracalna, a częściowo dlatego, że program nie przestrzega wzorca MVP, a oba mogą utrudnić utrzymanie programu w przyszłości. Właśnie dlatego wdrożyłem dodatkową iterację, aby rozbić klasę FDrawer na mniejsze klasy i tym samym zapewnić lepszą architekturę programu. Jest to zatem iteracja, która nie generuje bezpośrednio pędu w projekcie. Oczywiście lepiej byłoby wziąć to pod uwagę wcześniej w projekcie i od samego początku zaplanować lepszą architekturę programu. Jednak nie zawsze jest to takie łatwe, a zwłaszcza jeśli pracujesz nad programem, w którym cel nie jest całkowicie jasny, a wymagania są ustalane na bieżąco. Może to oznaczać, że początkowo skupiasz się na wyświetlaniu czegoś na ekranie, więc przyszłym użytkownikom można przedstawić częściowe wyniki w celu wyjaśnienia ostatecznych wymagań. Rezultatem często będzie, tak jak w niniejszej sprawie, program o niewłaściwej architekturze, a następnie powinieneś zatrzymać i wykonać pierwsze refaktoryzowanie. W tym przypadku do fdrawer.views dodano następujące klasy: - Osie, podobnie jak odpowiadająca im klasa wewnętrzna w FDrawer, zostały przeniesione do własnej klasy widoku

171 - Wykres, podobnie jak odpowiadająca mu klasa wewnętrzna w FDrawer, został przeniesiony do własnej klasy widoku - MainMenu, które jest kodem menu, które zostało przeniesione do własnej klasy przede wszystkim w celu zwiększenia przejrzystości - MainView, który jest głównym oknem - MainPresenter, czyli prezenter dla głównego okna Klasa FDrawer została odpowiednio zmieniona, a następnie jest klasą, która robi tylko instancję Modelu i MainPresenter. Wreszcie do fdrawer.views dodano klasę ViewTools, która zawiera tylko metody statyczne RYSOWANIE PLANU Za pomocą wykresu chcę zrozumieć funkcję określoną przez kilka punktów. Program musi następnie wyświetlić wykres funkcji jako odpowiednią liczbę punktów w układzie współrzędnych i ewentualnie powiązany z liniami. Układ współrzędnych musi więc być w stanie wyświetlać dwa typy wykresów, które są albo wspólną funkcją jednej rzeczywistej zmiennej, jak już zaimplementowano, i grafem wykresu zaimplementowanym w tej iteracji. Do tej pory funkcja była reprezentowana przez klasę modeli ExpressionModel, ale w celu obsługi obu typów funkcji w ten sam sposób model jest rozszerzany w następujący sposób: Klasa FunctionModel musi przypisać identyfikator do poszczególnych funkcji i będzie śledzić tylko kolor i grubość linii: public abstract class FunctionModel private static int lastid = 0; private int id; private ObjectProperty<ColorWrapper> color = new SimpleObjectProperty(Colors.list.get(0)); private ObjectProperty<Double> stroke = new SimpleObjectProperty(1.0); public FunctionModel() id = ++lastid;

172 Należy zauważyć, że klasa jest zdefiniowana jako abstrakcyjna, więc nie można jej utworzyć. Klasa ExpressionModel jest wtedy trywialna public class ExpressionModel extends FunctionModel private Expression expression; ale powinien implementować equals () i tostring (). Klasa PlotModel wymaga nieco więcej, ponieważ do wykresu wykresu dołączonych jest więcej ustawień: public class PlotModel extends FunctionModel private ObservableList<PlotPoint> points = FXCollections.observableArrayList(); private StringProperty name = new SimpleStringProperty(""); private StringProperty plottype = new SimpleStringProperty(PlotType.types.get(0)); private ObjectProperty<Double> plotsize = new SimpleObjectProperty(5.0); private BooleanProperty drawline = new SimpleBooleanProperty(false); private StringProperty linetype = new SimpleStringProperty(LineType.types.get(0)); To rozszerzenie oznacza drobne zmiany w innym miejscu w kodzie i, między innymi, model został zmieniony na: package fdrawer.models; public class Model private AxesModel axes = new AxesModel(); private ObservableList<FunctionModel> functions = FXCollections.observableArrayList(); public AxesModel getaxes() return axes; public ObservableList<FunctionModel> getfunctions()

173 return functions; W iteracji dodano następujące okno dialogowe służące do zdefiniowania wykresu wykresu: W tabeli po lewej stronie możesz wprowadzić punkty, a dwa przyciski pod tabelą służą do utworzenia punktu i usunięcia punktu, w którym przycisk Usuń wiersz usuwa wybrany wiersz. Do modelu dodano następujący typ: public class PlotPoint private DoubleProperty x = new SimpleDoubleProperty(0); private DoubleProperty y = new SimpleDoubleProperty(0); który reprezentuje punkt na wykresie wykresu. Po prawej stronie możesz zdefiniować opcje dla wykresu wydruku. Wykres musi mieć nazwę, której można użyć do identyfikacji wykresu w interfejsie użytkownika. Typ PlotType służy do określenia geometrii punktu: public class PlotType public static ObservableList<String> types = FXCollections.observableArrayList(); static types.addall("circle", "Square", "Rhombus", "Cross");

174 podczas gdy następne dwa kombinatory definiują rozmiar i kolor punktu. Poniższe pole wyboru wskazuje, czy punkty powinny być połączone liniami i, w stosownych przypadkach, istnieją dwie opcje: public class LineType public static ObservableList<String> types = FXCollections.observableArrayList(); static types.addall("line", "Curve"); Aby zaimplementować okno dialogowe, warstwa widoku jest rozszerzana o dwie klasy: PlotView i PlotPresenter. Dodano nowy przycisk do paska narzędzi, ale w przeciwnym razie największą zmianą jest rozszerzenie klasy Wykres, dzięki czemu można teraz rysować wykresy wykresów REFAKTOROWANIE DIALOGU WYRAŻENIA Program ma kilka braków w rysowaniu wykresów, ponieważ wykres klasy nie uwzględnia domeny dla funkcji. Może to powodować nieprawidłowe wykresy lub wykresy, które wydają się nieprawidłowe. Dlatego wprowadziłem iterację wyłącznie w celu rozwiązania tego problemu. Zasadniczo ważne jest określenie domeny funkcji, dlatego okno dialogowe ExpressionView jest rozszerzone do: Można teraz dodać kilka przedziałów, w których suma tych przedziałów jest domeną. Jeśli nie wprowadzisz żadnych interwałów, domena będzie postrzegana jako rzeczywista oś. Dla każdego interwału możesz wprowadzić 4 wartości:

175 1. A jest lewym punktem końcowym. Jeśli wartość jest pusta, lewy punkt końcowy ma wartość minus nieskończoność. 2. Zacznij oznaczać punkt początkowy. Jeśli jest pusty, żaden znak nie jest wyświetlany, ale w przeciwnym razie może być Zamknij, co oznacza wypełnione koło lub Otwarte, co oznacza otwarte koło. 3. B jest właściwym punktem końcowym. Jeśli wartość jest pusta, prawy punkt końcowy jest nieskończony. 4. Koniec wskazujący znak punktu końcowego. Jeśli jest pusty, żaden znak nie jest wyświetlany, ale w przeciwnym razie może być Zamknij, co oznacza wypełnione koło lub Otwarte, co oznacza otwarte koło. Rozszerzenie oznacza pewne zmiany w modelu, w którym istnieje klasa modelu dla przedziału: public class Interval private DoubleProperty a = new SimpleDoubleProperty(Double.NEGATIVE_INFINITY); private StringProperty lower = new SimpleStringProperty(""); private DoubleProperty b = new SimpleDoubleProperty(Double. POSITIVE_INFINITY); private StringProperty upper = new SimpleStringProperty(""); i zmieniono także klasę ExpressionModel public class ExpressionModel extends FunctionModel private Expression expression; private ObservableList<Interval> intervals = FXCollections.observableArrayList(); Najbardziej wszechstronnymi zmianami w tej iteracji są jednak Wykres klasy, ponieważ musi on uwzględniać fakt, że funkcja może mieć teraz domenę i ostatecznie można narysować punkty końcowe. Wreszcie klasa powinna zachowywać się ładnie, jeśli narysujesz wykres, który nie jest zdefiniowany na całej osi rzeczywistej, ale nie określi domeny. W rezultacie klasa stopniowo stała się stosunkowo złożona, a klasa może ostatecznie zostać włączona do refaktoryzacji WDROŻENIE MENU FUNKCJI Następna iteracja obejmuje, zgodnie z planem, wdrożenie wszystkich funkcji w menu. Jest ich kilka, a niektóre są nawet dość złożone, więc tutaj podzieliłem iterację na wiele iteracji z jedną iteracją dla każdego menu. W tej iteracji przyjrzę się menu Funkcje, które powinno zawierać następujące elementy menu: - Utwórz nową funkcję - Utwórz / zmodyfikuj tanget - Twórz / modyfikuj linie pionowe

176 - Wstaw / zmodyfikuj kreskowanie - Utwórz serię punktów - Zmień funkcje - Zmodyfikuj serie punktów Klasa MainMenu jest odpowiednio aktualizowana. Klasa ma zmienną instancji Map <String, MenuItem> items = new HashMap (); z widocznością pakietu, gdzie każdy element menu jest identyfikowany za pomocą klucza, a mapa służy do umożliwienia w MainPresenter przypisania procedur obsługi zdarzeń do poszczególnych elementów menu. Pierwszy i trzeci ostatni element menu odpowiada funkcjom (utwórz nową funkcję i utwórz wykres), które są już zaimplementowane jako przyciski na pasku narzędzi, więc nic poza klasą MainPresenter nie musi kojarzyć właściwej procedury obsługi zdarzeń. Punkt menu Utwórz / zmodyfikuj styczną jest nową funkcją, w której powinno być możliwe narysowanie stycznej do funkcji w danym punkcie. Jeśli wybierzesz funkcję, pojawi się następujące okno, które u dołu pokazuje podsumowanie funkcji dodanych do rysunku, a jeśli wybierzesz jedną z tych funkcji, możesz również dodać punkty dla stycznych w górnej tabeli, a także jako parametry koloru i grubości stycznej: Po kliknięciu OK rysowane są odpowiednie styczne. Funkcja wymaga rozszerzenia klasy ExpressionModel, ponieważ funkcja może teraz mieć powiązane styczne: public class ExpressionModel extends FunctionModel private Expression expression; private ObservableList<Interval> intervals = FXCollections.observableArrayList(); private ObservableList<LineModel> tangents = FXCollections.observableArrayList();

177 Tutaj LineModel jest prostą klasą modelu, która rozszerza klasę FunctionModel o jedną właściwość dla punktu stopy stycznej. Aby narysować styczną, klasa Expression jest rozszerzana za pomocą metody, która jako wartość graniczna może obliczyć iloraz różniczkowy w punkcie. Element menu Utwórz / modyfikuj linie pionowe powinien być używany do wstawiania linii pionowych do rysunku, na przykład pionowych asymptot. Funkcja otwiera następujące okno: gdzie można dodać punkty (wartości x), w których linia pionowa musi przecinać oś x. Funkcja jest stosunkowo prosta, ale wymaga rozszerzenia modelu: public class Model private AxesModel axes = new AxesModel(); private ObservableList<FunctionModel> functions = FXCollections.observableArrayList(); private ObservableList<LineModel> lines = FXCollections.observableArrayList(); i podobnie, wykres klasy musi zostać zaktualizowany, aby rysował linie pionowe. Następna funkcja Wstaw / Zmień kreskowanie pozwala wstawić kreskowanie poniżej lub powyżej funkcji, a jeśli wybierzesz element menu, pojawi się następujące okno: gdzie wybrać funkcję. Jeśli klikniesz OK, pojawi się okno:

178 Tutaj możesz wstawić ograniczenia pionowe i poziome w obszarze do kreskowania, a także wybrać kolor kreskowania, a także określić, czy powinno to być standardowe wypełnienie czy wzór. Funkcja nie jest łatwa do zaimplementowania, a wykres klasy należy znacznie rozszerzyć. Ponadto klasa modeli ExpressionModel musi zostać rozszerzona o nową zmienną: public class ExpressionModel extends FunctionModel private Expression expression; private HatchModel hatching; private ObservableList<Interval> intervals = FXCollections.observableArrayList(); private ObservableList<LineModel> tangents = FXCollections.observableArrayList(); Funkcja może ewentualnie wykluć niezamierzony obszar, jeśli ograniczenia są ustawione na xiy oraz jeśli wykres funkcji przecina wybrany obszar. Powodem jest to, że obszar kreskowania jest zdefiniowany jako ścieżka i należy go zmienić w przyszłej wersji programu. Dwie ostatnie funkcje menu Funkcje są w zasadzie zaimplementowane, ponieważ odpowiadają edycji funkcji lub edycji już utworzonego wykresu. Jedyne, czego brakuje, to sposób wyboru funkcji lub wykresu do edycji, i robi się to w taki sam sposób jak powyżej z kreskowaniem. W związku z tą iteracją rozszerzono funkcję rysowania wykresu, więc typ linii łączącej punkty może być również linią regresji WDROŻENIE MENU ZOOM Menu musi mieć następujące funkcje: - Pomniejsz - Zbliżenie - Podziel za osią x - Podziel za osią y

PHP 5 język obiektowy

PHP 5 język obiektowy PHP 5 język obiektowy Wprowadzenie Klasa w PHP jest traktowana jak zbiór, rodzaj różnych typów danych. Stanowi przepis jak stworzyć konkretne obiekty (instancje klasy), jest definicją obiektów. Klasa reprezentuje

Bardziej szczegółowo

JAVA W SUPER EXPRESOWEJ PIGUŁCE

JAVA W SUPER EXPRESOWEJ PIGUŁCE JAVA W SUPER EXPRESOWEJ PIGUŁCE Obiekt Obiekty programowe to zbiór własności i zachowań (zmiennych i metod). Podobnie jak w świecie rzeczywistym obiekty posiadają swój stan i zachowanie. Komunikat Wszystkie

Bardziej szczegółowo

JavaFX. Zaawansowane technologie Javy 2019

JavaFX. Zaawansowane technologie Javy 2019 JavaFX Zaawansowane technologie Javy 2019 Historia postania JavaFX Początkowym pakietem do tworzenia aplikacji GUI w Javie był pakiet AWT. Niedługo po wprowadzeniu został on zastąpiony pakietem Swing,

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Laboratorium z przedmiotu Programowanie obiektowe - zestaw 02 Cel zajęć. Celem zajęć jest zapoznanie z praktycznymi aspektami projektowania oraz implementacji klas i obiektów z wykorzystaniem dziedziczenia.

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Laboratorium z przedmiotu - zestaw 02 Cel zajęć. Celem zajęć jest zapoznanie z praktycznymi aspektami projektowania oraz implementacji klas i obiektów z wykorzystaniem dziedziczenia. Wprowadzenie teoretyczne.

Bardziej szczegółowo

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych Obiekty reprezentują pewne pojęcia, przedmioty, elementy rzeczywistości. Obiekty udostępniają swoje usługi: metody operacje,

Bardziej szczegółowo

Rozdział 4 KLASY, OBIEKTY, METODY

Rozdział 4 KLASY, OBIEKTY, METODY Rozdział 4 KLASY, OBIEKTY, METODY Java jest językiem w pełni zorientowanym obiektowo. Wszystkie elementy opisujące dane, za wyjątkiem zmiennych prostych są obiektami. Sam program też jest obiektem pewnej

Bardziej szczegółowo

D:\DYDAKTYKA\ZAI_BIS\_Ćwiczenia_wzorce\04\04_poprawiony.doc 2009-lis-23, 17:44

D:\DYDAKTYKA\ZAI_BIS\_Ćwiczenia_wzorce\04\04_poprawiony.doc 2009-lis-23, 17:44 Zaawansowane aplikacje internetowe EJB 1 Rozróżniamy dwa rodzaje beanów sesyjnych: Stateless Statefull Celem tego laboratorium jest zbadanie różnic funkcjonalnych tych dwóch rodzajów beanów. Poszczególne

Bardziej szczegółowo

Dokumentacja do API Javy.

Dokumentacja do API Javy. Dokumentacja do API Javy http://java.sun.com/j2se/1.5.0/docs/api/ Klasy i obiekty Klasa jest to struktura zawierająca dane (pola), oraz funkcje operujące na tych danych (metody). Klasa jest rodzajem szablonu

Bardziej szczegółowo

dr inż. Piotr Czapiewski Tworzenie aplikacji w języku Java Laboratorium 1

dr inż. Piotr Czapiewski Tworzenie aplikacji w języku Java Laboratorium 1 Ćwiczenie 1 Uruchamianie programu w Netbeans Uruchom środowisko Netbeans. Stwórz nowy projekt typu Java Application. Nadaj projektowi nazwę HelloWorld (Project Name), zwróć uwagę na folder, w którym zostanie

Bardziej szczegółowo

Programowanie Obiektowe GUI

Programowanie Obiektowe GUI Programowanie Obiektowe GUI Swing Celem ćwiczenia jest ilustracja wizualnego tworzenia graficznego interfejsu użytkownika opartego o bibliotekę Swing w środowisku NetBeans. Ponadto, ćwiczenie ma na celu

Bardziej szczegółowo

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu. Zrozumienie funkcji danych statycznych jest podstawą programowania obiektowego. W niniejszym artykule opiszę zasadę tworzenia klas statycznych w C#. Oprócz tego dowiesz się czym są statyczne pola i metody

Bardziej szczegółowo

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem.

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem. WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM NetBeans Wykonał: Jacek Ventzke informatyka sem. VI 1. Uruchamiamy program NetBeans (tu wersja 6.8 ) 2. Tworzymy

Bardziej szczegółowo

TEMAT : KLASY DZIEDZICZENIE

TEMAT : KLASY DZIEDZICZENIE TEMAT : KLASY DZIEDZICZENIE Wprowadzenie do dziedziczenia w języku C++ Język C++ możliwa tworzenie nowej klasy (nazywanej klasą pochodną) w oparciu o pewną wcześniej zdefiniowaną klasę (nazywaną klasą

Bardziej szczegółowo

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej Programowanie obiektowe Interfejsy Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej pawel.rogalinski pwr.wroc.pl Interfejsy Autor: Paweł Rogaliński Instytut Informatyki,

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe IV. Interfejsy i klasy wewnętrzne Małgorzata Prolejko OBI JA16Z03 Plan Właściwości interfejsów. Interfejsy a klasy abstrakcyjne. Klonowanie obiektów. Klasy wewnętrzne. Dostęp do

Bardziej szczegółowo

Zaawansowane aplikacje internetowe

Zaawansowane aplikacje internetowe Zaawansowane aplikacje internetowe EJB 1 Rozróżniamy dwa rodzaje beanów sesyjnych: Stateless Statefull Celem tego laboratorium jest zbadanie różnic funkcjonalnych tych dwóch rodzajów beanów. Poszczególne

Bardziej szczegółowo

Metody Metody, parametry, zwracanie wartości

Metody Metody, parametry, zwracanie wartości Materiał pomocniczy do kursu Podstawy programowania Autor: Grzegorz Góralski ggoralski.com Metody Metody, parametry, zwracanie wartości Metody - co to jest i po co? Metoda to wydzielona część klasy, mająca

Bardziej szczegółowo

Java: otwórz okienko. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Java: otwórz okienko. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak Java: otwórz okienko Programowanie w językach wysokiego poziomu mgr inż. Anna Wawszczak PLAN WYKŁADU klasy wewnętrzne, lokalne i anonimowe biblioteka AWT zestaw Swing JFrame JPanel komponenty obsługa zdarzeń

Bardziej szczegółowo

Programowanie w Javie

Programowanie w Javie Programowanie w Javie Andrzej Czajkowski Lista nr 0 Debugger w Javie Celem ćwiczenia jest poznanie podstawowych funkcji narzędzia debugera (odpluskwiacz) w środowisku Eclipse. Po ukończeniu ćwiczenia student

Bardziej szczegółowo

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Polimorfizm, metody wirtualne i klasy abstrakcyjne Programowanie obiektowe Polimorfizm, metody wirtualne i klasy abstrakcyjne Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej pawel.rogalinski pwr.wroc.pl Polimorfizm,

Bardziej szczegółowo

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object Java: kilka brakujących szczegółów i uniwersalna nadklasa Object Programowanie w językach wysokiego poziomu mgr inż. Anna Wawszczak PLAN WYKŁADU Konstrukcja obiektów Niszczenie obiektów i zwalnianie zasobów

Bardziej szczegółowo

Politechnika Poznańska Wydział Budowy Maszyn i Zarządzania

Politechnika Poznańska Wydział Budowy Maszyn i Zarządzania 1) Cel ćwiczenia Celem ćwiczenia jest zapoznanie się z podstawowymi elementami obiektowymi systemu Windows wykorzystując Visual Studio 2008 takimi jak: przyciski, pola tekstowe, okna pobierania danych

Bardziej szczegółowo

Platformy Programistyczne Podstawy języka Java

Platformy Programistyczne Podstawy języka Java Platformy Programistyczne Podstawy języka Java Agata Migalska 6 maja 2014 Plan wykładu 1 Sztuka wysławiania się w języku Java 2 Cały świat jest obiektem 3 Kolekcje 4 Zmienne i metody statyczne 5 Słowo

Bardziej szczegółowo

JavaFX. Programowanie Obiektowe Mateusz Cicheński

JavaFX. Programowanie Obiektowe Mateusz Cicheński JavaFX Programowanie Obiektowe Mateusz Cicheński Is JavaFX replacing Swing as the new client UI library for Java SE? Yes. http://www.oracle.com/technetwork/java/javafx/overview/faq-1446554.html Zastosowania

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Wykład 2: Wstęp do języka Java 3/4/2013 S.Deniziak: Programowanie obiektowe - Java 1 Cechy języka Java Wszystko jest obiektem Nie ma zmiennych globalnych Nie ma funkcji globalnych

Bardziej szczegółowo

public enum Environment { Development("Deweloperskie"), Test("Testowe"), Production("Produkcyjne"); private String name;

public enum Environment { Development(Deweloperskie), Test(Testowe), Production(Produkcyjne); private String name; 1 Ćwiczenie Zbudować okienko logowania do systemu. Okienko zawiera: (1) listę wyboru z możliwością wyboru środowiska: produkcyjnego, testowego, deweloperskiego, (2) listy wyboru identyfikatora użytkownika

Bardziej szczegółowo

Zaawansowane aplikacje WWW - laboratorium

Zaawansowane aplikacje WWW - laboratorium Zaawansowane aplikacje WWW - laboratorium Przetwarzanie XML (część 2) Celem ćwiczenia jest przygotowanie aplikacji, która umożliwi odczyt i przetwarzanie pliku z zawartością XML. Aplikacja, napisana w

Bardziej szczegółowo

Klasy. dr Anna Łazińska, WMiI UŁ Podstawy języka Java 1 / 13

Klasy. dr Anna Łazińska, WMiI UŁ Podstawy języka Java   1 / 13 Klasy Klasa to grupa obiektów, które mają wspólne właściwości, a obiekt jest instancją klasy. Klasa w języku Java może zawierać: pola - reprezentują stan obiektu (odniesienie do pola z kropką), methods

Bardziej szczegółowo

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h] 1. Typy. Java jest językiem programowania z silnym systemem kontroli typów. To oznacza, że każda zmienna, atrybut czy parametr ma zadeklarowany typ. Kompilator wylicza typy wszystkich wyrażeń w programie

Bardziej szczegółowo

Wprowadzenie do projektu QualitySpy

Wprowadzenie do projektu QualitySpy Wprowadzenie do projektu QualitySpy Na podstawie instrukcji implementacji prostej funkcjonalności. 1. Wstęp Celem tego poradnika jest wprowadzić programistę do projektu QualitySpy. Będziemy implementować

Bardziej szczegółowo

Laboratorium 1. Wzorce oprogramowania lab1, Zofia Kruczkiewicz

Laboratorium 1. Wzorce oprogramowania lab1, Zofia Kruczkiewicz Aplikacja internetowa zbudowana w oparciu o środowisko Visual Web Java Server Faces. Zarządzanie obiektami typu SesionBeans, RequestBeen i ApplicationBeans, Laboratorium 1 Wzorce oprogramowania lab1, Okres

Bardziej szczegółowo

Przypomnienie o klasach i obiektach

Przypomnienie o klasach i obiektach Wykład 14 Programowanie obiektowe ciąg dalszy, str 1 Przypomnienie o klasach i obiektach -5 należydo int 314 należy do double false należy do boolean {27, 314,-15 należy do double[] wartość należy do typ

Bardziej szczegółowo

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016 Wykład 1 3 marca 2016 Słowa kluczowe języka Java abstract, break, case, catch, class, const, continue, default, do, else, enum, extends, final, finally, for, goto, if, implements, import, instanceof, interface,

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Wykład 7 Marcin Młotkowski 8 kwietnia 2015 Plan wykładu Z życia programisty, część 1 1 Z życia programisty, część 1 2 3 Z życia programisty, część 2 Model View Controller MVC w

Bardziej szczegółowo

KLASY, INTERFEJSY, ITP

KLASY, INTERFEJSY, ITP KLASY, INTERFEJSY, ITP ZAGADNIENIA: Klasy, modyfkatory dostępu, pakiety. Zmienne i metody statyczne. Klasy abstrakcyjne, dziedziczenie. Interfejsy. Komentarze i javadoc, http://th-www.if.uj.edu.pl/zfs/ciesla/

Bardziej szczegółowo

Enkapsulacja, dziedziczenie, polimorfizm

Enkapsulacja, dziedziczenie, polimorfizm 17 grudnia 2008 Spis treści I Enkapsulacja 1 Enkapsulacja 2 Spis treści II Enkapsulacja 3 Czym jest interfejs Jak definuje się interfejs? Rozszerzanie interfejsu Implementacja interfejsu Częściowa implementacja

Bardziej szczegółowo

Podstawy obiektowości

Podstawy obiektowości Podstawy obiektowości Zad. Zamówienie 1. Napisać program do obsługi zamówień. Program powinien składać się z dwóch klas: Zamowienie oraz Pozycja, przy czym każde zamówienie zawierać może jedną lub więcej

Bardziej szczegółowo

Kurs programowania. Wykład 8. Wojciech Macyna. 10 maj 2017

Kurs programowania. Wykład 8. Wojciech Macyna. 10 maj 2017 Wykład 8 10 maj 2017 Współbieżność Watki w JAVA-ie Współbieżność może być realizowana na poziomie systemu operacyjnego (procesy) lub na poziomie aplikacji (watki). W JAVA-ie powszechnie stosuje się watki.

Bardziej szczegółowo

Technologie i usługi internetowe cz. 2

Technologie i usługi internetowe cz. 2 Technologie i usługi internetowe cz. 2 Katedra Analizy Nieliniowej, WMiI UŁ Łódź, 15 luty 2014 r. 1 Programowanie obiektowe Programowanie obiektowe (z ang. object-oriented programming), to paradygmat programowania,

Bardziej szczegółowo

Projektowanie aplikacji internetowych laboratorium

Projektowanie aplikacji internetowych laboratorium Projektowanie aplikacji internetowych laboratorium Programowanie w języku Java Do realizacji projektu potrzebne jest zintegrowane środowisko programistyczne NetBeans 7 (zrzuty ekranów pochodzą z wersji

Bardziej szczegółowo

UML a kod w C++ i Javie. Przypadki użycia. Diagramy klas. Klasy użytkowników i wykorzystywane funkcje. Związki pomiędzy przypadkami.

UML a kod w C++ i Javie. Przypadki użycia. Diagramy klas. Klasy użytkowników i wykorzystywane funkcje. Związki pomiędzy przypadkami. UML a kod w C++ i Javie Projektowanie oprogramowania Dokumentowanie oprogramowania Diagramy przypadków użycia Przewoznik Zarzadzanie pojazdami Optymalizacja Uzytkownik Wydawanie opinii Zarzadzanie uzytkownikami

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Laboratorium 1. Wstęp do programowania w języku Java. Narzędzia 1. Aby móc tworzyć programy w języku Java, potrzebny jest zestaw narzędzi Java Development Kit, który można ściągnąć

Bardziej szczegółowo

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016 Wykład 2 17 marca 2016 Dziedziczenie Klasy bazowe i potomne Dziedziczenie jest łatwym sposobem rozwijania oprogramowania. Majac klasę bazowa możemy ja uszczegółowić (dodać nowe pola i metody) nie przepisujac

Bardziej szczegółowo

1 Intefejsy graczne. 1.1 Okienka. 1.2 Komponenty

1 Intefejsy graczne. 1.1 Okienka. 1.2 Komponenty 1 Intefejsy graczne JavaFX pocz tkowo zewn trzna biblioteka j zyka Java obecnie jest zalecanym rozwi zaniem tworzenia interfejsów u»ytkownika w nowych programach. 1.1 Okienka Klasa Application jest gªówn

Bardziej szczegółowo

Klasy i obiekty cz II

Klasy i obiekty cz II Materiał pomocniczy do kursu Podstawy programowania Autor: Grzegorz Góralski ggoralski.com Klasy i obiekty cz II Hermetyzacja, mutatory, akcesory, ArrayList Rozwijamy aplikację Chcemy, aby obiekty klasy

Bardziej szczegółowo

Aplikacje w środowisku Java

Aplikacje w środowisku Java Aplikacje w środowisku Java Materiały do zajęć laboratoryjnych Klasy i obiekty - dziedziczenie mgr inż. Kamil Zieliński Katolicki Uniwersytet Lubelski Jana Pawła II 2018/2019 W ramach poprzedniego laboratorium

Bardziej szczegółowo

Wykład 5 Okna MDI i SDI, dziedziczenie

Wykład 5 Okna MDI i SDI, dziedziczenie Wykład 5 Okna MDI i SDI, dziedziczenie Autor: Zofia Kruczkiewicz Zagadnienia 1. Aplikacja wielookienkowa. Zakładanie projektu typu CLR Windows Forms 1.1. Aplikacja typu MDI 1.2. Aplikacja typu SDI 2. Dziedziczenie

Bardziej szczegółowo

Podstawy Języka Java

Podstawy Języka Java Podstawy Języka Java Programowanie obiektowe Programowanie obiektowe (z ang. object-oriented programming), to paradygmat programowania, w którym programy definiuje się za pomocą obiektów elementów łączących

Bardziej szczegółowo

PWSG Ćwiczenia 12. Wszystkie ukończone zadania należy wysłać na adres: lub

PWSG Ćwiczenia 12. Wszystkie ukończone zadania należy wysłać na adres: lub PWSG Ćwiczenia 12 Wszystkie ukończone zadania należy wysłać na adres: sara.m.jurczyk@gmail.com lub sarajurczyk@kul.lublin.pl Zadanie 1: Różnica między zwykłymi polami/metodami, a polami/metodami static

Bardziej szczegółowo

1 Wątki 1. 2 Tworzenie wątków 1. 3 Synchronizacja 3. 4 Dodatki 3. 5 Algorytmy sortowania 4

1 Wątki 1. 2 Tworzenie wątków 1. 3 Synchronizacja 3. 4 Dodatki 3. 5 Algorytmy sortowania 4 Spis treści 1 Wątki 1 2 Tworzenie wątków 1 3 Synchronizacja 3 4 Dodatki 3 5 Algorytmy sortowania 4 6 Klasa Runnable 4 Temat: Wątki Czym są wątki. Grafika. Proste animacje. Małe podsumowanie materiału.

Bardziej szczegółowo

Typy sparametryzowane

Typy sparametryzowane Typy sparametryzowane Streszczenie Celem wykładu jest zaprezentowanie typów sparametryzowanych. Czas wykładu 90 minut. Istnieją algorytmy, których zasada działania nie zależy od typu danych wejściowych.

Bardziej szczegółowo

Definiowanie własnych klas

Definiowanie własnych klas Programowanie obiektowe Definiowanie własnych klas Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej pawel.rogalinski @ pwr.wroc.pl Definiowanie własnych klas Autor:

Bardziej szczegółowo

Wstęp - Prosta aplikacja internetowa w technologii Java EE 5. Programowanie komponentowe 1

Wstęp - Prosta aplikacja internetowa w technologii Java EE 5. Programowanie komponentowe 1 Wstęp - Prosta aplikacja internetowa w technologii Java EE 5 Programowanie komponentowe 1 Przykład 1- Wykonanie prostej aplikacji internetowej w technologii JavaEE w środowisku Netbeans 5.5 z wykorzystaniem

Bardziej szczegółowo

Aplikacje internetowe i rozproszone - laboratorium

Aplikacje internetowe i rozproszone - laboratorium Aplikacje internetowe i rozproszone - laboratorium Enterprise JavaBeans (EJB) Celem tego zestawu ćwiczeń jest zapoznanie z technologią EJB w wersji 3.0, a w szczególności: implementacja komponentów sesyjnych,

Bardziej szczegółowo

Wykład 4: Klasy i Metody

Wykład 4: Klasy i Metody Wykład 4: Klasy i Metody Klasa Podstawa języka. Każde pojęcie które chcemy opisać w języku musi być zawarte w definicji klasy. Klasa definiuje nowy typ danych, których wartościami są obiekty: klasa to

Bardziej szczegółowo

Współbieżność w środowisku Java

Współbieżność w środowisku Java Współbieżność w środowisku Java Wątki i ich synchronizacja Zagadnienia Tworzenie wątków Stany wątków i ich zmiana Demony Synchronizacja wątków wzajemne wykluczanie oczekiwanie na zmiennych warunkowych

Bardziej szczegółowo

Kurs WWW. Paweł Rajba. pawel@ii.uni.wroc.pl http://pawel.ii.uni.wroc.pl/

Kurs WWW. Paweł Rajba. pawel@ii.uni.wroc.pl http://pawel.ii.uni.wroc.pl/ Paweł Rajba pawel@ii.uni.wroc.pl http://pawel.ii.uni.wroc.pl/ Spis treści Wprowadzenie Automatyczne ładowanie klas Składowe klasy, widoczność składowych Konstruktory i tworzenie obiektów Destruktory i

Bardziej szczegółowo

akademia androida Składowanie danych część VI

akademia androida Składowanie danych część VI akademia androida Składowanie danych część VI agenda 1. SharedPreferences. 2. Pamięć wewnętrzna i karta SD. 3. Pliki w katalogach /res/raw i /res/xml. 4. Baza danych SQLite. 5. Zadanie. 1. SharedPreferences.

Bardziej szczegółowo

PARADYGMATY PROGRAMOWANIA Wykład 4

PARADYGMATY PROGRAMOWANIA Wykład 4 PARADYGMATY PROGRAMOWANIA Wykład 4 Metody wirtualne i polimorfizm Metoda wirualna - metoda używana w identyczny sposób w całej hierarchii klas. Wybór funkcji, którą należy wykonać po wywołaniu metody wirtualnej

Bardziej szczegółowo

6. Formularze tabelaryczne, obiekty nawigacji - rozgałęzienia

6. Formularze tabelaryczne, obiekty nawigacji - rozgałęzienia 6. Formularze tabelaryczne, obiekty nawigacji - rozgałęzienia 1. Kolejne zadanie będzie polegało na utworzeniu formularza tabelarycznego prezentującego utwory określonego wykonawcy. Formularz utworzymy

Bardziej szczegółowo

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna Język JAVA podstawy Wykład 3, część 3 1 Język JAVA podstawy Plan wykładu: 1. Konstrukcja kodu programów w Javie 2. Identyfikatory, zmienne 3. Typy danych 4. Operatory, instrukcje sterujące instrukcja warunkowe,

Bardziej szczegółowo

Programowanie obiektowe i zdarzeniowe

Programowanie obiektowe i zdarzeniowe Marek Tabędzki Programowanie obiektowe i zdarzeniowe 1/23 Programowanie obiektowe i zdarzeniowe wykład 6 polimorfizm Na poprzednim wykładzie: dziedziczenie jest sposobem na utworzenie nowej klasy na podstawie

Bardziej szczegółowo

C++ Przeładowanie operatorów i wzorce w klasach

C++ Przeładowanie operatorów i wzorce w klasach C++ i wzorce w klasach Andrzej Przybyszewski numer albumu: 89810 14 listopada 2009 Ogólnie Przeładowanie (przeciążanie) operatorów polega na nadaniu im nowych funkcji. Przeładowanie operatora dokonuje

Bardziej szczegółowo

Narzędzia i aplikacje Java EE. Usługi sieciowe Paweł Czarnul pczarnul@eti.pg.gda.pl

Narzędzia i aplikacje Java EE. Usługi sieciowe Paweł Czarnul pczarnul@eti.pg.gda.pl Narzędzia i aplikacje Java EE Usługi sieciowe Paweł Czarnul pczarnul@eti.pg.gda.pl Niniejsze opracowanie wprowadza w technologię usług sieciowych i implementację usługi na platformie Java EE (JAX-WS) z

Bardziej szczegółowo

Aplikacja webowa w Javie szybkie programowanie biznesowych aplikacji Spring Boot + Vaadin

Aplikacja webowa w Javie szybkie programowanie biznesowych aplikacji Spring Boot + Vaadin Aplikacja webowa w Javie szybkie programowanie biznesowych aplikacji Spring Boot + Vaadin Czym jest Spring Boot? Spring Boot jest szkieletem aplikacji, opiera się o Spring Framework czyli Framework szeroko

Bardziej szczegółowo

Ćwiczenie 1. Kolejki IBM Message Queue (MQ)

Ćwiczenie 1. Kolejki IBM Message Queue (MQ) Ćwiczenie 1. Kolejki IBM Message Queue (MQ) 1. Przygotowanie Przed rozpoczęciem pracy, należy uruchomić "Kreator przygotowania WebSphere MQ" oraz przejść przez wszystkie kroki kreatora, na końcu zaznaczając

Bardziej szczegółowo

11.6 Klasa do obsługi liczb wymiernych

11.6 Klasa do obsługi liczb wymiernych 246 11.6 Klasa do obsługi liczb wymiernych Klasa do obsługi liczb wymiernych, którą teraz zaprojektujemy w celu zilustrowania korzyści wynikających z programowania obiektowego, służy do zgrabnego wykonywania

Bardziej szczegółowo

BAZY DANYCH Panel sterujący

BAZY DANYCH Panel sterujący BAZY DANYCH Panel sterujący Panel sterujący pełni z reguły rolę centrum, z którego wydajemy polecenia i uruchamiamy różnorodne, wcześniej zdefiniowane zadania, np. wyświetlamy formularze lub drukujemy

Bardziej szczegółowo

Klasy Obiekty Dziedziczenie i zaawansowane cechy Objective-C

Klasy Obiekty Dziedziczenie i zaawansowane cechy Objective-C #import "Fraction.h" #import @implementation Fraction -(Fraction*) initwithnumerator: (int) n denominator: (int) d { self = [super init]; } if ( self ) { [self setnumerator: n anddenominator:

Bardziej szczegółowo

2. Kliknij Insert->Userform. Jeżeli Toolbox nie pojawi się automatycznie, kliknij View -> Toolbox. Otrzymany widok powinien być jak poniżej.

2. Kliknij Insert->Userform. Jeżeli Toolbox nie pojawi się automatycznie, kliknij View -> Toolbox. Otrzymany widok powinien być jak poniżej. Formularze VBA Przykład1 INTERAKTYWNY FORMULARZ Program tworzący interaktywny formularz. Objaśnienie: w dowolnym momencie można wprowadzić wartość w polu tekstowym ID, Excel VBA wczytuje odpowiedni rekord.

Bardziej szczegółowo

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji. JAVA Java jest wszechstronnym językiem programowania, zorientowanym obiektowo, dostarczającym możliwość uruchamiania apletów oraz samodzielnych aplikacji. Java nie jest typowym kompilatorem. Źródłowy kod

Bardziej szczegółowo

Microsoft.NET: ASP.NET MVC + Entity Framework (Code First)

Microsoft.NET: ASP.NET MVC + Entity Framework (Code First) Microsoft.NET: ASP.NET MVC + Entity Framework (Code First) Do realizacji projektu potrzebne jest zintegrowane środowisko programistyczne Microsoft Visual Studio 2012. W ramach projektu budowana jest prosta

Bardziej szczegółowo

Wykład 8: klasy cz. 4

Wykład 8: klasy cz. 4 Programowanie obiektowe Wykład 8: klasy cz. 4 Dynamiczne tworzenie obiektów klas Składniki statyczne klas Konstruktor i destruktory c.d. 1 dr Artur Bartoszewski - Programowanie obiektowe, sem. 1I- WYKŁAD

Bardziej szczegółowo

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy Pojęcie klasy Program napisany w języku Java składa się ze zbioru klas. Każda klasa zawiera

Bardziej szczegółowo

Aplikacje Internetowe. Najprostsza aplikacja. Komponenty Javy. Podstawy języka Java

Aplikacje Internetowe. Najprostsza aplikacja. Komponenty Javy. Podstawy języka Java Aplikacje Internetowe Podstawy języka Java Najprostsza aplikacja class Hello { public static void main(string[] args) { System.out.println("Hello World!"); Komponenty Javy JRE Java Runtime Environment

Bardziej szczegółowo

Języki i techniki programowania Ćwiczenia 2

Języki i techniki programowania Ćwiczenia 2 Języki i techniki programowania Ćwiczenia 2 Autor: Marcin Orchel Spis treści: Język C++... 5 Przekazywanie parametrów do funkcji... 5 Przekazywanie parametrów w Javie.... 5 Przekazywanie parametrów w c++...

Bardziej szczegółowo

Aplikacje w środowisku Java

Aplikacje w środowisku Java Aplikacje w środowisku Java Materiały do zajęć laboratoryjnych Klasy i obiekty - wprowadzenie mgr inż. Kamil Zieliński Katolicki Uniwersytet Lubelski Jana Pawła II 2018/2019 Klasa zbiór pól i metod Obiekt

Bardziej szczegółowo

Wprowadzenie do laboratorium. Zasady obowiązujące na zajęciach. Wprowadzenie do narzędzi wykorzystywanych podczas laboratorium.

Wprowadzenie do laboratorium. Zasady obowiązujące na zajęciach. Wprowadzenie do narzędzi wykorzystywanych podczas laboratorium. Wprowadzenie do laboratorium. Zasady obowiązujące na zajęciach. Wprowadzenie do narzędzi wykorzystywanych podczas laboratorium. Prowadzący Dr inż. Zofia 1 La1 La2 Forma zajęć - laboratorium Wprowadzenie

Bardziej szczegółowo

Informatyka II. Laboratorium Aplikacja okienkowa

Informatyka II. Laboratorium Aplikacja okienkowa Informatyka II Laboratorium Aplikacja okienkowa Założenia Program będzie obliczał obwód oraz pole trójkąta na podstawie podanych zmiennych. Użytkownik będzie poproszony o podanie długości boków trójkąta.

Bardziej szczegółowo

Materiały do laboratorium MS ACCESS BASIC

Materiały do laboratorium MS ACCESS BASIC Materiały do laboratorium MS ACCESS BASIC Opracowała: Katarzyna Harężlak Access Basic jest językiem programowania wykorzystywanym w celu powiązania obiektów aplikacji w jeden spójny system. PROCEDURY I

Bardziej szczegółowo

Korzystanie z edytora zasad grupy do zarządzania zasadami komputera lokalnego w systemie Windows XP

Korzystanie z edytora zasad grupy do zarządzania zasadami komputera lokalnego w systemie Windows XP Korzystanie z edytora zasad grupy do zarządzania zasadami komputera lokalnego w systemie Windows XP W tym opracowaniu opisano, jak korzystać z edytora zasad grupy do zmiany ustawień zasad lokalnych dla

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Laboratorium 12 - wstęp do JavyFX mgr inż. Krzysztof Szwarc krzysztof@szwarc.net.pl Sosnowiec, 31 maja 2017 1 / 15 mgr inż. Krzysztof Szwarc Programowanie obiektowe Krótka lekcja

Bardziej szczegółowo

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe Algorytmy i złożoności Wykład 3. Listy jednokierunkowe Wstęp. Lista jednokierunkowa jest strukturą pozwalającą na pamiętanie danych w postaci uporzadkowanej, a także na bardzo szybkie wstawianie i usuwanie

Bardziej szczegółowo

Systemy Rozproszone - Ćwiczenie 6

Systemy Rozproszone - Ćwiczenie 6 Systemy Rozproszone - Ćwiczenie 6 1 Obiekty zdalne Celem ćwiczenia jest stworzenie obiektu zdalnego świadczącego prostą usługę nazewniczą. Nazwy i odpowiadające im punkty końcowe będą przechowywane przez

Bardziej szczegółowo

Interfejsy i klasy wewnętrzne

Interfejsy i klasy wewnętrzne Interfejsy i klasy wewnętrzne mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011 Interfejs klasy sposób komunikacji z jej obiektami (zestaw składowych publicznych). Określa on zestaw

Bardziej szczegółowo

import java.util.*; public class ListExample { public static void main(string args[]) { List<String> lista1= new ArrayList<String> ();

import java.util.*; public class ListExample { public static void main(string args[]) { List<String> lista1= new ArrayList<String> (); collection Zadanie.1 Napisać program, który: a) tworzy listę (implementacja tablicy ArrayList), dodając po jednym elemencie (korzystając z operacji podstawowej add). Następnie wypisuje całą listę, drugi

Bardziej szczegółowo

xmlns:prism=http://www.codeplex.com/prism c. <ContentControl prism:regionmanager.regionname="mainregion" />

xmlns:prism=http://www.codeplex.com/prism c. <ContentControl prism:regionmanager.regionname=mainregion /> 1 Tworzenie Shella a. W pierwszej kolejności tworzymy nowy projekt: WPF Application. Name: Shell SolutionName: PrismApp b. Dodajemy bibliotekę PRISM za pomocą NuGet Managera (dla.net Framework 4.5 Prism

Bardziej szczegółowo

Programowanie komputerowe. Zajęcia 7

Programowanie komputerowe. Zajęcia 7 Programowanie komputerowe Zajęcia 7 Klasy Klasy to typy danych, które pozwalają na zgromadzenie w jednej zmiennej (obiekcie) zarówno danych jak i operacji związanych z tymi danymi. Obiekt danej klasy może

Bardziej szczegółowo

Architektura interfejsu użytkownika

Architektura interfejsu użytkownika Uniwersytet Jagielloński Interfejsy graficzne Wykład 3 Architektura interfejsu użytkownika Barbara Strug 2011 Hall of shame Hall of Shame Hall of Fame O czym dzisiaj Model Widok- Kontroler Hierarchia widoków

Bardziej szczegółowo

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów Operatory są elementami języka C++. Istnieje zasada, że z elementami języka, takimi jak np. słowa kluczowe, nie można dokonywać żadnych zmian, przeciążeń, itp. PRZECIĄŻANIE OPERATORÓW Ale dla operatorów

Bardziej szczegółowo

Aplikacje w Javie- wykład 11 Wątki-podstawy

Aplikacje w Javie- wykład 11 Wątki-podstawy 1 Aplikacje w Javie- wykład 11 Wątki-podstawy Treści prezentowane w wykładzie zostały oparte o: Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014 http://docs.oracle.com/javase/8/docs/ http://docs.oracle.com/javase/9/docs/

Bardziej szczegółowo

lekcja 8a Gry komputerowe MasterMind

lekcja 8a Gry komputerowe MasterMind lekcja 8a Gry komputerowe MasterMind Posiadamy już elementarną wiedzę w zakresie programowania. Pora więc zabrać się za rozwiązywanie problemów bardziej złożonych, które wymagają zastosowania typowych

Bardziej szczegółowo

LINQ TO XML. Autor ćwiczenia: Marcin Wolicki

LINQ TO XML. Autor ćwiczenia: Marcin Wolicki LINQ TO XML Celem ćwiczenia jest zapoznanie się z możliwościami przetwarzania dokumentów XML na platformie.net. W toku zadania zostaną przedstawione dwie technologie: LINQ TO XML i XPath. Autor ćwiczenia:

Bardziej szczegółowo

/** Program demonstrujący działanie klasy GregorianCalendar import java.util.*; public class TestKalendarza // zbuduj d i zainicjalizuj z aktualną datą GregorianCalendar d = new GregorianCalendar(); int

Bardziej szczegółowo

Wykład 7: Pakiety i Interfejsy

Wykład 7: Pakiety i Interfejsy Wykład 7: Pakiety i Interfejsy Plik Źródłowy w Javie Składa się z: instrukcji pakietu (pojedyncza, opcjonalna) instrukcji importujących (wielokrotne, opcjonalne) deklaracji klasy publicznej (pojedyncza,

Bardziej szczegółowo

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak Java język programowania obiektowego Programowanie w językach wysokiego poziomu mgr inż. Anna Wawszczak 1 Język Java Język Java powstał w roku 1995 w firmie SUN Microsystems Java jest językiem: wysokiego

Bardziej szczegółowo

Klasy abstrakcyjne i interfejsy

Klasy abstrakcyjne i interfejsy Klasy abstrakcyjne i interfejsy Streszczenie Celem wykładu jest omówienie klas abstrakcyjnych i interfejsów w Javie. Czas wykładu 45 minut. Rozwiązanie w miarę standardowego zadania matematycznego (i nie

Bardziej szczegółowo

Aplikacje w środowisku VBA. Visual Basic for Aplications

Aplikacje w środowisku VBA. Visual Basic for Aplications Aplikacje w środowisku VBA Visual Basic for Aplications Podstawowe informacje o VBA Visual Basic for Aplications, w skrócie VBA, to język programowania rozwijany przez Microsoft, którego zastosowanie pozwala

Bardziej szczegółowo

Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r.

Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r. Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r. Programowanie obiektowe Programowanie obiektowe (z ang. object-oriented programming), to

Bardziej szczegółowo