Biblioteki graficzne: Swing, AWT, SWT Adam Barski
Program prezentacji: Biblioteka AWT Wprowadzenie Przykładowy program Komponenty Kontenery i zarządcy rozkładu Obsługa zdarzeń Biblioteka SWING Style Wątek dystrybucji zdarzeń Komponenty zaawansowane Edytor wizualny Biblioteka SWT Konfiguracja projektu Struktura programu Wątki
Biblioteka AWT (Abstract Windows Toolkit) dostępna w Java SE od wersji 1.0 klasy w pakietach java.awt.* jest zależna od systemu operacyjnego, komponenty AWT wywołują natywne kontrolki. stworzoną aplikację można umieścić na stronie www jako tzw. Applet. jest „thread-safe” – możemy wykonywać zmiany w GUI z dowolnego wątku automatyczne usuwanie komponentów (oprócz obiektów Frame i Dialog – należy usuwać je ręcznie za pomocą dispose()). Obecnie jest bardzo rzadko stosowana. Zalety: Bardzo szybka Interfejs prosty w implementacji Istnieje w Javie od początku Wady: brak możliwości ujednolicenia interfejsu na różnych platformach Brak zaawansowanych kontrolek
Przykładowy program package pl.baracus; import java.awt.*; import java.awt.event.*; public class Awt extends Frame { private Button button; //przycisk private Label label; // etykieta private Choice choice; // pole wyboru private Panel p1, p2, p3; //3 panele private Checkbox cb1, cb2; // znaczniki private Checkbox cb3, cb4; // przełączniki private CheckboxGroup cbg; // grupa przełączników public Awt() { super("Awt Application Sample"); setSize(200,200); p1 = new Panel();p2 = new Panel();p3 = new Panel(); p1.setLayout(new FlowLayout()); p2.setLayout(new FlowLayout());p3.setLayout(new FlowLayout());
Przykładowy program //PANEL 1 cb1 = new Checkbox("Checkbox 1"); cb2 = new Checkbox("Checkbox 2"); cb2.setState(true); p1.setBackground(new Color(203,255,203)); p1.add(cb1);p1.add(cb2); //PANEL 2 cbg = new CheckboxGroup(); cb3 = new Checkbox("RadioBtn 1", cbg, false); cb4 = new Checkbox("RadioBtn 1", cbg, false); p2.setBackground(Color.blue); p2.add(cb3);p2.add(cb4); //PANEL 3 button = new Button("Button"); choice = new Choice(); choice.add("Yes");choice.add("No"); p3.setBackground(new Color(255,253,183)); p3.add(button);p3.add(choice); label = new Label("Label",Label.CENTER); setLayout(new GridLayout(2,2)); add(p1);add(p2);add(p3);add(label); } public static void main(String[] args) { Awt awt = new Awt(); awt.setVisible(true); }
Przykładowy program Windows 7 Ubuntu 10.10 Applet Ubuntu 10.10
Komponenty - dziedziczą po klasie Component, klasy Menu po MenuComponent Button List Canvas Scrollbar Label Textfield Checkbox TextArea CheckboxGroup Choice Menu Popup Menu
Kontenery i zarządcy rozkładu Kontenery służą do przechowywania prostych komponentów. Dziedziczą po klasie Container. Zarządcy rozkładu Jeżeli chcemy używać pozycjonowania poprzez współrzędne x,y musimy ustawić setLayout(null) FlowLayout Rozmieszczanie komponentów jeden po drugim, w wierszu, jeżeli komponent się nie mieści, Przenoszony jest do następnego wiersza. BorderLayout Rozmieszczenie brzegowe, oraz w centrum, za pomocą oznaczeń stron świata NORTH, EAST, SOUTH,WEST, CENTER
Kontenery i zarządcy rozkładu GridLayout Rozmieszczanie siatkowe, polega na rozmieszczeniu elementów w siatce o podanej Ilości wierszy i kolumn. Siatka o stałym rozmiarze GridBagLayout Rozmieszczenie podobne do GridLayout, lecz elementy siatki nie muszą mieć tego samego rozmiaru. CardLayout Rozmieszczenie na zasłaniających się kartach
Obsługa zdarzeń Model tradycyjny Opiera się na dziedziczeniu, po wystąpieniu zdarzenia dla obiektu danej klasy, jest ono obsługiwane poprzez metodę action(zdarzenie generowane przez użytkownika), lub przez metodę handleEvent(generowane przez elementy graficzne), lub przekazane do klasy nadrzędnej Przykład: class AWTExample extends Frame{ … public boolean action(Event evt, Object arg) { if (evt.target==button1){ //do something return true; } return false; } } Model delegacyjny W modelu delegacyjnym, do obsługi zdarzeń generowanych przez źródło, mogą być delegowane dowolne obiekty, które implementują odpowiedni interfejs (Listener). Jeden lub kilka obiektów klas nasłuchujących może zostać zarejestrowany do obsługi różnego rodzaju zdarzeń pochodzących z różnych źródeł. Model ten pozwala zarówno na obsługę jak i na generowanie zdarzeń.
Obsługa zdarzeń Ogólne zasady programowania obsługi zdarzeń w modelu delegacyjnym przedstawiają się następująco: Deklaracja klasy implementującej odpowiedni interfejs nasłuchujący. Przykłady : public class MyFrame extends Frame implements WindowListener public class ButtonListener implements ActionListener Rejestracja dla danego komponentu obiektu klasy nasłuchującej: źródło.addRodzajListener(obiektKlasyListener); Przykład: button.addActionListener(new ActionListener(){ …. }); this.addActionListener(this); Implementacja metod z interfejsu nasłuchującego. Interfejs ActionListener posiada tylko jedną metodę do implementacji: public void actionPerformed(ActionEvent evt) { // deklaracja reakcji na zdarzenia }
Obsługa zdarzeń Klasy adaptacyjne Zdarzenia można obsługiwać na cztery sposoby: w klasie zewnętrznej – stosujemy gdy istnieje możliwość separacji kodu odpowiedzialnego za GUI od reakcji na zdarzenie w klasie wewnętrznej – gdy wykorzystujemy referencje obiektu który jest źródłem zdarzenia w klasie anonimowej – gdy chcemy rejestrować Listener dla tylko jednej rodziny zdarzeń w podklasie – gdy chcemy aby podklasa np. komponent MyButton rozszerzający klasę Button mógł reagować na zdarzenia, np. ActionEvent. Robimy to za pomocą metody w konstruktorze: np. EnableEvents(AWTEvent.ACTION_EVENT_MASK); Klasy adaptacyjne Klasy adaptacyjne (Adapter) powstały, aby usprawnić implementowanie interfejsów nasłuchu. Na przykład implementacja interfejsu WindowListener wymagałaby wypisania wszystkich jego metod, nawet gdy ich nie wykorzystujemy: windowActivated(WindowEvent) windowClosed(WindowEvent) windowClosing(WindowEvent) windowDeactivated(WindowEvent) windowDeiconified(WindowEvent) windowIconified(WindowEvent) windowOpened(WindowEvent) Zamiast tego: frame.addWindowListener(new WindowAdapter(){ @Override public void windowClosing(WindowEvent e) { //TODO }});
Obsługa zdarzeń Metoda rejestrująca Komponent, którego dotyczy addActionListener(ActionListener) Button, List, TextField, MenuItem addAdjustmentListener(AdjustmentListener) Scrollbar addComponentListener(ComponentListener) Component addContainerListener(ContainerListener) Container addFocusListener(FocusListener) addItemListener(ItemListener) Choice, List, Checkbox, CheckboxMenuItem addKeyListener(KeyListener) addMouseListener(MouseListener) addMouseMotionListener(MouseMotionListener) addTextListener(TextListener) TextComponent addWindowListener(WindowListener) Window
Biblioteka Swing dostępna jako dodatek do Java 1.1, wcielona do SE w wersji 1.2 klasy w pakietach javax.swing.*; w dużym uproszczeniu jest nakładką na AWT samodzielnie rysującą elementy graficzne i korzystającą z mechanizmu kontroli zdarzeń AWT wszelkie operacje należy wykonywać w wątku Event Dispatch Thread stworzoną aplikację można uruchamiać ze strony www przez JApplet Zalety: Swing zawiera bogaty i wygodny w użyciu zestaw elementów intefejsu użytkownika Swing w niewielkim stopniu zależy od platformy dzięki czemu jest mniej podatny na błędy związane z danym systemem. Sposób działania i wygląd elementów Swinga jest taki sam na różnych platformach (lecz wyglądają inaczej niż elementy natywne) W NetBeans istnieje narzędzie do graficznego projektowania interfejsu Wady: Jest powolny.
class SwingFrame extends JFrame { Przykładowy program package pl.baracus; import java.awt.* import javax.swing.* class SwingFrame extends JFrame { private JButton button; //przycisk private JLabel label; // etykieta private JRadioButton r1,r2; // pole wyboru private JPanel p1, p2, p3; private JCheckBox cb1, cb2; // znaczniki private JComboBox combo; public SwingFrame(){ setTitle("Swing Application Sample");setSize(250,250); p1 = new JPanel();p2 = new JPanel();p3 = new JPanel(); p1.setLayout(new FlowLayout()); p2.setLayout(newFlowLayout());p3.setLayout(new FlowLayout()); //PANEL 1 cb1 = new JCheckBox("Checkbox 1");cb2 = new JCheckBox("Checkbox 2"); cb2.setSelected(true); p1.setBackground(new Color(203,255,203)); p1.add(cb1);p1.add(cb2); //PANEL 2 ButtonGroup group = new ButtonGroup();
r2 = new JRadioButton("RadioBtn 2"); group.add(r1);group.add(r2); Przykładowy program r1 = new JRadioButton("RadioBtn 1"); r2 = new JRadioButton("RadioBtn 2"); group.add(r1);group.add(r2); p2.add(r1);p2.add(r2); p2.setBackground(Color.blue); } public class SwingTest { public static void main(String[] args){ EventQueue.invokeLater(new Runnable(){ public void run(){ SwingFrame frame = new SwingFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); //PANEL 3 button = new JButton("Button"); String[] choices = { "Yes", "No" }; combo= new JComboBox(choices); p3.setBackground(new Color(255,253,183)); p3.add(button);p3.add(combo); label = new JLabel("Label",SwingConstants.CENTER); setLayout(new GridLayout(2,2)); add(p1);add(p2);add(p3);add(label);
Przykładowy program Windows 7 (Ocean) Ubuntu 10.10 (Ocean) Windows 7 (WindowsLookAndFeel)
Brak wyglądu natywnego możemy kompensować poprzez zastosowanie stylów. Style Brak wyglądu natywnego możemy kompensować poprzez zastosowanie stylów. Domyślnym stylem w Swingu był Metal do Javy SE 5.0, który wygląda tak samo na każdej platformie, obecnie trochę rozbudowany nazywa się Ocean. Niektóre style są chronione prawami autorskimi i dostępne są tylko dla konkretnej platformy (np. WindowsLookAndFeel) zadziała tylko na Windows. Więcej stylów można znaleźć pod adresem http://javootoo.com Zmiana stylu poprzez plik swing.properties w katalogu jre/lib swing.defaultaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel swing.defaultaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel swing.defaultaf=javax.swing.plaf.metal.MetalLookAndFeel dynamicznie w kodzie String plaf = „com.sun.java.swing.plaf.motif.MotifLookAndFeel”; try { UIManager.setLookAndFeel(plaf); SwingUtilities.updateComponentTreeUI(panel); } catch (Exception e) {e.printStackTrace();} Listowanie zainstalowanych stylów: UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
Wątek dystrybucji zdarzeń Wszelkie operacje modyfikujące interfejs użytkownika, lub obsługujące zdarzenia, muszą by wykonywane w Wątku Dystrybucji Zdarzeń (Event Dispatch Thread). Większość elementów Swinga nie jest „thread-safe”, przez co używanie ich w innych wątkach może doprowadzić do trudnych do wykrycia błędów. if (SwingUtilities.isEventDispatchThread()){ //kod wykonywany normalnie } else{ Runnable r = new Runnable(){ @Override public void run() { //tworzymy nowy wątek i później wywołujemy go w EDT }; try { SwingUtilities.invokeAndWait(r); } catch (InterruptedException e) {….} SwingWorker Wszelkie operacje, które zajmują dużo czasu, powinny być wykonywane w wątkach w tle, każdy z takich wątków reprezentowany jest przez klasę SwingWorker. Implementuje interfejs java.util.concurrent.Future. SwingWorker worker = new SwingWorker<Image, Void>() { public Image [] doInBackground() { … @Override public void done() {
Komponenty zaawansowane JTable JTree JTabbedPane JSlider JProgressBar Użycie HTML do formatowania tekstu
Edytor wizualny w NetBeans
Biblioteka SWT (Standard Widget Toolkit) dostępna jako dodatek, stworzony przez fundację Eclipse. Biblioteka jest w pełni open-source. Klasy w pakiecie org.eclipse.swt.* Ideą biblioteki jest, aby wygląd i zachowanie komponentów graficznych był w pełni zgodny z systemem operacyjnym, również pod względem szybkości Korzysta z JNI do opakowywania natywnego kodu danej platformy i dostarcza ujednoliconych interfejsów. Zalety: Aplikację można skompilować do kodu natywnego Bardzo szybka, w szczególności ma mniejsze wymagania pamięciowe niż np. Swing Istnieje dla każdej znaczącej platformy. (Windows, Linux, Mac, Solaris) Wady: Trudno rozbudować komponent SWT lub stworzyć własny SWT długo nie miało odpowiednika Rendererów i wciąż trudno tworzyć tabelki czy listy z zawartością graficzną Trzeba samemu zatroszczyć się o zwalnianie zasobów (funkcja dispose())
Konfiguracja projektu Aby uruchomić aplikację napisaną pod SWT, musimy: ściągnąć archiwum z biblioteką dla konkretnego systemu operacyjnego oraz stylu graficznego np: swt-3.6.2-win32-win32-x86.zip swt-3.6.2-gtk-linux-x86_64.zip swt-3.6.2-motif-linux-x86.zip zaimportować projekt z archiwum, dodać SWT do projektu w Build Path stworzy się , projekt org.eclipse.swt
Przykładowy program import org.eclipse.swt.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; public class SWTSample { public static void main (String [] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setSize(250,280) ;shell.setText("SWTSample"); //ustawienia layout managera GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.horizontalSpacing=0;layout.verticalSpacing=0; layout.marginHeight=0;layout.marginWidth=0; layout.makeColumnsEqualWidth = true; shell.setLayout(layout); //PANEL 1 GridData data = new GridData(GridData.FILL_BOTH); Composite composite = new Composite(shell, SWT.NONE);
Przykładowy program composite.setBackground(new Color(display,203,255,203)); Button c1 = new Button(composite, SWT.CHECK);c1.setText("Checkbox 1"); Button c2 = new Button(composite, SWT.CHECK);c2.setText("Checkbox 2"); composite.setLayout(new RowLayout(SWT.VERTICAL)); composite.setLayoutData(data); //PANEL 2 data = new GridData(GridData.FILL_BOTH); Composite composite2 = new Composite(shell, SWT.NONE); composite2.setBackground(display.getSystemColor(SWT.COLOR_BLUE)); Button r1 = new Button(composite2, SWT.RADIO);r1.setText("RadioButton 1"); Button r2 = new Button(composite2, SWT.RADIO);r2.setText("RadioButton 2"); composite2.setLayout(new RowLayout(SWT.VERTICAL)); composite2.setLayoutData(data); //PANEL3 Composite composite3 = new Composite(shell, SWT.NONE); composite3.setBackground(new Color(display,255,253,183)); Button three = new Button(composite3, SWT.PUSH); three.setText("Button");String[] ITEMS = { "Yes", "No" };
Przykładowy program CCombo combo = new CCombo(composite3, SWT.DROP_DOWN); combo.setBounds(0, 0, 55, 15);combo.setItems(ITEMS);combo.select(0); composite3.setLayout(new RowLayout(SWT.VERTICAL)); composite3.setLayoutData(data); data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); Label label = new Label(shell, SWT.NONE);label.setText("Label"); label.setLayoutData(data); shell.open (); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); } display.dispose ();
Przykładowy program Windows 7 Ubuntu 10.10
Struktura programu Typowa aplikacja SWT ma następującą strukturę: Klasa Display która reprezentuję sesje SWT. Jest to połączenie pomiędzy SWT a systemem operacyjnym. Display zarządza również komunikacją pomiędzy wątkiem UI a innymi wątkami. Jedna lub więcej klas Shell – reprezentujących główne okno aplikacji. Pierwszy obiekt reprezentuje okno nadrzędne, pozostałe to zazwyczaj dialogi. Tylko ta klasa, w przeciwieństwie do innych Widgetów nie potrzebuje obiektu rodzica. Inne klasy widgetów które tworzymy wewnątrz klasy Shell Następnie po stworzeniu struktury: Inicjalizujemy rozmiary oraz inne stany kontrolek wewnątrz Shell. Tworzymy Listenery zdarzeń oraz je rejestrujemy. Otwieramy okno Shell Uruchamiamy pętlę rozprowadzającą zdarzenia (Event Dispatching Loop), która działa do czasu, aż zamkniemy okno główne (Shell). Usuwamy obiekt Display metodą dispose. Style widgetów Aby ustawić style lub inne właściwości widgetu, przy jego tworzeniu ustawiamy tzw. Style bits. Działają one podobnie jak w WinApi. Możemy je łączyć za pomocą logicznego OR. W przypadku gdy platforma nie implementuje danego stylu, jest on ignorowany. Przykład SWT.NONE || SWT.CENTER
Wykonywanie kodu poza wątkiem UI System operacyjny posiada wątek dystrybucji zdarzeń UI, takich jak tworzenie kontrolek, zdarzenia kliknięcia, odświeżanie okien itp. Za wątek główny SWT, nazywany SWT UI Thread, uznajemy wątek w którym stworzyliśmy pętlę główną klasy Display. Wszelki operacje graficzne muszą być wykonywane w tym wątku. Wykonywanie kodu poza wątkiem UI Kod poza wątkiem SWT UI możemy wykonać przy pomocy klasy Display: metoda syncExec(Runnable) jest używana, gdy kod aplikacji może poczekać na wartość zwracaną przez obiekt Runnable. metoda asyncExec(Runnable) używamy, gdy nie musimy czekać na wynik operacji obiektu Runnable – przykład – operacje na ProgressBar. Przykład: // operacje wymagające dużo pbliczeń ... // w tym miejscu chcemy ukaktualnić GUI, lecz nie czekamy // na wynik tej operacji – używamy metody async display.asyncExec (new Runnable () { public void run () { if (!myWindow.isDisposed()) myWindow.redraw (); } }); // kolejne czasochłonne operacje
Dziękuję za uwagę