Turbo Vision - Hogyan készítsünk Norton Utilies tipusú felhasználói felületet I ?

Már régóta beszélünk róla, hogy kellene készíteni egy olyan felhasználói-felületet szöveges módban, mely garfikus kinézetű. Ezt már sok cég programja meg is valósította. (DOS / MSAV - Defrag, NU, SR) Próbáljunk az eddigi sok program hibáiból okulni, és praktikumait felhasználni.

Az első dolog, hogy felmérjük a feladatunkat. Meg kell határozni mibôl, mit akarunk csinálni.

Lássuk a fô három pont megvalosítási lépéseit:

Elôször, a TV átalakítása, szinesebbét tétele:

type
  PPCXDeskTop = ^TPCXDeskTop;
  TPCXDeskTop = Object(TDeskTop)

procedure InitBackGround; virtual;
end;

PPCXApplication = ^TPCXApplication;
TPCXApplication = Object(TApplication)
  public
  constructor Init;
  destructor Done; virtual;
  procedure InitDeskTop; virtual;
  function GetPalette: PPalette; virtual;
end;

Látjuk, hogy új desktop-ot definiáltunk, annak érdekében, hogy a #176-os (kockás) karaktert, egy üresre a space (helyköz - #32)-re cseréljük.

Mivel az applikációnak a GetPalette metódusával már át is definiáltuk (lást OOP03.doc) a palettáját, és a TBackground palettájának egyetlen eleme a tulajdonosának - az-az az applikáció - palettájának elsô elemére mutat, így elég volt csak az applikáció palettájának elsô elemét megváltoztani a más háttérszín elérése végett. (Persze azért megjegyzem, hogy az applikáció palettájának elsô eleme csak a háttér színének tárolására van fenntarva.)

Nos ahogy új desktop-ot adunk az új (PCX) applikációhoz, úgy annak a színe, s a feltöltési karaktere is megváltozik.

Továbbá (sôt továbbá javasolnám Karthago elfoglalását ... huhhh de régen voltam én történelem órán ...), szóval továbbá érdemes lenne az applikációnak elmenteni a képernyôn lévô szöveget, hogy a programból való kilépés után ugyanazt lássuk, mint elôtte. Ezt simán $B800:0 címtôl 4096 byte elmentése a prg. indítása elôtt (constructor Init; elôtt), majd kilépéskor, az-az a destructor Done;-ban. (Lásd a forráskódot.)

Ha már ennyire belelendültünk akkor, alakítsuk még "pofásabbra" az applikációt. Vegyük a drága - jó Windows-t. Nos ott van minden programnak egy fejléce, amire a program neve van írva. Huhh, ok csináljuk ezt meg mi is.

Ezt legegyszerűbben egy TStaticText-tel oldhatjuk meg. Persze valami izléses színben. Legyen mondjuk sötétkék (01 szín) háttér, és fehér szinű betűk (15). Ehhez, mint már lassan megszoktuk új objektumot kell örökítenünk a TStaticText-bôl, amit nevezzünk a könnyebb elérhetôség végett TPCXTitleBar-nak. Lássuk az új objektum felépítését:

type
  PPCXTitleBar = ^TPCXTitleBar;
  TPCXTitleBar = Object(TStaticText)
    constructor Init(AText: String);
    function  GetPalette: PPalette; virtual;
    procedure SetTitleText(AText: String);
  end;
Nos azt láthatjuk, hogy eltér az eredeti TStaticText-tôl már az init is. Hogy miért, a válasz egyszerű: hol akarná az ember a program fejlécét máshol, mint a 0,0 (bal-felsô sarokban). Így nem kell a koordinátákat megadni.

A GetPalette metódus egyértelmű, hisz ezzel adjuk meg a színt. A kérdés, hogy hogyan ? Mibôl, ... mi fog mire mutatni ? Az applikáció palettájához hozzá kell adni egy új színt a program-cím szinét. Legyen ez esetünkben az utolsó. (175. fô paletta index.) Erre az indexre fog mutatni a TPCXTitleBar palettájának egyetlen eleme.

