Mai számunkban olyan kontrollokat írok le, amit az előző számokban még nem tettem meg, de fontosnak tartok. Az előző számban a scrollbar-okról (gördítősáv) volt szó, így adott minden feltétel ahhoz, hogy először is egy editáló – "szöveg-szerkesztő" ablakocskát készítsünk.

A TEditWindow átírása đ TPCXEditWindow

Mindenek előtt helyesen be kell állítani a TPCXDesktop elhelyezkedését, mert ezt már rég elmulasztottuk, amikor a TTitleBar-t deklaráltuk. Ezt most a PCX_APP.pas unit-ban a TPCXApplication InitDeskTop() metódusában kell megtennünk. Ez a régi forráskód:

procedure TPCXApplication.InitDeskTop;

var R: TRect;

begin
  GetExtent(R);
  Inc(R.A.Y); {Jó változat: Inc(R.A.Y, 2); }
  Dec(R.B.Y);
  DeskTop:=New(PPCXDeskTop, Init(R));
end;

S az Inc(R.A.Y); utasítást kell kijavítanunk Inc(R.A.Y, 2);-re, hogy a desktop a második sorban kezdődjön, s ne az elsőben. Ezáltal már jó helyre helyezhetjük az ablakokat, ha a Desktop^.GetExtent() metódust használjuk mely – mint tanultuk – az adott view kiterjedését adja vissza az Owner-ének koordináta-rendszeréhez képest.

Most, hogy kijavítottuk ezt a TFileEditor view-objektumnak kell valami tisztességes háttérszínt adni a megszokott GetPalette() metódussal.

type
  PPCXFileEditor = ^TPCXFileEditor;
  TPCXFileEditor = Object(TFileEditor)

function GetPalette: PPalette; virtual;
end;

function TPCXFileEditor.GetPalette: PPalette;

const P: String[Length(CPCXFileEditor)] = CPCXFileEditor;

begin
  GetPalette:=@P;
end;

Hogy ez az objektum, hogy csatlakozik a TEditWindow-hoz ? Ez annak egy mezője, s ez végül is egy view, egy TEditor ős view mely, hivatott egy szöveg ablak – ill. TXT file tartalmának kiíratására. Maga a TWindow-ból lesz a TEditorWindow, ez a keretfelület, s ebbe jön bele a TFileEditor.

A TEditWindow objektumot azért kell átírnunk, hogy szép kerete (frame) ill. saját, új scrollbar-jainkat használja. Lássuk a származtatás ill. az implementálás menetét:

type
  PPCXEditWindow = ^TPCXEditWindow;
  TPCXEditWindow = Object(TEditWindow)
    constructor Init(var Bounds: TRect; FileName: FNameStr; ANumber: Integer);
    procedure InitFrame; virtual;
    function  GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
  private
    ControlBoxWin: PPCXControlBoxWin;
  end;

constructor TPCXEditWindow.Init(var Bounds: TRect; FileName: FNameStr; ANumber: Integer);
var
  P: TPCXPoint;
  HScrollBar, VScrollBar: PPCXScrollBar;
  Indicator: PPCXIndicator;
  R: TRect;
begin
  Inherited Init(Bounds, '', ANumber);
  Options := Options or ofTileable;
  R.Assign(18, Size.Y - 1, Size.X - 2, Size.Y);
  HScrollBar := New(PPCXScrollBar, Init(R));
  HScrollBar^.Hide;
  Insert(HScrollBar);
  if IsPCXGraphCharsOn
  then R.Assign(Size.X - 2, 1, Size.X, Size.Y - 1)
  else R.Assign(Size.X - 1, 1, Size.X, Size.Y - 1);
  VScrollBar := New(PPCXScrollBar, Init(R));
  VScrollBar^.Hide;
  Insert(VScrollBar);
  R.Assign(2, Size.Y - 1, 16, Size.Y);
  Indicator := New(PPCXIndicator, Init(R));
  Indicator^.Hide;
  Insert(Indicator);
  GetExtent(R);
  R.Grow(-2, -1);
  Editor := New(PPCXFileEditor, Init(R, HScrollBar, VScrollBar, Indicator, FileName));
  Insert(Editor);

  P.Assign(0,0);
  New(ControlBoxWin, Init(P));
  Insert(ControlBoxWin);
end;

Lássuk szép sorjában, hogy mi is történik:

A TPCXEditWindow keretét az alábbiakban készítjük el:

procedure TPCXEditWindow.InitFrame;
var
  R: TRect;

