13

Praca magisterska - Pośrednictwo użytkowe systemu MS DOS

Etapy projektowania obiektów typu USERCONTROL

Pierwsze prace projektowe związane z pośrednictwem użytkowym nie były prowadzone obiektowo. Dysponując zbiorem funkcji, napisanych parę lat temu, stworzono podwaliny nowego środowiska obiektowego. W pracy korzystano z kompilatora języka C++ firmy Borland w wersji 3.1 oraz prostego edytora tekstowego dostarczanego z pośrednictwem użytkowym "Norton Commander". Nie korzystano ze środowiska programistycznego dostarczanego wraz z kompilatorem. Uznano że mniej narażonym na błędy sposobem projektowania, będzie korzystanie z kompilatora zewnętrznego bcc i programu make. Wiązało się to z koniecznością opanowania reguł tworzenia zbiorów typu makefile. Taki sposób tworzenia programów jest bardzo dobrze znany i użytkowany na platformach opartych na systemie UNIX. Pozwala on na pełniejszą kontrolę procesu kompilacji i łączenia programu.

Projektowanie obiektów typu USERCONTROL rozpoczęto od stworzenia obiektu zarządzającego powierzchnią ekranu.

Klasa Wnd

Zadaniem obiektów tej klasy było deklarowanie okna na ekranie. Z operacją "deklarowanie okna" , wiąże się m.in. przydzielenie pamięci operacyjnej, przechowującej oryginalną zwartość okna i skopiowanie powierzchni ekranu w to miejsce.

W pierwszych fazach projektu bardzo intensywnie korzystano z gotowych bibliotek zapewniających wizualizację informacji na ekranie. Korzystając m.in. z funkcji cputs, cprintf, getch , realizowano całą komunikację programu z otoczeniem.

Obiekty klasy Wnd wykorzystywano przy tworzeniu wirtualnych okien na ekranie komputera. Okna można było tworzyć, chować, pokazywać i usuwać. W ciągu dalszego rozwoju, wzbogacono klasę Wnd o metody cieniujące. Metody cieniujące, to funkcje związane z tworzeniem na ekranie złudzenia wielopłaszczyznowości ekranu i padającego z lewego górnego rogu światła. Klasa Wnd w końcowej fazie projektu stała się klasą abstrakcyjną.

Klasa jest abstrakcyjna, gdy posiada choć jedną czystą metodę wirtualną. Metodę taką deklarujemy następująco:


virtual void show() = 0;


Deklaracja taka podlega dziedziczeniu, to znaczy, że każda klasa potomna dziedziczy naszą czystą metodę wirtualną, a więc staje się również klasą abstrakcyjną. Nakłada to na programistę obowiązek uściślenia definicji takiej metody dla każdej klasy potomnej, której obiekty mają być dostępne.

Klasa Events

Klasa ta pojawiła się na początku tworzenia projektu wraz z klasą Wnd. Obiekty tej klasy obsługują zdarzenia, gospodarują sygnałami oraz obsługują przerwania dochodzące z myszy, reagując na następujące zdarzenia:

Projektując klasę, starano się aby konstruowany program obciążał zasoby komputera w mniejszym stopniu niż pośrednictwo "Norton Commander". Narzucające się rozwiązanie, zakładające że program będzie bez przerwy działać w pętli, oczekując na zdarzenia z myszy lub z klawiatury, było nie do przyjęcia. System operacyjny, oddając czas procesora programowi, byłby zmuszony zastosować kryteria czasowe przy wywłaszczaniu procesu z przydzielonych zasobów, a program i tak większość czasu spędziłby na bezproduktywnej pętli.

Należało zastosować inne rozwiązanie.

Postanowiono że proces będzie oczekiwać na zdarzenia z klawiatury. System operacyjny po pojawieniu się żądania ze strony procesu, pobrania informacji z klawiatury, jest w stanie wywłaszczyć proces z zasobów, na czas kiedy żadna informacja nie znajduje się w buforze klawiatury. Jednak stosując ten sposób, pojawił się problem odbioru informacji z myszy. Problem ten rozwiązano przejmując wektor przerwań myszy i "doczepiając" własną procedurę. Procedura po pojawieniu się zdarzenia z myszy, umieszczała w buforze klawiatury pewien znak. Bufor klawiatury pełni rolę kolejki zdarzeń. Takie rozwiązanie okazało się skuteczne i ujednoliciło obsługę myszy i klawiatury w programie.