A SetTitleText meg arra szolgál, hogyha egy program esetleg idô közben morfoizálódna avagy a programozó hirtelen felindulásból topic-ot (címet) kívánna váltani az az OOP elvei szerint könnyen megoldható legyen.

Megvalósítás a forráskódban (PCX_APP.pas).

Fontos megemlíteni hogy a Borland, a Turbo Vision alkotói azt a szokást alakították ki, hogy bizonyos az applikációhoz tartozó elemeket nem az objektumban (tehát nem a TApplication-ban) deklaráltak, hanem az ôket tartalmazó unit-ban az APP.pas-ban. Nyilván ezt azért tették, hogy az összes objektum elérhesse, igaz ezzel megsértették az adatrejtés elvét. De hát mi is így teszünk, s a TPCXTitleBar-t a PCX_APP.pas Interface részében deklaráljuk.

const
  PCXTitlebar: PPCXTitleBar = nil;
Ehhez az inicializált változóhoz (konstanshoz) a TPCXApplication Init-je hozza létre a view-ot.

Eredeti célkitűzésünk a Turbo Vision szinesebbé tétele, ehhez szükséges az az információ ami segít minket abban, hogy ne csak az alacsony intenzitású szineket (0-7) lehessen háttérszínként használni, hanem a világosabbakat (0-15) is. Ez persze azzal jár, hogy nem lesznek villogó szineink de ki bánja !? Fent ezt már a háttérszín kialakításánál fel is használtuk.

A háttér-intenzitás és a karakterátírás megvalósítása

A háttér-intenzitás gyakorlati megvalósítása:
procedure SetBackGroundIntensity(On: Boolean); Assembler;
asm
  mov bl, On
  or  bl, bl
  jne @To0
  mov bl, 01h
  jmp @Folyt
@To0:
  xor bl, bl
@Folyt:
  mov ax, 1003h
  int 10h
end;
Láthatjuk, hogy videó (INT 10h) megszakítás valósítja ezt meg. Ha fenti procedure True értéket kap akkor felkapcsolja, ha False akkor lekapcsolja a háttérintenzitást. A háttérintemzitást le is lehet kérdezni a memória $40:$65 byte-ján, s ezen byte 5. bit-én. Ha ez a bit lekapcsolt akkor van háttérintenzitás, ellenkezô esetben nem.

A karakterátírás gyakorlati megvalosítását a Tippek - Trükkök rovat 3. számában közöltem. Mivel nem kívánom újra leírni így teljes mélységeiben mindent megtalálhattok a PC-XUSER\OOP\T&T03.HLP\ könyvtárban t&t03.arj file-ban. Ez a T&T 3. száma tömörítve.

A lényeg a amire hangsúlyt kell helyeznünk - hisz már mindenki tud karaktert átírni - hogy milyen képű karaktereket kell létrehoznunk. A rég említett CHAREDIT vizuális programmal fogjuk megtervezni a karakterek képét. Vegyük sorra gondolatainkat:

 
 
Lássuk, hogyan is szerkesztjük meg ezeket a karaktereket László József CHAREDIT programjával:
 

Láthatóak az egyes karakterek, az F1-vel lehívjuk ôket, a kék mezôben szerkesztjük ôket a space billentyűvel kapcsoljuk ki ill. be a pontokat. Az F2 gombbal pedig mentjük el a kész karakter bit-térképét.

(Itt láthatók a keret sarkait összekötô karakterek !!!)

Nos térjünk vissza lassan a GUI-k (Graphical User Interface, pl. OS/2, Windows) ablakcímeihez. Nyilván észrevettük, hogy a bal felsô sarokban található egy kis gomb aminek a segítségével egy menüt hívhatunk le. Ez a menü tartalmazza az ablakkal (- vagy az applikáció ablakával) végezhetô műveleteket: kicsinyítés, nagyítás, bezárás. Ezt control-box-nak nevezzük. Rajzoljuk mi is egyet. Ezt csak két karakterbôl tudjuk létrehozni így:

A középen látható vastag vonal a két karakter határa. Fontos: hogy takarékoskodjunk a keret rajzoló karakterekkel, így elég, ha csak a bal oldali karakter a keret-karakter - hogy összeérjen a control-box, és jobb oldalinak nem kell annak lennie.

Az átírt, új karaktereket használó származtatott objektumok írása I

A Turbo Vision-nek is van valami hasonló gombja, igaz nincs parosítva hozzá menü. Ez a gomb három karakterbôl van, s valahogy így néz ki: [X] Nos hát a milyen ennél szebb. Ezt a három karaktert kell kicserélnünk a sajátunkra. Ez a forráskód átirásával lehetséges. Lássuk ezt nagy vonalakban:

Gondoljuk végig, milyen objektumokra lesz szükség, és ezeket a program mely részein kívánjuk használni:

Ezek megvalósítása nagy lépésekben:

Mivel több helyen kívánjuk alkalmazni a controll-box-ot (applikáció - window - dialog) ezért hozzunk létre egy közös ôst mely elvégzi a legalapvetôbb műveleteket. Legyen ô a TPCXControllbox. Metódusai:

  PPCXControlBox = ^TPCXControlBox;
  TPCXControlBox = Object(TStaticText)
    constructor Init(P: TPCXPoint);
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure ExecControlMenuBox; virtual;
  private
    ControlMenuBox: PPCXMenuBox;
  end;
Láthatjuk, hogy ez is, mint a titleline is a TStaticText-bôl származik hisz nem más, mint két - vagy három kiirandó karakter, amihez menüt társítunk. A továbbiakban esetleges menüt a ControlMenuBox változóban tároljuk.
constructor TPCXControlBox.Init(P: TPCXPoint);
var
  R    : TRect;
  TempS: String;
begin
  ControlMenuBox:=nil;
  R.A.X:=P.X; R.A.Y:=P.Y;
              R.B.Y:=R.A.Y+1;
  if IsPCXGraphCharsOn
  then begin
         R.B.X:=2;
         TempS:=ControlBox;
        end
  else begin
         R.B.X:=3;
         TempS:='[X]';
       end;
  Inherited Init(R, TempS);
end;
Láthatjuk, hogy inicializáljuk a ControlMenuBox vátozót. Elôkészítjük az R: TRect változót a TStatusLine örökölt inicializálásához. Az R.A = P. Az-az a koordináták az R. A mezôjében megegyeznek, 1 sor lesz a szélesség.

Ha grafikus kinézetű az applikációnk (az-az használunk karakterátírást) akkor kettô széles a kiirandó szöveg, és a szöveg - pontosabban a kiirandó két karakter - a fent lerajzolt ábra szerint, ill. ha csak sima textmódban vagyunk akkor ugye 3 karakteres, még pedig ez: [X] .

Majd meghívjuk az örökölt TStatusLine init-et.

Ezen view handleevent-je akkor hívódik meg ha rá klikkelünk, vagy forrókulccsal elôhívjük az-az ha fókuszba kerül. Ekkor meg kell jelenni egy menünek amibôl ki lehet választani, hogy milyen műveletet akarok elvégezni. Ez a virtuális (!) metódus valósítja meg, a menü lefuttatását. Fontos, hogy virtuális legyen, mert akkor az utódok más és más menüket jeleníthetnek meg annélkül, hogy a HandleEvent()-et újra kellene írni.

Ennek a fô objektumnak nem kell ehhez a metódushoz kódot társítania így simán ennyi lesz:

procedure TPCXControlBox.ExecControlMenuBox;
begin
  Abstract;
end;
Ahol az absztrakt jelzi, hogy még nem használt metódus, majd az utódok használják. Ha mégis meghívnánk szépen leáll runtime error-ral a programunk, figyelmeztetve, hogy ez csak egy absztrakt metódus.