begin
  GetExtent(R);
  Frame := New(PPCXFrame, Init(R));
end;

A TPCXEditWindow HandleEvent()-je így néz ki:

procedure TPCXEditWindow.HandleEvent(var Event: TEvent);
begin
  if Event.KeyCode=kbAltSpace then ControlBoxWin^.ExecControlMenuBox;
  Inherited HandleEvent(Event);
  if Event.Command = cmNewLine then
  begin
    Event.KeyCode:=Event.KeyCode and (Not kbAltSpace);
    Event.Command:= Event.Command and (Not cmNewLine);
  end;
end;

Itt csak azért kell variálni, mert az Alt+Space-nek a Turbo Vision billentyű konvertálása után az lesz a kódja, mintha új sort nyomtunk volna. Így egy faramuci módon kell lekezelni.

Ennek figyelése úgy néz ki, hogy még mielőtt az ősök HandleEvent()-je lekezelné a ControllBox előhívásához szükséges Alt+Space-t azelőtt mi magunk tesszük meg. Ha ez megtörtént, s az Alt+Space-ből cmNewLine lett akkor az így keletkezett új sor parancsot meg kiiktatjuk.
 
Ha ügyesek voltunk, akkor egy ilyen editáló ablakot kaptunk:

 
 

További nyalánkságok, MessageBox()

Gondolom, észrevettétek hogy a TVDEMO programot futva az elmentésre felszólító dialógus, pontosabban messagebox (üzenetablak) még a régi, sima text formátumú. Ennek elkerülése végett saját MessageBox()-ot kell létrehoznunk. Ez persze megint egyszerű móka, hisz a MessageBox() forráskódjában lévő PDialog-okat, ill. PButton-okat kell a PCX-es megfelelőkre kicserélni. Ugyanakkor feldobhatjuk programunkat a Norton-ból megismert figyelemfelhívó piros, figyelmeztető dialógusokkal. A messagebox két típusból áll, a MessageBoxRect()-ből, mely megvalósítja magát a dialógust a gombokkal, a szövegmezővel és oda rakja a képernyőn ahova a bemenő R: TRect megadta, ill. a MessageBox() rutinból mely úgy futtatja le a MessageBoxRect()-et, hogy előtte minden képen a képernyő közepére helyezi azt (Vagyis a bemenő R: TRect-et maga számolja !). Így mi is a MessageBox()-ot használjuk későbbiekben.

Mi rövidebb neveket használunk: PCXMsgBoxRect() ill. PCXMsgBox().

function PCXMsgBoxRect(var R: TRect; const Msg: String; Params: Pointer; AOptions: Word): Word;
{var változó deklaráció}

begin
  {... itt a gombokon látható szövegeket hozzuk létre}
  if ((AOptions and 3)<>mfError) and ((AOptions and 3)<>mfWarning)
           then Dialog := New(PPCXBlueDialog,Init(R, Titles[AOptions and $3]))
           else Dialog := New(PPCXRedDialog,Init(R, Titles[AOptions and $3]));
  with Dialog^ do
  begin
    R.Assign(3, 2, Size.X - 2, Size.Y - 3);
    FormatStr(S, Msg, Params^);
    Control := New(PStaticText, Init(R, S));
    Insert(Control);
    X := -2;
    ButtonCount := 0;
    for I := 0 to 3 do
      if AOptions and ($0100 shl I) <> 0 then
      begin
        R.Assign(0, 0, 10, 2);
        Control := New(PPCXButton, Init(R, ButtonName[I], Commands[i], bfNormal));
        Inc(X, Control^.Size.X + 2);
        ButtonList[ButtonCount] := Control;
        Inc(ButtonCount);
      end;
    X := (Size.X - X) shr 1;
    for I := 0 to ButtonCount - 1 do
    begin
      Control := ButtonList[I];
      Insert(Control);
      Control^.MoveTo(X, Size.Y - 3);
      Inc(X, Control^.Size.X + 2);
    end;
    SelectNext(False);
  end;
  if AOptions and mfInsertInApp = 0 then
       PCXMsgBoxRect := DeskTop^.ExecView(Dialog)
  else PCXMsgBoxRect := Application^.ExecView(Dialog);
  Dispose(Dialog, Done);
end;

A rutin működése egyszerű, a fenti forráskód elejéből kivágtam az a string inicializációt amelyben a gombokon lévő feliratokat rendeljük a string-hez, hisz ez egyértelmű.

function PCXMsgBox(const Msg: String; Params: Pointer; AOptions: Word): Word;
var R: TRect;