Klasa Events, została wyposażona w późniejszej fazie projektu w zmienne stanu. Jako że wszystkie obiekty typu USERCONTROL dziedziczą pola i metody po klasie Events naturalnym było umieszczenie w niej kluczowych informacji o stanie aplikacji.


DrawObjectDrawObjectDrawObject





Ilustracja 5.1 Kolejność dziedziczenia obiektów Events i Wnd

Zmienne stanu wszystkich obiektów pochodnych klasie Events musiały korzystać z jednej kopii zmiennych stanu. Jako zmienne stanu programu przyjęto:

Obiekty klasy Events zostały wyposażone w metody pozwalające na nadawanie sygnałów. Wszystkie obiekty USERCONTROL komunikują się przy pomocy sygnałów. Sygnał, to jedna z zmiennych stanu. Kontrolą, odbieraniem i obsługą sygnałów zajmują się obiekty klasy Control.

Ognisko to zmienna przechowująca informacje dotyczące zgrupowanych obiektów. Tylko jeden z grupy obiektów jest właścicielem ogniska. Obiekt będący właścicielem ogniska ma pierwszeństwo przy odbiorze zdarzeń z klawiatury.

Klasa Control

Powstanie klasy Control wynikło z potrzeby zaistnienia w szkielecie programu obiektu zajmującego się grupowaniem i obsługą obiektów wyprowadzonych z klasy Wnd.


DrawObject

DrawObjectDrawObject


DrawObject

DrawObject



Ilustracja 5.2 Hierarchia klas obiektów

Obiekt potomny klasy Control jest wyposażony w metody pozwalające na kontrolę przepływu ogniska w aplikacji. Obiekt ten, ma w swoim wnętrzu tablicę wskaźników do obiektów potomnych klasy Wnd. Wykorzystując fakt, że obiekty potomne Wnd muszą mieć zdefiniowane funkcje wirtualne, obiekt Control może grupować i wywoływać odpowiednie metody zarejestrowanych obiektów. Rejestracja obiektów jest potrzebna do utworzenia tablicy obiektów we wnętrzu obiektu Control.

Klasa Control wyposażona została także w metody pobierające zdarzenia z kolejki zdarzeń. Pobranie zdarzenia wiąże się z pobraniem informacji o stanie klawiatury i myszy.

Konstruktor klasy Control kopiuje też do swojego wnętrza aktualną wartość ogniska aplikacji. Dzięki temu można utworzyć kilka obiektów klasy Control i wywoływać je sekwencyjnie. Istnienie destruktora wirtualnego w obiektach pochodnych klasy Wnd, pozwala obiektom zarejestrowanym w obiekcie pochodnym klasy Control, na automatyczne usuwanie obiektów zarejestrowanych.

Klasa Button

Obiekty klasy Button były pierwszymi obiektami wizualizującymi swój stan wewnętrzny na ekranie.

Obiekty tej klasy umożliwiają zmianę swojego stanu przy pomocy klawiatury lub myszy.

DrawObjectDrawObjectDrawObject

DrawObjectDrawObject


Ilustracja 5.3 Hierarchia dziedziczenia dla obiektu klasy Button


Obiekty klasy Button mają zdefiniowane w swoim wnętrzu metody Show i Action.

Metoda Show ma za zadanie aktualizować postać obiektu na ekranie w zależności od zmiennych stanu danego obiektu. Zmiennymi stanu obiektu Button są:

Postać jest aktualizowana w zależności o zmiany zmiennych stanu.

Zadeklarowano 3 style przycisków zbudowanych na podstawie obiektu Button.



Ilustracja 5.4 Style BUMP, SPLASH i PRIMITIVE przycisków obiektu klasy Button


Obiekty klasy Button zachowują się różnie w zależności od wybranego stylu.

Metoda Action to druga funkcja wirtualna. Jej zadaniem jest wysyłanie sygnałów i obsługa zdarzeń przychodzących z klawiatury i myszy. Podczas testów, sygnał był nadawany za pomocą metod obiektu Events jak i przy pomocy sygnału dźwiękowego. Przycisk po naciśnięciu wydawał sygnał. Zdarzenia z klawiatury też mogą powodować pojawienie się sygnału z przycisku. Przycisk "opuszczony" za pomocą klawiatury po krótkim okresie czasu wraca do stanu "podniesiony". Synchronizacja obrazu przycisku na ekranie i zmiennych stanu przycisku, następuje po ponownym wywołaniu metody Show.

Klasa TrivEdit

Wśród obiektów typu USERCONTROL nie może zabraknąć obiektów odpowiedzialnych za edycję tekstów. Projektując klasę TrivEdit tworzono podwaliny pod tego typu obiekty. Nazwa klasy zaczynająca się od słowa Triv, sugeruje że mamy do czynienia z klasą fundamentalną. Przez klasę fundamentalną rozumiemy zbiór metod, których zadaniem jest dostarczenie funkcji pozwalających operować na danych, ale tworzenie obiektów tej klasy nie ma głębszego sensu.

Klasa TrivEdit dostarcza metod operujących na ciągach znaków. Zadeklarowane w tym obiekcie funkcje pozwalają na wstawianie i usuwanie tekstu z bufora. Bufor, który poddawany jest edycji, w razie potrzeby potrafi zwiększyć swoją objętość. Ta klasa interpretuje bufor jako ciąg edycyjny. Przez ciąg edycyjny rozumiemy ciąg znaków, z którym jest skojarzona pozycja kursora. Projektowano klasę TrivEdit pod kątem wykorzystania obiektów zbudowanych w oparciu o jej metody przy konstrukcji obiektów edytora pełnoekranowego oraz pośrednictwa tekstowego. Ostatecznie wykorzystano ją przy konstrukcji pośrednictwa tekstowego.

Klasa ShellEdit

Klasa ShellEdit była projektowana pod kątem wykorzystania jej obiektów do konstrukcji pośrednictwa tekstowego. Obiekty tej klasy dziedziczą wielobazowo po obiektach klasy Wnd oraz TrivEdit


DrawObjectDrawObjectDrawObjectDrawObject

DrawObjectDrawObjectDrawObject



Ilustracja 5.5 Hierarchia dziedziczenia klas dla obiektu ShelEdit

Na ekranie wizualizowany jest jako czarny pasek wypełniony ciągiem edycyjnym w kolorze białym. Ciąg edycyjny można poprzedzić komentarzem. Ta własność klasy jest wykorzystywana przy wizualizacji aktualnego katalogu w pośrednictwie tekstowym.

Na ekranie wizualizowana jest zazwyczaj tylko część bufora poddawanego edycji. Jeśli tekst poddawany edycji nie mieści się na ekranie, pojawi się informacja o ukrytej reszcie tekstu.

Metody Show i Action zostały skonstruowane tak, aby można było wykorzystując kombinacje klawiszy, edytować tekst i wizualizować okno ciągu edycyjnego. Przez okno ciągu edycyjnego rozumiemy otoczenie ciągu edycyjnego w którym znajduje się kursor.

Klasa ShellEdit pozwala w czasie tworzenia, na zadeklarowanie sygnału jaki się ma pojawić w przypadku naciśnięcia klawisza Enter. Docelowo powinno się taką możliwość wprowadzić dla większości aktywnych obiektów a w szczególności dla obiektów klasy Button.

Obiekty klasy ShellEdit mogą być inicjowane ciągiem edycyjnym. Zainicjowany obiekt przedstawia ciąg edycyjny w kolorze turkusowym. Dane w kolorze turkusowym informują że są "brudne". Przez pojęcie "brudne dane" rozumiemy ciąg edycyjny, który zostanie z obiektu usunięty po wprowadzeniu dowolnego nowego znaku. "Brudne dane" możemy poddać edycji. Jeśli rozpoczniemy ich edycję zmienią kolor na biały.

Klasa TrivContainer

