PROE wykład 8 Interfejs użytkownika (GUI) dr inż. Jacek Naruniec
Co to jest GUI? Graphical User Interface (GUI), czyli po prostu interfejs użytkownika (UI). Najpopularniejsze GUI dla C++: Qt WinAPI MFC GTK.NET
WinAPI Windows API (API Application Programming Interface). Bardzo dużo możliwości, natywne środowisko. Początkowo trudne do opanowania. Nie jest wieloplatformowe. Bardzo szybkie brak narzutów.
WinAPI Najprostszy program: Jedynie tworzy wiadomość, która blokuje program do czasu aż okno nie zostanie zamknięte. WinMain zamiast funkcji main. większość funkcjonalności WinAPI uchwyt do naszej aplikacji zawsze NULL, nieistotne argumenty programu określa jak jest tworzone okno (widoczne, niewidoczne, etc.) -Wszystkie typy z przedrostkiem H (HINSTANCE, HMENU, itp.), to uchwyty, które identyfikują poszczególne elementy. Można je rozumieć jako wskaźniki na elementy. - LPSTR jest zdefiniowany jako char*. LP określa, że będzie to wskaźnik (LONG POINTER).
WinAPI MessageBox Tak jak wiele funkcji WinAPI posiada znacznie więcej możliwości. Dokładną składnię i możliwości wszystkich funkcji WinAPI łatwo znaleźć w MSDN. Inne wywołanie okienka wiadomości:
Tworzenie okna Aby stworzyć własne okno aplikacji (a nie tylko message box a), należy: Zarejestrować klasę okna (aby docierały do niego komunikaty). Stworzyć okno. Wyświetlić okno. Obsługiwać kolejkę komunikatów dopóki nie nastąpi polecenie zamknięcia aplikacji. W WinAPI wszystko działa na zasadzie komunikatów przesyłanych do funkcji, która umie je obsłużyć.
Tworzenie okna Rejestracja klasy okna:
Tworzenie okna Tworzenie i wyświetlenie okna:
Tworzenie okna WinMain:
Tworzenie okna GetMessage pobiera kolejną wiadomość z kolejki komunikatów. TranslateMessage tłumaczy wirtualne przyciski na odpowiedniki znakowe. Nie jest tutaj konieczne. DispatchMessage rozsyła wiadomość tam, gdzie jest to wymagane do odpowiednich funkcji okien. Jest to podstawa działania programów WinAPI kolejka komunikatów
Tworzenie okna Kto te komunikaty ma obsługiwać? My! Co zrobić w przypadku komunikatu o zamknięciu? Co zrobić w przypadku komunikatu o zniszczeniu okna? Co zrobić w przypadku innych komunikatów? wykonaj domyślną operację.
Tworzenie okna Efekt: Proste okienko. Standardowe przyciski.
Kontrolki Do każdej kontrolki, jak i do większości elementów WinAPI, będziemy mogli uzyskać uchwyt za pomocą którego możemy się do niej odnieść. Kontrolkę tworzymy podobnie jak okno wpisując typ klasy odpowiadający kontrolce (np. BUTTON, COMBOBOX, LISTBOX, STATIC, ). Stwórzmy przycisk (np. w funkcji tworzącej okno, po stworzeniu okna):
Kontrolki Kontrolkami będą elementy, które możemy umieścić w naszym oknie, czyli np.: Przyciski Pola edycyjne Pola stałe Listy rozwijane
Kontrolki Nasz przycisk jeszcze nic nie robi. Każda akcja w oknie powoduje wygenerowanie komunikatu. Naciśnięcie przycisku spowoduje wysłanie wiadomości WM_COMMAND (przez przycisk który otrzyma wiadomość o naciśnięciu przycisku) Musimy przechwycić wiadomość o naciśnięciu przycisku!
Kontrolki Co jeśli mamy więcej przycisków? Możemy zidentyfikować nadawcę poprzez parametr przesyłany wraz z wiadomością lparam (który jest wskaźnikiem): w tworzeniu okna: w obsłudze wiadomości:
Kontrolki Zamiast uchwytów (musielibyśmy wszystkie trzymać) możemy stosować identyfikatory okien. Tutaj zostały umieszczone w innym pliku (np. indentifiers.h), dołączonym w pliku z WinMain em (#include identifiers.h ). Przy tworzeniu kontrolki nadajemy jej identyfikator. Zakładając, że jest oknem pochodnym (WS_CHILD), to identyfikator wpisujemy w polu uchwytu do Menu (wiem, mało intuicyjne )
Kontrolki Teraz możemy już korzystać z identyfikatorów. Identyfikator może być sprawdzony w funkcji obsługi wiadomości z parametru wparam (w przeciwieństwie do lparam, który jest wskaźnikiem): Staramy się, aby każda kontrolka miała swój unikalny identyfikator!
Kontrolki Inne kontrolki: Pole edycyjne wskazanie, że tworzymy pole edycyjne (nazwa klasy) udogodnienia do wielolinijkowych tekstów
Kontrolki Zmodyfikujmy obsługę naszego pierwszego przycisku tak, żeby pokazywał się tekst zawarty w polu edycyjnym, drugi przycisk - żeby je resetował. W tym celu musimy zmodyfikować naszą obsługę wiadomości (oczywiście korzystając z identyfikatora pola edycyjnego): GlobalAlloc służy do dynamicznej alokacji pamięci.
Kontrolki Lista elementów: Dodawanie elementów do listy odbywa się poprzez wysłanie jej wiadomości o tym, aby element został dodany do listy.
Kontrolki Odczytanie wartość wybranej pozycji listy: DlgDirSelectEx jako drugi parametr przyjmuje LPTSTR. Należy pamiętać, że jest to (w przypadku aplikacji multibyte) po prostu char*.
Kontrolki Co jeśli chcielibyśmy zmienić domyślne zachowanie kontrolki? Konkretna wiadomość dochodzi do tego okna (kontrolki też rozumiemy jako okna), na którym mamy aktualnie uwagę (focus). Oznacza to, że każda kontrolka ma swoją obsługę komunikatów, w której: Wykonuje swoje standardowe operacje. Może wysyłać inne wiadomości tak jak WM_COMMAND, które my odczytaliśmy dla przycisku. Oznacza to, że WM_COMMAND tak naprawdę wygenerował przycisk w swojej obsłudze zdarzenia naciśnięcia przycisku. Abyśmy chcieli zmienić domyślne zachowanie kontrolki musielibyśmy sami przedefiniować jej obsługę komunikatów (dalej będziemy to robić dla innej kontrolki)
Wyświetlanie obrazu Funkcje rysowania bazują na kontekście urządzenia (device context). Uchwyt do kontekstu urządzenia to HDC Jeśli pobierzemy kontekst danego urządzenia, to możemy na nim rysować. Na końcu należy pamiętać o zwolnieniu kontekstu urządzenia. Kontekst pobieramy po stworzeniu okna. pobranie uchwytu do kontekstu urządzenia narysowanie w kontekście elipsy zwolnienie uchwytu
Wyświetlanie obrazu Co spowoduje minimalizacja i maksymalizacja okna? nasz okrąg zniknął. W momencie minimalizacji/maksymalizacji do okna wysłana zostaje wiadomość narysuj jeszcze raz okno, a dokładnie WM_PAINT. To samo dzieje się, jeśli okno przesuniemy poza obrys ekranu i potem powrócimy. Wniosek: obsługa standardowa WM_PAINT poprawnie przerysowuje ikonki, natomiast niepoprawnie wszystkie dodatkowe elementy. Musimy poprawić obsługę komunikatu WM_PAINT!
Wyświetlanie obrazu Obsługa rysowania musi wywołać BeginPaint, które pobiera uchwyt do kontekstu urządzenia i EndPaint, która go zwalnia. Za każdym razem gdy zaistnieje potrzeba przeładowania obrazka, to zostanie wygenerowany komunikat WM_PAINT. BeginPaint zwraca kontekst urządzania.
Wyświetlanie obrazu Teraz spróbujmy wczytać obraz. Zakładamy istnienie obrazka w określonym przez nas katalogu. Najpierw definiujemy panel, w którym będziemy chcieli wyświetlić obrazek, czyli:
Wyświetlanie obrazu Następnie wczytujemy bitmapę z dysku, u nas po prostu w funkcji WinMain. W celu oszczędzania pamięci można wywołać DeleteObject do wyczyszczenie pamięci (jeśli czyszczenie tylko na końcu programu, to DeleteObject wykona się automatycznie).
Wyświetlanie obrazu Naszą funkcję rysowania umieśćmy na razie w obsłudze komunikatu WM_PAINT głównego okna:
Wyświetlanie obrazu Ale co zrobić żeby nasz obrazek wyświetlał się w naszym panelu? Możemy Stworzyć własną funkcję obsługi komunikatów panelu, która będzie odpowiednio obsługiwać WM_PAINT. Musimy także wskazać oknie panelu wskaźnik do funkcji obsługi komunikatów.
Zasoby (resources) Zasoby są to pewne dane, które zostaną włączone w nasz plik wykonywalny (exe). Informacje o nich są przechowywane w plikach.rc. Najprościej z nich skorzystać za pomocą edytora (View->Other Windows->Resource View) Stwórzmy w pliku zasobów okno dialogowe:
Zasoby Włączamy toolbox (View->ToolBox) i możemy w zwykłym edytorze dodawać nasze elementy. Dobrze wejść we właściwości (PPM na okienko, Properties) i zmienić ID na jakiś własny, np. IDD_NASZDIALOG)
Zasoby Wczytywanie w naszym pliku zasobów na podstawie identyfikatora (tutaj ustawiłem IDD_NASZDIALOG) odbywa się poprzez funkcję MAKEINTRESOURCE. Wyświetlenie dialogu z zasobów to funkcja DialogBox:
Zasoby Brakuje jeszcze dwóch elementów: Należy do nagłówków dodać plik resource.h gdzie znajdują się standardowe identyfikatory (np. IDD_NASZDIALOG) aby je przejrzeć, należy w Resource View kliknąć PPM->Resource Symbols Należy dodać procedurę obsługi komunikatów stworzonego okna dialogowego (tutaj określona jako DlgProc) W resource.h mamy także przypisane identyfikatory kontrolek które stworzyliśmy można z nich korzystać (GetDlgItem zwraca nam uchwyt do kontrolek).
Zasoby
Zasoby Do innych zasobów należą Menu wyboru Ikonki Obrazki (zamiast wczytywania z pliku) Akceleratory (tablica w której zawarte są skróty klawiszowe do menu) Kursory Informacje o wersji programu
WinAPI O czym jeszcze można się nauczyć? Inne funkcje rysowania (GDI) Akceleratory Zdarzenia myszy, klawiatury MDI masę innych rzeczy Co warto jeszcze wiedzieć? WinAPI nie trzeba się uczyć na pamięć. Zawsze zaglądamy w dokumentację po gotowce/wzorce. Tak naprawdę WinAPI nie jest obiektowe ale to akurat nie musi być wadą.
WinAPI Czy WinAPI jest godne polecenia? + - wysoka wydajność nie wymaga instalacji, konfiguracji natywne rozwiązania stopień skomplikowania ale zależy co chcemy zrobić, proste rzeczy często łatwiej zrobić w czymś innym (np. Qt) działa tylko dla Windowsa rozwiązania często nieintuicyjne
Qt Jedna z najpopularniejszych obecnie bibliotek do tworzenia GUI. Intuicyjne rozwiązania. Wieloplatformowa. Obiektowa. Ładny edytor okienek (QtDesigner). QtCreator jako środowisko programistyczne (ze swojej strony nie polecam, chociaż zdania są podzielone) Do Visual Studio wymaga QT Visual Studio Add-in (http://download.qt.io/official_releases/vsaddin/).
Prosty program Qt wymaga zainstalowania i skonfigurowania środowiska, tutaj nie będziemy się tym zajmować. O bibliotekach będzie na kolejnym wykładzie, tutaj zakładamy, że potrafimy to zrobić. Najprostsza aplikacja: pętle komunikatów, inicjalizacja deklaracje klasy przycisku
Prosty program Kontrolki Qt dziedziczą po klasie QWidget, która zawiera podstawowe operacje manipulacji oknem, dlatego mogliśmy wyświetlić przycisk jako aplikację. QWidget można po prostu rozumieć jako kontrolkę. rodzic widgeta
Prosty program Zdarzenia w Qt realizowane są poprzez mechanizm slotów (czy gniazd jak kto woli) i sygnałów. Zdarzenie powoduje wygenerowanie sygnału, który zwrotnie uruchamia funkcję slotu. Istnieją pewne standardowe sygnały, np. naciśnięcia przycisku:
Prosty program Z dokumentacji Qt:
Prosty program Tym razem do stworzenia projektu wykorzystamy wizarda Qt (przy zainstalowanym QtVS Add-in):
Prosty program W eksploratorze wygenerowało nam się kilka plików, aplikacja na razie generuje tylko okno. interfejs użytkownika, otwierając uruchomimy QtDesigner
Prosty program
Prosty program Klasa naszego okna powinna być w formie obiektowej. Możemy sami definiować swoje sloty/sygnały. Klasa dziedziczy po QMainWindow(a wyżej po QWidget) konieczne aby tworzyć własne sloty/sygnały nasz nowy slot
Prosty program Powiązanie slotów w designerze: Powiązanie slotów. Wybór sygnału. Wybór slotu.
Prosty program
Qt Dalsza część Qt na kolejnym wykładzie.