begin
  R.Assign(0, 0, 40, 9);
  if AOptions and mfInsertInApp = 0 then
       R.Move((Desktop^.Size.X - R.B.X) div 2, (Desktop^.Size.Y - R.B.Y) div 2)
  else R.Move((Application^.Size.X - R.B.X) div 2, (Application^.Size.Y - R.B.Y) div 2);
  PCXMsgBox := PCXMsgBoxRect(R, Msg, Params, AOptions);
end;

És itt a PCXMsgBox() aminél már láthatjuk, hogy nincs szükség az elhelyezkedés (R: TRect) megadására, hisz azt maga számolja, s így a képernyő közepére helyezi a dialógust. Valahogy így:

Nos hogy ezzel végeztünk, tegyük szebbé a FileOpenDialog-ot mely a file megnyitásakor hívódik meg. Itt se kell meggebednünk csak a frame-et, meg egy apróságokat kell átírnunk.

TPCXFileDialog

Ezen view felépítésekor fogunk pár olyan view-ot használni amit nem ismertetek, csak említés szintjén foglalkozunk velük. Ilyenek a TFileInputLine, TPCXFrameLine3D, TFileList ...

Lássuk az átírt, szépített forráskódot, s közben a magyarázatot:

constructor TPCXFileDialog.Init(AWildCard: TWildStr; const ATitle, InputName: String; AOptions: Word; HistoryId: Byte);

var
  Control: PView;
  R: TRect;
  Opt: Word;

begin
  R.Assign(15,1,64,20);
  TDialog.Init(R, ATitle);
  Options := Options or ofCentered;
  WildCard := AWildCard;

Nos először a változók deklarációja, majd

  R.Assign(3,3,34,4);
  FileName := New(PFileInputLine, Init(R, 79));
  FileName^.Data^ := WildCard;
  Insert(FileName);   R.Assign(2,2,3+CStrLen(InputName),3);
  Control := New(PLabel, Init(R, InputName, FileName));
  Insert(Control);   R.Assign(2,5,35,16);
  Insert(New(PPCXFrame3D, Init(R, ' ', AllGraphFrame, FrameBlack)));   R.Assign(2,14,35,15);
  Control := New(PPCXScrollBar, Init(R));
  Insert(Control);   R.Assign(3,6,34,14);
  FileList := New(PFileList, Init(R, PPCXScrollBar(Control)));
  Insert(FileList);   R.Assign(3,5,10,6);
  Control := New(PLabel, Init(R, '~F~iles ', FileList));
  Insert(Control);   R.Assign(35,3,46,5);
  Opt := bfDefault;
  if AOptions and fdOpenButton <> 0 then
  begin
    Insert(New(PPCXButton, Init(R, '~O~pen', cmFileOpen, Opt)));
    Opt := bfNormal;
    Inc(R.A.Y,3); Inc(R.B.Y,3);
  end;
    if AOptions and fdOkButton <> 0 then
  begin
    Insert(New(PPCXButton, Init(R, 'O~K~', cmFileOpen, Opt)));
    Opt := bfNormal;
    Inc(R.A.Y,3); Inc(R.B.Y,3);
  end;   if AOptions and fdReplaceButton <> 0 then
  begin
    Insert(New(PPCXButton, Init(R, '~R~eplace',cmFileReplace, Opt)));
    Opt := bfNormal;
    Inc(R.A.Y,3); Inc(R.B.Y,3);
  end;   if AOptions and fdClearButton <> 0 then
 begin
   Insert(New(PPCXButton, Init(R, '~C~lear',cmFileClear, Opt)));
   Opt := bfNormal;
   Inc(R.A.Y,3); Inc(R.B.Y,3);
 end; Insert(New(PPCXButton, Init(R, 'Cancel', cmCancel, bfNormal)));   Inc(R.A.Y,3); Inc(R.B.Y,3);
  if AOptions and fdHelpButton <> 0 then
  begin
    Insert(New(PPCXButton, Init(R, 'Help',cmHelp, bfNormal)));
    Inc(R.A.Y,3); Inc(R.B.Y,3);
  end; R.Assign(1,16,48,18);
Control := New(PFileInfoPane, Init(R));
Insert(Control);
SelectNext(False);
if AOptions and fdNoLoadDir = 0 then ReadDirectory; end;

function TPCXFileDialog.GetPalette: PPalette;
const P: String[Length(CPCXBlueDialog)] = CPCXBlueDialog;
begin
  GetPalette:=@P;
end;

GetPalette: Ez a jó ismert szín-paletta megadásához szükséges rutin.
 

procedure TPCXFileDialog.InitFrame;

var R: TRect;

begin
  GetExtent(R);
  Frame:=New(PPCXFrame, Init(R));
end;

InitFrame: A keret átrajzolásához szükséges rutin.

procedure TPCXFileDialog.ReadDirectory;

begin
  FileList^.ReadDirectory(WildCard);
  Directory := NewStr(GetCurDir);
end;

S az ominózus ReadDirectory mely az aktuális könyvtár file-jait ill. al-könyvtár bejegyzéseit olvassa be. Munkánk gyümölcsét az alábbi kép mutatja:

 

Végül, de nem utolsó sorban a CheckBox-ok, ill. a RadioButton-ok kerülnek terítékre

Hogy mi is az a CheckBox, hát az a be-x-elhető négyzetecske, valahogy így: x S a RadioButton ? Emmeg ez: ¤ . Nos komolyra fordítva a szót a Turbo Vision-nek természetesen van CheckBox-a csak az a sima text mód miatt így néz ki: [X]. Sőt RadioButton is van ez meg még szörnyebben néz ki: (*). Valami szebbet is készíthetnénk ezeknél, ha már karakterátírással foglalkozunk. Most se lesz nagyon nehéz dolgunk csak az eredeti forráskódot kell alakítgatnunk.
Kezdjük a CheckBox-al. Milyen metódusokat módosítunk ?

type

  PPCXCheckBoxes = ^TPCXCheckBoxes;
  TPCXCheckBoxes = Object(TCheckBoxes)
    constructor Init(var Bounds: TRect; AStrings: PSItem; IsRightOn: Boolean);
    procedure DrawMultiBox(const Icon, Marker: String);
    procedure Draw; virtual;
    function  GetPalette: PPalette; virtual;
    procedure Press(Item: Integer); virtual;
  private
    IsRight: Boolean;
    function Column(Item: Integer): Integer;
    function FindSel(P: TPoint): Integer;
    function Row(Item: Integer): Integer;
  end;

Először is elmondanám, hogy egy újítást kell végzünk a CheckBox-on. Sokszor előfordul, hogy nem a bal oldalra szeretnénk tenni a be-x-elhető kis négyzetet, hanem jobbra. Ahhoz, hogy ezt nyugodtan megtehessük a később átirandő Draw; metódust kell megfelelően kialakítani. Viszont az objektumnak tudnia kell, hogy bal ill. jobb oldalra kívánjuk –e tenni a négyzetet. Ezért kell az Init() metódust módosítani. Így:

constructor TPCXCheckBoxes.Init(var Bounds: TRect; AStrings: PSItem; IsRightOn: Boolean);
begin
  Inherited Init(Bounds, AStrings);
  IsRight:=IsRightOn;
end;

Egy lokális változóban mentjük el, hogy jobbra van –e igazítva, miután meghívtuk az örökölt Init() metódust.

A következő metódus ami a leglényegesebb, ez a DrawMultiBox(), ez végzi magát a kirajzolást, míg a Draw; metódus pedig ezt a DrawMultiBox rutint vezérli. Lássuk a DrawMuliBox() rutin forráskódját, s vele együtt a magyarázatot:

procedure TPCXCheckBoxes.DrawMultiBox(const Icon, Marker: String);
var
  I,J,Cur,Col: Integer;
  CNorm, CSel, CDis, Color: Word;
  B: TDrawBuffer;
  SCOff: Byte;

begin
  CNorm := GetColor($0301);
  CSel := GetColor($0402);
  CDis := GetColor($0505);

for I := 0 to Size.Y do begin
  MoveChar(B, ' ', Byte(CNorm), Size.X);
  for J := 0 to (Strings.Count - 1) div Size.Y + 1 do begin
  Cur := J*Size.Y + I;
  if Cur < Strings.Count then begin
  Col := Column(Cur);
  if (Col + CStrLen(PString(Strings.At(Cur))^) + 5 <
  Sizeof(TDrawBuffer) div SizeOf(Word)) and (Col < Size.X) then
  Begin if not ButtonState(Cur) then
  Color := CDis
else if (Cur = Sel) and (State and sfFocused <> 0) then
  Color := CSel
else
  Color := CNorm; if not IsRight then
begin
  MoveChar(B[Col], ' ', Byte(Color), Size.X - Col);   MoveStr(B[Col], Icon, Byte(Color));   WordRec(B[Col+2]).Lo := Byte(Marker[MultiMark(Cur) + 1]);   MoveCStr(B[Col+5], PString(Strings.At(Cur))^, Color); end else begin
  MoveChar(B[Col], ' ', Byte(Color), Size.X - Col);
  MoveStr(B[Size.X-4], Icon, Byte(Color));
  WordRec(B[Size.X-2]).Lo := Byte(Marker[MultiMark(Cur) + 1]);
  MoveCStr(B[Col+2], PString(Strings.At(Cur))^, Color);
end; if ShowMarkers and (State and sfFocused <> 0) and (Cur = Sel) then

begin
  WordRec(B[Col]).Lo := Byte(SpecialChars[0]);
  WordRec(B[Column(Cur+Size.Y)-1]).Lo := Byte(SpecialChars[1]);
end;

    end;
  end;
end;

WriteBuf(0, I, Size.X, 1, B);

end;
if IsRight then SetCursor(Column(Sel)+Size.X-2,Row(Sel))
           else SetCursor(Column(Sel)+2,Row(Sel)); Message(Application, evCommand, cmMouseChanged, @Self); end;

Most pedig maga a Draw; metódus következik mely vezérli a checkbox-t. Lehetőségünk van a Draw ill. a DrawMuliBox szétválastásának eredménye képen, hogy különböző checkbox-aink legyenek. Most a sima text, ill. a grafikus-text módtól függően rajzolunk ki különböző checkbox-ot.

procedure TPCXCheckBoxes.Draw;
const
  GraphButton = #32+CheckBoxLeft+#32+CheckBoxRight+#32; {' [ ] '}
  TextButton = ' [ ] ';
begin
  if IsPCXGraphCharsOn
  then DrawMultiBox(GraphButton, CheckBoxCenterN+CheckBoxCenterX) {' X'}
  else DrawMultiBox(TextButton, ' X');
end;

Ha átírt karaktereket használunk akkor az átírt karakterek ASCII kódjából összeállított ikont adjuk át, ill. a hasonló karakterekből kialakított üres négyzetbelsőt ill. a be-x-eltet.

Ha sima text módban dolgozunk akkor, pedig a sima ASCII karakterből összeállított ikont adjuk át, (’ [ ] ’) ill. ennek a belsejét vagy üres: ’ X’ vagy a második karakter az X.

Ezen aprócska rutinok meg nyilván valóak, hogy mit csinálnak: Lássuk a művünket:

Láthatjuk, hogy bal ill. jobb oldalra illesztett checkbox-okat sikerült produkálnunk. Lássuk a létrehozáshoz szükséges forráskódot:
 
Balra illesztett, Jobbra illesztet checkbox:
R.Assign(3, 8, 37, 12); 
Insert(New(PPCXCheckBoxes, Init(R, 
NewSItem('~C~ase sensitive', 
NewSItem('~W~hole words only', 
NewSItem('~P~rompt on replace', 
NewSItem('~R~eplace all', nil)))), False)));
R.Assign(3, 8, 37, 12); 
Insert(New(PPCXCheckBoxes, Init(R, 
NewSItem('~C~ase sensitive', 
NewSItem('~W~hole words only', 
NewSItem('~P~rompt on replace', 
NewSItem('~R~eplace all', nil)))), True)));
A TRadioButton-t nem részletezem, hisz elviekben, s megvalósításban ugyanez. Megtalálható az összes eddigivel a PCX_DLG.pas Unit-ban.

Nos úgy érzem ennyi elég volt mára, így zárul az OOP móka-tára, de végső elkeseredéstek előtt megnyugtatástok végett közlöm véletek, hogy az PC-XUSER\OOP\R4sGLTV könyvtárban található R4sGLTV.ZIP-ben az általam átírt összes Turbo Vision-ös view. (Sajna csak TPU-ban, de akinek ez olyan lelkiproblémát jelent eMail-ezzen, s majd meglátjuk mit tehetünk.)

Minden fontosabb view-ot megvalósítottunk, így azt hiszem az OOP rovatot ezen, view-átirogató formájában kivégzem, avagy megszüntetem, s az OOP rovat – feltehetőleg – ezek után a C nyelv objektum-orientáltságával lesz hivatott foglalkozni. De amint említettem volt, (tiszta ómagyar a stílus ... ?) amit még nem írtam le az megtalálható az R4sGLTV.zip-ben, továbbá bármi kérdésteket eMail-en továbbíthatjátok hozzám.

 
Bérczi László
eMail: PC-XUser@FREEMAIL.C3.HU, Subject: "OOP Rovat"