Projektując obiekty typu USERCONTROL zaszła konieczność skorzystania z obiektu klasy kontener. Producent środowiska programistycznego, firma Borland, dostarcza gotowe rozwiązanie. Jednak okazało się ono nieprzydatne ze względu na swoją objętość. Pracę nad własnym rozwiązaniem rozpoczęto od analizy przykładów zaczerpniętych z książki [2]. Konstrukcja własnej klasy zapewniającej własności kontenera, okazała się realizowalna. Zbudowano prostą klasę przechowującą wskaźniki do obiektów niewiadomego typu. W jednym kontenerze mogły być zgrupowane różnego rodzaju obiekty. Kontener ulegał automatycznemu powiększeniu w razie konieczności. Jednak usuwaniem danych z kontenera musiał zajmować się już obiekt pochodny. Niedopełnienie warunków usunięcia zawartości kontenera powodowało sukcesywne zwiększanie się zablokowanej pamięci przez system operacyjny. W końcowej fazie projektu wzbogacono obiekt TrivContainer o możliwość usuwania obiektów również z wnętrza kontenera.

Klasa FileContainer

Rozwijając klasę TrivContainer stworzono klasę FileContainer. Nazwa sugeruje obiekt przeznaczony do obsługi plików. W istocie obiekt ten nadaje się do obsługi plików. Jednak nie jest konieczne obsługiwanie tym obiektem plików. Przez obsługę plików rozumiemy interpretację pliku tekstowego jako ciągu linii zakończonych znakiem Enter i odczyt ich do kontenera.


DrawObjectDrawObject

DrawObject


Ilustracja 5.6 Hierarchia dziedziczenia obiektów klasy FileContainer

Obiekt klasy FileContainer przechowuje dodatkowe informacje dotyczące odczytanych linii tekstu. Informacje te były konieczne przy ich wizualizacji w panelach. Dotyczy to wyjaskrawiania, przywracania do stanu początkowego czy interpretacji pewnych linii jako komentarz.

Utworzenie obiektu FileContainer nie wiąże się od razu z obsługą pliku. Obsługę pliku możemy wymusić metodą Load lub UpLoad. Istnieje możliwość dodania do kontenera elementów "ręcznie", przy pomocy metody AddItem.

Na tym poziomie abstrakcji nie trzeba już się przejmować usuwaniem zawartości kontenera. Obiekty przechowywane w kontenerze są ciągami tekstowymi i destruktor klasy FileContainer je usuwa.

Klasa została wyposażona w metody pozwalające na odszukiwanie i zaznaczanie elementów na podstawie wzorca. Przez wzorce rozumiemy ciągi tekstów "pasujące" lub nie do ciągów przechowywanych w kontenerze. Pojęcie "pasujące" oznacza że dany ciąg jest podciągiem tekstu w określonych ramach.

Dodatkowo zostały w klasie umieszczone metody pozwalające na manipulację dodatkową informacją dotyczącą danego ciągu. Mianowicie, ustalaniem typu danego elementu na bazie wzorca oraz zmianą informacji o zaznaczeniu lub odznaczeniu danego elementu.

Informacje o typie, stanie i interpretacji jako komentarz danego elementu zostały umieszczone w pierwszych dwóch polach ciągu tekstowego. Informacja ta jest tworzona tylko przy obsłudze pliku danym obiektem.

Klasa ItemList

Klasa ItemList to pierwsza i jedyna klasa dziedzicząca metody po klasie FileContainer.

Klasa ta została pomyślana jako narzędzie do tworzenia obiektów przypominających menu. W późniejszym okresie rozbudowano klasę do tego stopnia, że nadawała się do wizualizacji informacji o plikach i katalogach.

Obiekty tej klasy przedstawiają swoją zawartość na ekranie komputera i pozwalają na ich przeglądanie. Obiekty klasy ItemList dziedziczą wielobazowo po obiektach Wnd i FileContainer.


DrawObjectDrawObject

DrawObjectDrawObject

DrawObject


DrawObjectDrawObjectDrawObject

DrawObject


Ilustracja 5.7 Hierarchia dziedziczenia dla obiektu ItemList

