Systemy operacyjne Wykład 3a Działanie aplikacji okienkowej dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki http://wbieniec.kis.p.lodz.pl/pwsz 1 Zdarzeniowy model aplikacji System Windows bazuję na komunikatach, które zarządzają stanem aplikacji. Komunikaty informują aplikacje o tym co się dzieje w systemie. - komunikaty poleceń (ang. command messages) - wysyłane w celu wykonania jakiejś czynności -komunikaty powiadomień (ang. notification messages) - dla powiadomienia aplikacji lub systemu o wystąpieniu zdarzenia. Komunikaty przetwarzane są w kolejce 1. wystąpienie zdarzenia - system umieszcza je w kolejce komunikatów, 2. process pobiera komunikat z kolejki 3. komunikat jest wysyłany pod adresem okna docelowego, 4. jeżeli w procedurze okna, uwzględniono jego obsługę, jest ona wywoływana, w przeciwnym wypadku obsługa w sposób domyślny 2 Zdarzeniowy model aplikacji Działanie aplikacji Windows polega na przetwarzaniu nieskończonej pętli while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } 3 Budowa komunikatu HWND hwnd – wskaźnik na okno, do którego adresowany jest komunikat UINT message – określa identyfikator komunikatu (standardowy lub własny) WPARAM wParam, LPARAM lParam – dodatkowe parametry komunikatu. Razem 8 bajtów. Możliwość wykorzystania tylko kilku pierwszych bajtów (parametr wyższy). DWORD time Czas w momencie, którym zdarzenie miało miejsce. Pole mało istotne, rzadko używane. POINT pt; Pole przechowuje współrzędne kursora w momencie zaistnienia zdarzenia. To pole także rzadko jest używane. 4 Komunikaty okna: Komunikaty niekolejkowane WM_CREATE - Procedura obsługi okna otrzymuje ten komunikat podczas wykonywania przez system operacyjny funkcji CreateWindow. WM_DESTROY - Komunikat informujący o tym, że system rozpoczął proces niszczenia okna na żądanie użytkownika. Komunikaty kolejkowane WM_PAINT - Większość programów pracujących pod kontrolą systemu Windows wywołuje funkcję UpdateWindow podczas inicjalizacji w funkcji WinMain zaraz przed uruchomieniem pętli komunikatów. W ten sposób system wysyła do procedury okna pierwszy komunikat WM_PAINT, który informuje procedurę okna o konieczności wypełnienia obszaru klienta. Następnie procedura okna otrzymuje komunikat WM_PAINT zawsze wtedy, gdy ma miejsce jedno z następujących zdarzeń: - wcześniej zasłonięty obszar okna jest odsłaniany w wyniku zmiany pozycji innego okna przez użytkownika; - użytkownik zmienia rozmiar okna (jeżeli styl klasy okna posiada flagi CS_HREDRAW i CS_VREDRAW); program wykorzystuje funkcje ScrollWindow lub ScrollDC do przewinięcia części jego obszaru klienckiego; - program używa funkcji InvalidateRect lub InvalidateRgn do jawnego wygenerowania komunikatu WM_PAINT. 5 WM_KEYDOWN WM_KEYUP Komunikaty wysyłane do okna podczas wciskania lub zwalniania niesystemowego przycisku klawiatury. Parametry: - wParam określa kod jego wirtualnego przycisku. WM_COMMAND - Komunikat wysyłany gdy użytkownik wybiera polecenie menu lub gdy kontrolka wysyła komunikat do okna nadrzędnego. Parametry: - wParam (high-word) ma wartość 0 dla poleceń menu, a w przypadku kontrolki, specyficzny dla niej kod powiadomienia; - wParam (low-word) określa identyfikator pola menu lub kontrolki; - lParam ma wartość 0 dla poleceń menu lub określa uchwyt okna kontrolki. WM_LBUTTONDOWN - Komunikaty wysyłane gdy użytkownik wciska (zwalnia) lewy (prawy) przycisk WM_LBUTTONUP myszy podczas, gdy kursor znajduje się w obszarze klienckim okna. Parametry: WM_RBUTTONDOWN - wParam określa jakie wirtualne przyciski są wciśnięte: WM_RBUTTONUP • MK_CONTROL – przycisk CTRL, • MK_LBUTTON – lewy przycisk myszy, • MK_MBUTTON – środkowy przycisk myszy, • MK_RBUTTON – prawy przycisk myszy, • MK_SHIFT – przycisk SHIFT; - lParam (low-word) określa współrzędną x kursora; - lParam (high-word) określa współrzędną y kursora. WM_MOUSEMOVE - Komunikat wysyłany do okna podczas ruchu kursora. Parametry komunikatu są identyczne jak w przypadku wciskania i zwalniania przycisków myszy. 6 Programowanie zdarzeniowe Ideą programowania zdarzeniowego jest to, że zaimplementowane przez nas funkcje nie są wywoływane jawnie w kodzie programu, lecz przez system operacyjny lub wydzielony moduł programu. Funkcje te nazywa się zwrotnymi (callback). Jest to nawiązanie do idei przerwań Komunikaty: Funkcje obsługujące tzw. kolejkę komunikatów pochodzących od systemu lub: • mysz; • klawiatura; • zmiana w interfejsie graficznym programu; • zegar; • operacje plikowe lub sieciowe. Wyjątki: Funkcje obsługujące błędy czasu wykonywania mogące wystąpić w programie: • operacje arytmetyczne (np. dzielenie przez zero); • operacje wejścia / wyjścia (brak pliku, brak połączenia sieciowego); • błędy adresowania pamięci (przekroczenie zakresu tablicy, nieistniejący obiekt). Budowa aplikacji Windows Aplikacja Windows działa w następujących fazach: Tworzymy tzw klasę okna, definiujemy jego wygląd wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra =0; wndclass.cbWndExtra =0; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } Tworzymy w pamięci instancję okna hwnd = CreateWindow (szAppName, TEXT ("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; 8 Budowa aplikacji Windows Uruchamiamy nieskończoną pętlę przetwarzania komunikatów while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } Funkcja DispatchMessage uruchamia tzw funkcję okna, w której możemy obsłużyć komunikaty LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONDOWN : MessageBox(hwnd, “Zostal wcisniety lewy przycisk myszy…", „Informacja", MB_ICONINFORMATION | MB_OK); return 0; case WM_CLOSE: if (IDCANCEL == MessageBox(hwnd, „Czy chcesz zamknac program?", „Pytanie", MB_ICONQUESTION | MB_OKCANCEL)) return 0; else break; case WM_DESTROY : PostQuitMessage(0); return 0; } return DefWindowProc (hwnd, message, wParam, lParam); } Komunikaty obsłużone przez użytkownika nie powinny trafić do domyślnej procedury okna ‘DefWindowProc’. W tym celu po obsłużeniu komunikatu należy wyjść z procedury zwracając wartość zero. Wszystkie komunikaty, których nie obsłużył użytkownik, trafiają do domyślnej 9 procedury okna ‘DefWindowProc’. Program WINAPI Funkcja WinMain Definiowanie klas okien programu Rejestracja zdefiniowanych klas Utworzenie okna głównego Wyświetlenie okna głównego Pętla komunikatów Pobranie komunikatu Przekazanie komunikatu Procedura obsługi okna 1 Obsługa komunikatów okna 1 Reakcja systemu na zdarzenie System HWND WM_CLOSE 0 0 operacyjny HWND WM_DESTROY 0 0 Procedura obsługi okna 2 Obsługa komunikatów okna 2 Kolejka komunikatów HWND WM_COMMAND LPARAM HWND WM_QUIT 0 0 WPARAM Implementacja (Visual C++, MFC) Architektura Dokument-Widok Podstawowe klasy CApplication – klasa aplikacji, uruchamiająca aplikację CMainFrame – klasa okna głównego CDocument – klasa dokumentu – obiekt skojarzony z otwartym plikiem CView – klasa widoku. Obiekt skojarzony z oknem wyświetlającym dokument. Wizualnie – tzw obszar klienta okna – część wewnątrz ramki Implementacja pętli komunikatów BEGIN_MESSAGE_MAP(CImgViewerView, CScrollView) //{{AFX_MSG_MAP(CImgViewerView) ON_WM_ERASEBKGND() //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CScrollView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CScrollView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CScrollView::OnFilePrintPreview) END_MESSAGE_MAP() 11 Implementacja Java W Javie i C# zaimplementowano Delegacyjny model obsługi zdarzeń W Javie wprowadzono tu koncepcję "oddelegowania" obsługi: metoda, która obsługuje zdarzenie jest metodą innej klasy niż obiekt, któremu zdarzenie się przytrafiło. Słuchacz (ang. Listener) to obiekt, który może obsługiwać zdarzenia. Aby móc generować obiekty-Słuchacze, klasa musi implementować interfejs nasłuchu. Interfejs nasłuchu zawiera abstrakcyjne metody do obsługi zdarzeń. Jego implementacja oznacza, iż metody te zyskują konkretne definicje. Java dostarcza wiele standardowych interfejsów nasłuchu, przygotowanych do odbioru konkretnych rodzajów zdarzeń. Klasy-słuchacze mogą implementować jeden lub kilka z tych interfejsów, zyskując 12 w ten sposób zdolność do obsługi wybranych zestawów rodzajów zdarzeń. Przykład - nasłuch przycisku import javax.swing.*; import java.awt.*; import java.awt.event.*; // by obsługiwać zdarzenia class Przycisk extends JFrame implements ActionListener { JButton b1 = new JButton("Przycisk 1"); JButton b2 = new JButton("Przycisk 2"); JTextField t = new JTextField(20); Przycisk(){ setLayout(new GridLayout(3,1)); b1.addActionListener(this); b2.addActionListener(this); getContentPane().add(t); getContentPane().add(b1); getContentPane().add(b2); pack(); setVisible(true); } public void actionPerformed(ActionEvent e){ t.setText(e.getActionCommand()); } public static void main(String[] args){ new Przycisk(); } } 13 Działanie nasłuchu Zdarzenie „akcja” tworzy obiekt ActionEvent Interfejs nasłuchu ActionListener zawiera deklarację void actionPerformed(ActionEvent e); implementacja KLIK [ ŻRÓDŁO] Przycisk (JButton b) Klasa słuchacza: CALL class Przyciski . . . implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Wciśnięto przycisk"); } } Obiekt słuchacza akcji (może być również kontenerem przycisku) Przyłączenie b.addActionListener(p) Przyciski p = new Przyciski(); 14 Uwagi 1. Każdy obiekt Javy może być Słuchaczem, jeśli tylko jego klasa implementuje jakiś zestaw interfejsów nasłuchu 2. Zdarzenia dla danego Źródła są generowane tylko wtedy, gdy do źródła przyłączony jest Słuchacz 3. Metody obsługi zdarzeń (tu: actionPerformed) nie zwracają żadnych wyników. 4. Ze względu na strukturyzację zdarzeń i interfejsów nasłuchu kod jest odporny na błędy: np. metoda actionPerformed musi mieć jako argument zdarzenie klasy ActionEvent 5. Do badania właściwości zdarzenia stosuje się wyłącznie metody (w klasach zdarzeń nic ma publicznych pól). Przykładem może być getActionCommand(), która zwraca napis skojarzony ze zdarzeniem ACTION_PERFORMED (tu – napis na przycisku). 6. Zdarzenia są filtrowane na niskim, ukrytym poziomie. Upraszcza to pisanie kodu. 15 Zdarzenia fizyczne i semantyczne java.util.EventObject +-- java.awt.AWTEvent +-- ActionEvent +-- AdjustmentEvent +-- ItemEvent +-- TextEvent +-- ComponentEvent +-- ContainerEvent +-- FocusEvent +-- InputEvent | +-- KeyEvent | +-- MouseEvent +-- WindowEvent ComponentEvent -> zdarzenia fizyczne (np. kliknięcie myszą czy zmiana rozmiaru komponentu). Wszystkie te zdarzenia dotyczą komponentów (stąd uszczegółowiony sposób identyfikacji źródła metoda getComponent() z klasy ComponentEvent zwracająca referencję do obiektu klasy Component). Zdarzenie semantyczne, mające znaczenie zależne od kontekstu: •ActionEvent - akcja •ItemEvent - zmiana stanu elementu wybieralnego •AdjustmentEvent - dostosowanie obiektu dostosowywalnego •TextEvent - zmiana tekstu obiektu tekstowego 16 Interfejsy nasłuchu zdarzeń Klasa zdarzenia Zdarzenie – stałej identyfikującej zdarzenie ActionEvent AdjustmentEve nt ItemEvent TextEvent MouseEvent ACTION_PERFORMED ADJUSTMENT_VALUE_CHANGED Nazwa metody obsługi danego zdarzenia void metoda(Klasa_zdarzenia k) actionPerformed adjustmentValueChanged ITEM_STATE_CHANGED itemStateChanged TEXT_VALUE_CHANGED textValueChanged MOUSE_ENTERED mouseEntered MOUSE_EXITED mouseExited MOUSE_PRESSED mousePressed MOUSE_RELEASED MOUSE_CLICKED mouseReleased mouseClicked MouseMotionE MOUSE_MOVED mouseMoved vent MOUSE_DRAGGED mouseDragged KeyEvent KEY_PRESSED keyPressed KEY_RELEASED keyReleased KEY_TYPED keyTyped Nasłuch ActionListener AdjustmentListener ItemListener TexlListener MouseListener MouseMotionListener KeyListener 17 Interfejsy nasłuchu zdarzeń Klasa zdarzenia KeyEvent Zdarzenie KEY_PRESSED KEY_RELEASED KEY_TYPED FocusEvent FOCUS_GAINED FOCUS_LOST ContainerEvent COMPONENT_ADDED COMPONENT_REMOVED ComponentEvent COMPONENT_HIDDEN COMPONENT_SHOWN COMPONENT_MOVED COMPONENT_RESIZED WindowEvent WINUOW_ACTIVATED WINDOW_DEACTIVATED WINDOW_ICONIFIED WINDOW_DEICONIFIED WINDOW_OPENED WINDOW_CLOSING WINDOW_CLOSED Metody obsługi Nasłuch keyPressed keyReleased keyTyped focusGained focusLosed componentAdded componentRemoved componentHidden componentShown componentMoved componentResized windowActivated windowDeactivated windowIconified windowDeiconified windowOpened windowClosing windowClosed KeyListener FocusListener ContainerListener ComponentListener WindowListener 18 Implementacja Python PyQt – sygnały i sloty Charakterystyczną cechą Qt jest mechanizm sygnałów i slotów będący sposobem porozumiewania się elementów aplikacji. Sygnał emitowany jest w przypadku wystąpienia określonej akcji (np. przyciśnięto przycisk). Slot to funkcja połączona (za pomocą QtCore.QObject.connect()) z określonym sygnałem i jest wykonywana, gdy taki sygnał zostanie wyemitowany. Połączenia sygnałów i slotów: - sygnał może być połączony z wieloma slotami - sygnał może być połączony z innym sygnałem - slot może być połączony z wieloma sygnałami - w PyQt sygnały są emitowane przez metodę QtCore.QObject.emit() - połączenia mogą być bezpośrednie - synchroniczne lub kolejkowane asynchroniczne - można tworzyć połączenia między wątkami - sygnały są rozłączane za pomocą metody QtCore.QObject.disconnect() 19 Implementacja JavaScript Rejestrowanie zdarzenia inline polega na określeniu zdarzenia wewnątrz znacznika. <a href="strona.html" onclick="alert(' Kliknąłeś! ')">kliknij</a> Rejestrowanie zdarzenia w sekcji skryptu (tu: z zastosowaniem funkcji anonimowej document.getElementById('Przycisk').onclick = function() { alert('zostałem klikniety!'); } Rejestrowanie zdarzenia z użyciem addEventListener var element = document.getElementById('Przycisk'); element.addEventListener('click', startDragDrop, false); element.addEventListener('click', wypiszCos, false); element.addEventListener('click', function() {this.style.color = 'red'; }, false); 20 JavaScript – Kaskadowe wykonywanie zdarzeń Załóżmy istnienie zagnieżdżonych bloków <div style="..." id="blok1">1 <div style="..." id="blok2">2 <div style="..." id="blok3">3</div> </div> </div> <script type="text/javascript"> document.getElementById('blok1').onclick = function() { alert('Kliknąłeś mnie!')}; </script> Kliknięcie w element wewnętrzny powoduje po wykonaniu zdarzenia przesłanie go do elementu otaczającego (tu zawsze wywoła się alert) Wyłączenie tego zjawiska realizujemy poprzez funkcję stopPropagation function stopBubble(e) { if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); } document.getElementById('blok31').onclick = function() { alert('Kliknąłeś mnie!')} document.getElementById('blok32').onclick = function(e){ stopBubble(e);} document.getElementById('blok33').onclick = function(e){ stopBubble(e);} 21 Architektura MVC Architektura MVC jest jedną z implementacji delegacyjnego modelu obsługi zdarzeń. Zakłada podział składników systemu aplikacyjnego na trzy kategorie: –Model (komponenty modelu): komponenty reprezentujące dane, na których operują aplikacje; komponenty modelu oferują także metody dostępu do danych –View (komponenty prezentacji): komponenty reprezentujące wizualizację (prezentację) danych dla użytkownika; komponenty prezentacji pobierają dane od komponentów modelu, a następnie wyświetlają je na ekranie użytkownika –Controller (komponenty sterujące): komponenty przechwytujące żądania użytkowników i odwzorowujące je w wywołania metod komponentów modelu; następnie komponenty sterujące przekazują sterowanie do komponentów prezentacji Implementacja JavaServlet Przeglądarka WWW wysyła sparametryzowane żądanie uruchomienia serwletu Controller (1). Po wywołaniu, serwlet Controller analizuje żądanie i tworzy obiekty wymagane do dalszego przetwarzania żądania – m.in. obiekty klas zewnętrznych JavaBeans realizujących funkcję Model. W obiektach tych wywoływane są funkcje logiki biznesowej (2). Następnie, serwlet Controller przekazuje żądanie do odpowiedniej strony Java Server Pages, realizującej funkcję View (3). Komponent View pobiera wyniki pracy funkcji logiki biznesowej z obiektów Model (4). Komponent View generuje wynikowy dokument dla użytkownika (5). 23 Wzorzec MVP Wzorzec architektury interfejsu użytkownika zarezerwowany dla aplikacji typu Rich Client Application (lub RIA) Interakcja View Presenter model • Widok i prezenter są połączone 1-1 (jeden prezenter ma jeden widok) • Logikę obsługuje prezenter, to on rejestruje się na powiadomienia, tworzy model • Widok ma tylko warstwę prezentacji (sterowaną przez prezenter) • Widok jest wstrzykiwany do prezentera przez interfejs 24 Implementacja JavaFX VIEW jest to Stage – obiekt dostarczany przez FX posiadający obiekt Scene, będący korzeniem drzewa widoku. Scenę definiujemy przez plik fxml. CONTROLLER – klasa implementująca interfejs Initializable. MODEL – klasa zawierająca stan aplikacji public class Stock { protected String symbol; protected String name; public Stock(String symbol, String name) { this.symbol = symbol; this.name = name; } public String getSymbol() { return symbol; } public String getName() { return name; } } 25 Implementacja JavaFX Interfejs i jego implementacja - model public interface StockPriceUpdatedListener { public void priceUpdated( Stock stock, double price ); } public class StockModel implements StockPriceUpdatedListener { /*deklaracje pól... */ public StringProperty addStock( Stock stock ) { /*metoda dodania produktu */ } @Override public void priceUpdated(Stock stock, double price) { /*metoda altualizacji produktu */ } } 26 Implementacja JavaFX Widok – tworzony jest poprzez SceneBuilder. Jego wynikiem jest plik FXML. Między innymi zawiera ten plik instrukcje typu <Button fx:id="subscribeButton" layoutX="136.0" layoutY="153.0" mnemonicParsing="false" onAction="#subscribeButtonClicked" text="Subscribe" /> Fragmenty klasy Kontrolera public class StockPricePanelController implements Initializable { @FXML protected Button subscribeButton; protected StockModel stockModel; @FXML public void subscribeButtonClicked( ActionEvent event ) { /* metoda obsługująca kliknięcie */ } } 27