Tym razem tworzymy nieco bardziej złożoną aplikację, w której będziemy wprowadzać do niej tekst, który będzie przetwarzany a efekt będzie wyświetlany. Przy okazji poznamy kilka podstawowych kontrolek i dowiemy się jak je uporządkować.
Aplikacja
Nasza kolejna aplikacja będzie miała do wykonania proste zadanie. Użytkownik wprowadzi do niej długa sekwencję nukleotydów, następnie krótszą, a program poszuka miejsc w których krótsza sekwencja będzie występowała w dłuższej i wyświetli wynik. Ponieważ program opiera się na szukania jednych łańcuchów znaków w innych, niekonieczne będzie ją trzeba stosować akurat do sekwencji nukleotydów ale w zasadzie do dowolnego tekstu.
Najpierw warto się zastanowić jak tak aplikacja może wyglądać. Mój projekt wygląda tak:
Jak widać, nie jest szczególnie skomplikowany.
Teraz zastanówmy się, jakich kontrolek będziemy potrzebowali. Może nam w tym pomóc wspomniana w poprzednim wpisie aplikacja Ensemble ale warto też zajrzeć na stronę firmy Oracle z opisem kontrolek
Podpisy do poszczególnych pól, utworzymy za pomocą kontrolki Label
służąca zasadniczo do wyświetlania tekstu, choć można w niej umieścić także grafikę.
Sekwencję będziemy wpisywać to kontrolki typu TextArea
, która umożliwia wpisywanie wielu linii tekstu.
Do wpisywania szukanej sekwencji, która będzie raczej krótka, posłuży nam TextField
, która zawiera jedną linię z ciągiem znaków.
Do wypisywania wyniku użyjemy kontrolki TextArea
ale bez możliwości edycji tekstu.
Przyciski – to będą oczywiście kontrolki typu Button
znane nam z poprzedniej lekcji.
Teraz warto się zastanowić nad sposobem rozmieszczenia kontrolek. Jak wspomniałem w poprzednim wpisie służą do tego węzły – panele pozwalające na kontrolę rozmieszczenia elementów interfejsu graficznego, niektóre mają w nazwie Pane
. Poznaliśmy już StackPane
, który umieszczał obiekty na sobie. W naszym przypadku będzie to bezużyteczne. Spróbujmy użyć do naszych celów dwu rodzajów paneli:
VBox
– pozwala umieszczać elementy pod sobą w pionie (jeden pod drugim)HBox
– porządkuje obiekty w poziomie (jeden obok drugiego)
Teraz zastanówmy się jak je wykorzystać w porządkowaniu elementów naszego graficznego interfejsu. Użyję panelu typu VBox
jako głównego kontenera, który możemy sobie wyobrazić jako regał z półkami. Z kolei HBox
-y będą służyć nam do porządkowania elementów w poziomie. Będą one potrzebne w dwu miejscach: tam gdzie będziemy wpisywać szukaną sekwencję i do rzędu z przyciskami. Można to graficznie pokazać tak:
Na czerwono zaznaczyłem VBox
, na niebiesko panele typu HBox
.
Zatem czas na umieszczenie tego wszystkiego w kodzie. Sugeruję, zgodnie z instrukcją z poprzedniej lekcji stworzyć domyślą aplikację w JavieFX a potem zmodyfikować plik:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
package szukaczsekwencji; import javafx.application.Application; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class SzukaczSekwencji extends Application { @Override public void start(Stage primaryStage) { // Tworzymy VBox, który będzie porządkował elementy w pionie // Argument liczbowy (tu 5) oznacza odstepy między elementami VBox siatkaPionowaVBox = new VBox(5); // Ustawiamy odstęp pomiędzy krawędziami wewnętrznymi VBox-u // a elementami w jego wnętrzu siatkaPionowaVBox.setPadding(new Insets(5)); // Etykieta to pierwszego pola Label sekwencjaLabel = new Label(); // Ustawiamy tekst wyświetlający się w etykiecie sekwencjaLabel.setText("Sekwencja"); // Dodajemy etykietę do VBox-u siatkaPionowaVBox.getChildren().add(sekwencjaLabel); // Tworzymy kontrolkę typu TextArea TextArea sekwencjaTextArea = new TextArea(); // Ustawiamy możliwość zawijania tekstu sekwencjaTextArea.setWrapText(true); // Dodajemy kontrolkę siatkaPionowaVBox.getChildren().add(sekwencjaTextArea); // Tworzymy HBox, z odstępami między elementami = 5 HBox szukanaHBox = new HBox(5); // Etykieta, tym razem przy tworzeniu obiektu przekazaliśmy argument - // tekst, który powinien się wyswietlać na etykiecie Label szukanaLabel = new Label("Szukaj: "); // Tworzymy TextField TextField szukanaTextField = new TextField(); // Ustawiamy minimalną szerokość pola tekstowego szukanaTextField.setMinWidth(235); // Dodajemy Label i TextField do HBox-u szukanaHBox.getChildren().add(szukanaLabel); szukanaHBox.getChildren().add(szukanaTextField); // Dodajemy HBox do VBox-u siatkaPionowaVBox.getChildren().add(szukanaHBox); // Kolejna etykieta Label wynikLabel = new Label("Wynik:"); // Dodajemy ją do VBox-u siatkaPionowaVBox.getChildren().add(wynikLabel); // Tworzymy TextArea do wypisywania wyniku TextArea wynikTextArea = new TextArea(); // Wyłączamy możliwość edycji wynikTextArea.setEditable(false); // Włączamy zawijanie tekstu wynikTextArea.setWrapText(true); // Dodajemy do VBox-u siatkaPionowaVBox.getChildren().add(wynikTextArea); // Tworzymy HBox dla przycisków HBox buttonsHBox = new HBox(5); // Pierwszy przycisk Button szukajButton = new Button("Szukaj"); // Ustawiamy akcję szukajButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { // Na razie nie mamy metody odpowiedzialnej za wyszukiwanie // więc w polu wynikTextArea umieszczamy... wynikTextArea.setText( // ... tekst pobrany z szukanaTextField + spacja ... szukanaTextField.getText() + " " + // + tekst pobrany z sekwencjaTextArea sekwencjaTextArea.getText()); } }); // Teraz przycisk "Zakończ" Button zakonczButton = new Button("Zakończ"); // Akcja dla przycisku "Zakończ" zakonczButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { // Tak się powinno zakańczać działanie aplikacji w JavaFX Platform.exit(); } }); // Dodawanie przycisków do HBox-u, metoda addAll pozwala dodawać // na raz wiele elementów buttonsHBox.getChildren().addAll(szukajButton, zakonczButton); // Dodajemy buttonsHBox do siatkaPionowaVBox siatkaPionowaVBox.getChildren().add(buttonsHBox); // Tworzymy Scene Scene scene = new Scene(siatkaPionowaVBox, 300, 250); // Tutuł okna primaryStage.setTitle("Szukanie sekwencji"); // Ustawianie sceny primaryStage.setScene(scene); // Pokaż primaryStage.show(); } public static void main(String[] args) { launch(args); } } |
Po uruchomieniu powinniśmy uzyskać takie okienko:
Teraz krótko omówię kilka aspektów powyższego kodu. Po pierwsze zauważ, że każdy z węzłów (paneli i kontrolek), które zostały użyte, posiadają pewne modyfikowalne właściwości. Dotyczą one na przykład ich wymiarów, tekstu, który się wyświetla, możliwości edycji, widzialności itd. Niektóre z tych właściwości są charakterystyczne dla konkretnego rodzaju elementu (np. ustawianie wyświetlanego tekstu dla przycisku czy etykiety ma sens ale byłoby raczej bezcelowe dla panelu VBox
). Wszystkie węzły dziedziczą po klasie Node
, warto więc zapoznać się jakie właściwości i metody nam oferuje w dokumentacji. Jak zwykle zachęcam do przeglądania dokumentacji interesujących nas węzłów a także podglądania podpowiedzi sugerowanych przez NetBeans. Dużo się można nauczyć sprawdzając działanie poszczególnych metod i zmieniając ustawienia dostępnych właściwości.
Nasza prosta aplikacja potrafiła także pobrać informacje (tekst) z jednych kontrolek i po modyfikacji, umieścić je w innych kontrolkach. Pobieranie odbywało się przy pomocy „getterów” (getText()
), przekazywanie informacji przez „settery”(setText()
). To oczywiście zaledwie przedsmak modyfikacji wyglądu i zachowania kontrolek i innych węzłów.
Dodajemy klasę odpowiedzialną za zmianę tekstu
Warto by teraz dopisać kod, odpowiedzialny za rzeczywiste wyszukiwanie sekwencji i zaznaczanie miejsc jej występowania. Wyszukiwanie zrealizujemy za pomocą metod udostępnianych przez klasę String
o czym była mowa w jednej z poprzednich lekcji. Ale zanim do tego dojdziemy stworzymy dodatkową klasę, w której umieścimy potrzebny kod. Moglibyśmy oczywiście dopisać metody do klasy, którą już mamy, ale jest dobrą praktyką oddzielanie kodu odpowiedzialnego za tworzenie interfejsu od kodu odpowiedzialnego za innego rodzaju operacje, np. wyszukiwanie, obliczenia itd.
Utwórz w pakiecie nową klasę Javy o nazwie Szukacz
. W niej umieść statyczną metodę szukaj (String sekwencja, String szukana)
i wypełnij ją kodem.
1 2 3 4 5 6 7 8 9 10 11 |
package szukaczsekwencji; public class Szukacz { public static String szukaj (String sekwencja, String szukana) { // Zmieniamy wszystkie wystąpienia szukanej sekwencji na taką samą, ale // otoczoną znakami '[' i ']' String rezultat = sekwencja.replaceAll(szukana, '['+szukana+']'); return rezultat; } } |
W naszym kodzie zastosowałem metodę statyczną, nie trzeba więc będzie tworzyć obiektu typu Szukacz
aby z niej skorzystać. Nie ma ku temu jakichś ważnych powodów, równie dobrze możesz napisać metodę niestatyczną i tworzyć obiekt.
Znajdywanie miejsc występowania szukanej sekwencji można rozwiązać na różne sposoby, na przykład wykorzystując metodę indexOf()
klasy String
(spróbuj w ramach treningu napisać taką metodę). W moim kodzie użyłem jak widać metodę replaceAll()
, która podmienia szukaną sekwencje na taką samą, ale poprzedzoną znakiem [
i zakończoną znakiem ]
. To jest oczywiście rozwiązanie bardzo proste, ładniej wyglądałoby na przykład pokolorowanie odpowiednich miejsc łańcucha znaków.
Teraz pozostaje zmienić odpowiedni fragment kodu w klasie SzukaczSekwencji
odpowiedzialny za akcję wywoływaną przez przycisk Szukaj
:
1 2 3 4 5 6 7 8 9 10 11 12 |
szukajButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { wynikTextArea.setText( // Wywołujemy metode statyczną szukaj z klasy Szukacz Szukacz.szukaj(sekwencjaTextArea.getText(), szukanaTextField.getText())); } }); |
Teraz możemy uzyskać np. taki wynik:
Inne panele
W naszej aplikacji użyliśmy dwóch rodzajów paneli: VBox
i HBox
, wcześniej poznaliśmy jeszcze StackPane
. Zastosowanie tej trójki pozwala na dość dużą swobodę komponowania wyglądu interfejsu graficznego ale nie zawsze jest to najwygodniejsze. Dlatego warto się zapoznać z innymi rodzajami paneli oferowanymi przez JavaFX. Poszerzają one klasę Pane
, zajrzyj do jej dokumentacji gdzie znajdziesz także spis dziedziczących po niej węzłów wraz z linkami do odpowiedniej dokumentacji. Jednym z nich jest GridPane
, który sprawdza się w wielu przypadkach, ponieważ tworzy „siatkę” co pozwala na łatwe uporządkowanie elementów w rzędach i kolumnach.
Zadanie
Przerób powyższą aplikację tak aby:
- Uporządkować elementy za pomocą panelu typu
GridPane
zamiastVBox
iHBox
. Możesz zmienić układ kontrolek. - Aplikacja wyświetlała liczbę znalezionych sekwencji
Dziękuję za kurs :) Naprawdę sporo się już z niego nauczyłem.
Odnośnie powyższego zagadnienia, stworzyłem klasę Szukacz na swój sposób i chciałem się nim tu podzielić:
public class Szukacz {
public static String szukaj (String sekwencja, String szukana) {
String rezultat = sekwencja.replaceAll(szukana, „”);
return rezultat;
}
}
Gdy przetestowałem program wszystko działa jak należy. Czy jednak moje rozwiązanie nie jest obarczone jakimiś błędami, których ja jako nowicjusz w tej dziedzinie być może nie zauważyłem??
Z góry dziękuję za uwagi i pozdrawiam,
Marek
Niestety kod nie wkleił się prawidłowo :/ W metodzie replaceAll w miejscu „” wprowadziłem szukany łańcuch znaków okalając go dodatkowo znacznikami w postaci – to tak w skrócie. Mam nadzieję, że teraz będzie ok. Pozdrawiam
I znowu nie poszło dobrze. Niestety nie jestem jeszcze zbyt zorientowany jak wpisywać kod aby poprawnie wyświetlała go strona www. Mam nadzieję w przyszłości go nadrobić. <>
replaceAll(szukana, „”);
Algorytm fajny, szukałem czegoś takiego i dobrze trafiłem, aczkolwiek jest on niedopracowany i może zwrócić zły wynik.
Załóżmy, że mam ciąg znaków: „aabbcc aabbcc aabbcc”
Chcę w tym ciągu znaleźć literkę „c”, wynik:
„aabb aabb aabb”
Na końcu znajduje tylko jedno „c”. Tak samo gdyby tych „c” było na końcu 10, zwróci tylko jedno. Moje rozwiązanie polega na dodaniu do sekwencji jednej spacji na końcu, wtedy sekwencja zostanie odpowiednio podzielona i wszystkie pojedyncze znaki będą mogły zostać wyszukane.
Poza tym, zamiast tego:
if (rezultat.length()==0)
rezultat = rezultat + pomiedzy[i] + „” + pomiedzy[i+1];
Można przed pętlą wstawić to:
rezultat+=pomiędzy[0];
I usunąć tamten warunek.
To samo z tym warunkiem:
if (pomiedzy.length > 1) {
Można usunąć, ponieważ i tak przed pętlą przypisze nam pierwszy element tablicy pomiędzy[], a jeżeli elementu szukanego nie będzie w sekwencji, wtedy cała sekwencja będzie znajdować się w tym właśnie pierwszym elemencie.
Jeśli gdzieś popełniłem błąd, to liczę, że mnie poprawicie :)
Jakby co wklejam mój cały kod (robiłem bez interfejsu graficznego, przy pomocy skanera pobieram od użytkownika dane):
public static void main (String args[]) {
Scanner skan=new Scanner(System.in);
System.out.printf(„Podaj tekst, w którym chcesz coś wyszukać: %n”);
String tekst=skan.nextLine();
System.out.printf(„Wpisz szukany tekst: „);
String szukany=skan.nextLine();
tekst+=” „;
String bezSzukanego[]=tekst.split(szukany);
String rezultat=bezSzukanego[0];
for (int i=0;i<bezSzukanego.length-1;i++){
rezultat+="”+bezSzukanego[i+1];
}
System.out.print(rezultat);
skan.close();
}}
Liczę na odpowiedź, ewentualną dyskusję, pozdrawiam
Dziękuję za czujność i zwrócenie uwagi na błąd. Teraz nie mam czasu się tym zająć, ale z pewnością do sprawy wrócę.
Pozdrawiam serdecznie.