Przedstawiając na ekranie obiekt ItemList umożliwiamy użytkownikowi zmianę podświetlenia aktywnego elementu za pomocą myszy lub klawiatury. Pierwszy i ostatni wiersz okna obiektu ItemList po naciśnięciu myszą przesuwają zawartość okna w górę lub w dół.


DrawObject

DrawObjectDrawObjectDrawObjectDrawObjectDrawObject



Ilustracja 5.8 Przykład wykorzystania obiektu ItemList

Po prawej stronie okna widać czarny pasek. Pasek stanu przyjmuje barwę białą jeśli podświetlenie aktywnego elementu znajduje się na końcu listy elementów lub czarną jeśli znajduje się na początku. Szerokość paska stanu może być ustalana podczas tworzenia obiektu. W skrajnym przypadku paska stanu może nie być.

Klasa ToolBar

W trakcie tworzenia pośrednictwa, wynikła potrzeba wykorzystania obiektu grupującego przyciski. Wykorzystując obiekt TrivContainer stworzono klasę, której obiekty do złudzenia przypomniały listwy przycisków. Obiekty umieszczane na listwie to obiekty klasy Button w stylu BUMP.


Ilustracja 5.9 Przykładowy obiekt klasy ToolBar

Obiekty klasy ToolBar były wykorzystywane przy tworzeniu pasków narzędziowych aplikacji. Wewnętrzne ogniskowanie pozwalało na rozdzielenie kontekstów aplikacji i obiektu klasy ToolBar.

Po pojawieniu się zdarzenia "przycisk w dół", obiekt wysyła sygnał.

Obiekty klasy ToolBar były pierwszymi obiektami zawierającymi inne obiekty.

DrawObject

DrawObjectDrawObject

DrawObjectDrawObjectDrawObject


DrawObjectDrawObject



Ilustracja 5.10 Hierarchia dziedziczenia dla obiektu klasy ToolBar

W kontenerze przechowywane są kolejne przyciski z listwy.

Metody Show i Action, najpierw przełączają zmienne stanu na wewnętrzne ognisko, a następnie, po wywołaniu metod obiektów wewnętrznych, ognisko obiektu nadrzędnego jest przywracane. Dzięki tej operacji, możliwe jest wewnętrzne ogniskowanie obiektu za pomocą kursora myszy. Takiej funkcji jeszcze nie spotkano w żadnym środowisku obiektowym.

Pomiędzy przełączaniem ognisk, metody te wykonują niewiele innych czynności, głównie zajmując się wywoływaniem metod Show i Action obiektów wewnętrznych.

Klasa Panel

W pośrednictwie użytkowym są dwa obiekty klasy Panel. Wynika to z założeń projektowych. Klasa Panel powstała z potrzeby posiadania obiektu grupującego belkę narzędziową i informację o plikach. Jest to klasa, której obiekty są właścicielami kilku innych obiektów. Do obiektu ItemList, przedstawiającego listę plików i katalogów, obiekt Panel umożliwia bezpośredni dostęp przy pomocy funkcji AccIL. W trakcie pracy, okazało się że wystąpiła potrzeba wykorzystania innego obiektu modalnego. Obiekt ten nazwano Box. Zostanie on opisany w kolejnym punkcie. Konstrukcja klasy Panel nie wymaga obligatoryjnie aby obiekt typu Box był jej częścią. W przyszłości należałoby przesunąć obiekt Box występujący w klasie Panel na wyższy poziom abstrakcji.


DrawObjectDrawObjectDrawObject

DrawObjectDrawObjectDrawObject

DrawObject



Ilustracja 5.11 Hierarchia dziedziczenia dla obiektu klasy Panel


Na ekranie po utworzeniu obiektu klasy Panel i wypełnieniu go informacją z pliku otrzymujemy poniższy widok.

DrawObject


DrawObjectDrawObjectDrawObjectDrawObjectDrawObject

Ilustracja 5.12 Wypełniony przykładową informacją obiekt klasy Panel

Belka informacyjna obiektu panel została utworzona w celu wizualizacji aktualnego katalogu z jakiego została pobrana informacja wypełniająca obiekt klasy ItemList poniżej. Obiekt klasy ItemList jest wypełniony informacją z pliku. Plik, który jest obsługiwany przez metody klasy ItemList, a uściślając, metody klasy ItemList odziedziczone po klasie FileContainer, deklarowany jest podczas tworzenia obiektu.

Poza wywoływaniem metod Show i Action obiektów wewnętrznych, metody Show i Action obiektu klasy Panel nie są wykorzystywane. Jedynie zmiana stanu elementów jest obsługiwana na poziomie metody Action obiektu klasy Panel. Do tego celu jest wykorzystywany obiekt klasy Box. Jak już nadmieniano wcześniej operacje te w przyszłości powinny być zrealizowane na wyższym poziomie abstrakcji.

Klasa Box

Projektując klasę Box, chciano stworzyć obiekt, którym będzie można wprowadzać dane, zadawać użytkownikowi pytania i wyświetlać pliki pomocy. Na bazie poprzednio zadeklarowanych klas wykonano projekt klasy Box. Obiekt klasy Box jest jedynym obiektem dziedziczącym metody po klasie Control i Wnd. Warunkiem bezkonfliktowego dziedziczenia klasy Events była zmiana kwalifikacji sposobu dziedziczenia w klasach Control i Wnd klasy Events na virtual. Taka modyfikacja doprowadziła do bardzo ciekawej hierarchii dziedziczenia dla obiektu Box.


DrawObject

DrawObject

DrawObjectDrawObjectDrawObjectDrawObject

DrawObject

DrawObjectDrawObjectDrawObject

DrawObject



Ilustracja 5.13 Hierarchia dziedziczenia dla obiektu klasy Box


Postać obiektu klasy Box jest deklarowana za pomocą połączenia (sumy logicznej) stylów, podczas jego tworzenia. W zależności od wybranego stylu obiektu, na ekranie i w pamięci komputera są tworzone odpowiednie podobiekty. Tak więc obiekt klasy Box, jest obiektem wielofunkcyjnym.

DrawObject

DrawObjectDrawObjectDrawObjectDrawObjectDrawObjectDrawObject

DrawObjectIlustracja 5.14 Przykładowy obiekt klasy Box


przedstawia przykładowy obiektu klasy Box. Na ilustracji widać kilka zgrupowanych obiektów w jednym oknie. Styl, jakiego należy użyć aby uzyskać powyższy efekt to SB_NAME|SB_SHELLEDIT|MB_TAK|MB_NIE|MB_POMOC.

Wszystkie dostępne style obiektu klasy Box to:


MB_TAK

Tworzy przycisk Tak

MB_NIE

Tworzy przycisk Nie

MB_ZBIOR

Tworzy przycisk Zbiór

MB_KATALOG

Tworzy przycisk Katalog

MB_ANULUJ

Tworzy przycisk Anuluj

MB_POMOC

Tworzy przycisk Pomoc

Tabela 1 Style przycisków obiektu klasy Box


SB_NAME

Pole informacyjne

SB_SHELLEDIT

Pole wprowadzania i edycji ciągu edycyjnego

SB_ITEMLIST

Pole wyboru elementu z listy

SB_DRIVECHOICE

Listwa przycisków, przedstawiających aktywne napędy.

Tabela 2 Style obiektów wewnętrznych obiektu klasy Box

Wszystkie przyciski, wstawiane do obiektu, są tworzone w stylu SPLASH.

Zadeklarowanie stylu wiąże się z koniecznością zadeklarowania informacji potrzebnych do utworzenia obiektów wewnętrznych.

Biała górna belka, oznaczona jako "belka informacyjna" po kliknięciu i przytrzymaniu myszą, umożliwia przesuwanie okna po ekranie komputera.

Obiekt klasy Box, jest obiektem najbardziej zaawansowanym abstrakcyjne jeśli brać pod uwagę hierarchię dziedziczenia klas. Nawet główny obiekt aplikacji (CORE) nie dziedziczy w tak skomplikowany sposób klas bazowych.

Obiekty klasy Box będą bardzo intensywnie wykorzystywane przy konstrukcji głównego obiektu aplikacji. Większość operacji potwierdzeń, zapytań i okien interakcyjnego pomocnika zostanie wykonana przy pomocy tego typu obiektów.

Politechnika Śląska - Gliwice, wrzesień 1995