Lássuk a belôle származtatott utódokat, és egynek részletes felépítését. (a többi hasonló analógia alapján felépíthetô). Három utód lesz, a három különbözô használati helyen:

Két metódust kell átírnia az utódnak. Nyilván az új palettát kell a view-hoz rendelni, ill. a MenuBox-ot lefuttatni:
function  TPCXControlBoxApp.GetPalette: PPalette;
const P: String[Length(CPCXControlBoxApp)] = CPCXControlBoxApp;
begin
  GetPalette:=@P;
end;
Nyilván virtuális metódus ez is, s a palettául szolgáló konstans az Interface részben van deklarálva. Ennek egy eleme van, mivel ez az applikáció controlbox-a, és ez a címsorban helyezkedik ez azonos színű lesz vele. Ez az egy elem, mint a címsor szine (CPCXTitleBar) a 175. indexre mutat a tulajdonosa, most az applikáció palettájában. Tehát: CPCXControlBoxApp = #175;

A többi ôs is hasonlóan rendeli a controlbox-hoz a palettát, de ott mivel a dialógus ill. az ablak a paletta-tulajdonosa ezért a #34-dik indexre mutat.

procedure TPCXControlBoxApp.ExecControlMenuBox;
var
  R    : TRect;
  O    : TPCXPoint;
  C    : Word;
  Event: TEvent;
begin
  O.Assign(0,0);
  MakeGlobal(O, O);
  R.Assign(O.X,O.Y+1,O.X+18,O.Y+7);
  New(ControlMenuBox, Init(R, NewMenu(
    NewItem('~M~ove', 'Crt-F5', kbCtrlF5, cmMove, hcNoContext,
    NewItem('~Q~uit', 'Alt- X', kbAltQ, cmQuit, hcNoContext,
    NewLine(
    NewItem('~R~efresh', '', kbNoKey, cmRefresh, hcNoContext, nil))))),
    nil));
  C:=Application^.ExecView(ControlMenuBox);
  if C<>cmCancel then
  begin
    case C of cmMove:
              begin
                KeyStrokeToKeyboardBuffer(0, $62);
                Message(Application, evCommand, cmMouseChanged, @Self);
              end;
              cmRefresh: Application^.ReDraw;
              cmQuit   : Message(Application, evCommand, cmQuit, @Self);
    end;
  end;
end;
Láthatjuk ez már egy fokkal szebb. A többi (a dialógus - ill. az ablak) controlbox menüje annyiban különbözik, hogy az az 1, 0 koordinátán található, ill. azokat lehet is mozgatni. Méghozzá most el is mondom mi van ott feljebb a cmMove:-nál:
KeyStrokeToKeyboardBuffer(0, $62);
Nos ezzel persze még nincs vége. Ahhoz, hogy lássunk is valamit a mi sok fáradtsággal elkészített controlbox-unkból, ahhoz létre is kell hozni ôket. Persze mindegyiküket a rá jellemzô helyen. Vegyük egy példát az applikációt:

Az applikáció init-jében (TPCXApplication.Init) simán létrehozzuk, és láthatóvá teszük:

{...}
  New(PCXTitlebar, Init(#3+ApplicationName)); {látható a cím-sor létrehozása}
  Insert(PCXTitlebar); {láthatóvá tétele}
  P.Assign(0,0); {a controlbox helyének kijelölése}
  New(ControlBoxApp, Init(P)); {létrehozása}
  Insert(ControlBoxApp); {megjelenítése}
{...}
Nos ez mind szép és jó, van már működô controlbox-unk, de mi van a kerettel, ettôl még nem lesz szép menünk, ill. ablakunk.

A menü új keret-karakterekkel történô kirajzolása

Mai témánk utolsó részéhez érkeztünk. Ez lesz tán' a legnehezebb, részint azért mert már a végére elfáradtunk, részint azért mert nagyon régen magyaráztam azokat a rutinokat melyeket az alábbi kódrészlet tartalmaz. (!OLDUSER\all.exe, OOP02.doc).

Nos mivel a menüt akarjuk kirajzoltatni, elôször nézzük meg a Pascal RTL (forráskód) \TV könyvtárában a MENUS.PAS unit-ot mely tartalmazza a TMenuBox objektumot mely, mint vettük egy menüt (egy menü téglalapot) rajzol ki. Tehát nézzük, meg hogy is oldották meg eredetileg, s ezt a forráskódot magunkhoz átmásolva módosítgassuk úgy, hogy szép legyen.

Szerencsénk van minimális átírással elérhetjük azt, hogy szép legyen a menü. Vegyük végig a menü kirajzolásának elvét:

A menü keretét akarjuk csak modosítani, így csak azt kell átírni a procedure-ában.

Szerencsénkre el van különítve egy rutin mely a buffer-be írja a keretet (ez a Draw procedure-án belűl egy lokális rutin a FrameLine() mely egy sort ír ki a keretbôl, a buffer-be), a képernyôre a DrawLine úgyszint lokális procedure írja ki.

Tehát nekünk a FrameLine rutint kell tanulmányoznunk, lássuk az eredeti lokális rutint:

procedure FrameLine(N: Integer);
const
  FrameChars: Array[0..19] of Char = '     - ';
begin
  MoveBuf(B[0], FrameChars[N], Byte(CNormal), 2);
  MoveChar(B[2], FrameChars[N + 2], Byte(Color), Size.X - 4);
  MoveBuf(B[Size.X - 2], FrameChars[N + 3], Byte(CNormal), 2);
end;
Hogyan működik ? Lássuk, hogyan kell módosítani a kiírást: Lássuk az ezt megvalosító új forráskódot:
procedure FrameLine(N: Integer);
const
     FrameChars: Array[0..19] of Char =  = '     - ';
  PCXFrameChars: Array[0..19] of Char =
    GFrameCornerLU+GFrameSummitU+GFrameSummitU+GFrameSummitU+GFrameCornerRU+
    GFrameCornerLD+GFrameSummitD+GFrameSummitD+GFrameSummitD+GFrameCornerRD+
    GFrameEdgeL+#32#32#32+GFrameEdgeR+
    GFrameEdgeL+#32#196#32+GFrameEdgeR;
begin
  if IsPCXGraphCharsOn
  then begin
         MoveBuf(B[0], PCXFrameChars[N], Byte(CNormal), 2);
         MoveChar(B[2], PCXFrameChars[N + 2], Byte(Color), Size.X - 4);
         MoveBuf(B[Size.X - 2], PCXFrameChars[N + 3], Byte(CNormal), 2);
       end
  else begin
         MoveBuf(B[0], FrameChars[N], Byte(CNormal), 2);
         MoveChar(B[2], FrameChars[N + 2], Byte(Color), Size.X - 4);
         MoveBuf(B[Size.X - 2], FrameChars[N + 3], Byte(CNormal), 2);
       end;
end;
Hogy is van ez ?
 
Lássuk PCXFrameChars tömb felépítését. Konkretizálom a konstansok nevének logikáját: Nos most már remélhetôleg minden világos. Ha nem az egyántalát nem baj, mert szerintem a cikk, és az összes forráskód (PCX_APP, PCX_DLG, PCX_UTIL, TVDEMO) együttes figyelésével lehet megérteni. A menü kirajzolásánál ajánlom a debugging-ot (F8 bill.). Következô számunkban remélhetôleg az ablakok - dialógusok szebb kivitelezésérôl beszélgetünk.

Hát ennyi mára, hisz ez már a 9.5-dik oldal. Ha valaki a forráskód részletes átrágása után se értené a dolgok mikéntjét, az eMail-ezzen.

Bérczi László
eMail:PC-XUser@IDG.HU, Subject: "OOP rovat"
BELA@MI.STUD.PMMFK.JPTE.HU
A Norton Utilities a Symantec bejegyzett védjegye 


Izelítôül egy szebb kinézetű program: