e-mail    Debatní kniha    Mapa stránek    Hlavní  
 perličky 
 

Přepínač pro Turbo Vision

Jaroslav Tulach

Následujícími řádky bych rád pomohl všem, kteří používají Turbo Vision a mají stejný nebo podobný problém jako já.

Většina programátorů v TV využívá dialogová okna a někteří odvážlivci si je dokonce načítají ze souborů resource. Obvyklá okna obsahují vstupní řádky, tlačítka a další objekty, jejichž pomocí uživatel zadává nebo modifikuje vstupní data. Pokud je okno určeno pouze k jednomu tvaru vstupu, je vše v pořádku, okno lze pohodlně uložit jako resource a při každém použití načíst ze souboru, provést, převzít data. Avšak pokud by tvar okna měl zůstat v podstatě nezměněn a pouze některá vstupní řádka (nebo jiný objekt) by neměla být přístupná pro editování, nastává nepříjemný problém.

Nepříjemný problém? Ale ne! Přece si mohu vytvořit dva tvary dialogového okna a oba uložit do souboru resource (fuj!). Řešení to je, ale moc pěkně nevypadá. A když bude zapotřebí více než dvou podobných oken, bude většina souboru resource zaplněna zbytečně se opakujícími informacemi.

Co s tím? Aha, přece by šlo předefinovat TInputLine a pomocí nějakého přepínače buď pouštět nebo mazat události, které by do tohoto okna přicházely. To je ono! Takhle by to šlo. Ale jeden háček to přece jen má - každý typ objektu, který je potřeba "vypnout", je nutné nejprve předefinovat, čímž opět naroste délka programu. Příliš obecné řešení to skutečně není. Snad by přicházelo do úvahy, pokud se "vypíná" jen jeden typ objektů. Ale pokud je jich více, tak z toho opět nic nebude.

Hmm... Tak zase trochu jinak a tentokrát naposledy. Téměř každý objekt v dialogovém okně má svůj název (TLabel). Nešlo by tedy podědit po TLabelu a vytvořit šikovného následovníka, který by vypínal a zapínal příslušný (s ním spojený) objekt? To by přece šlo! A navíc by stačila jedna nová třída objektů a bylo by možné vypínat a zapínat libovolné objekty (vstupní řádky, ...).

Vypadá to dobře. Teď zbývá už jen to nedůležitější - napsat program. Nového potomka zdědíme po TLabelu a pojmenujeme ho TSwitch (přepínač). Komunikaci mezi tímto objektem a programem zajistíme pomocí předefinování metod SetData, GetData a DataSize tak, aby pracovaly s jedinou proměnnou typu Boolean (délka jeden bajt). Pokud SetData přijme hodnotu TRUE, znamená to, že objekt spojený s TSwitchem lze rozsvítit; přijme-li FALSE, nastaví sebe a spojený objekt do stavu, kdy je nelze zvolit ani myší ani klávesnicí.

Detailů už bylo dost, teď jedna podstatná věc - totiž jak TSwitch ovládat. Pokud jsme dosud pro komunikaci s dialogovým oknem používali datovou strukturu (1) a chceme-li umožnit vypínání první vstupní řádky, upravíme strukturu do tvaru (2).

(1) record (2) record Slovo: String [20]; Slovo: String [20]; S1: String [3]; SlovoAno: Boolean; end; S1: String [3]; end;

Přičemž je nutné, aby TSwitch byl vložen ihned za prvním InputLine. Před zavoláním metody SetData nejprve nastavíme do proměnné SlovoAno buď TRUE nebo FALSE v závislosti na stavu, do kterého chceme první vstupní řádku dostat.

A je to z krku. Máme plně funkční přepínač objektů pro TV, který nemá nejmenší chybičku! Nemá chybičku? Kdepak, samozřejmě, že má (jako každý program). Např. copak musí mít každé okénko v dialogovém okně svůj nadpis? Nemusí. Ale co pak? Jednou z možností je vytvořit "prázdný" TSwitch o rozměrech 0 x 0. Ten bude neviditelný a bude pouze přijímat data při SetData. Ale pozor, čeká na nás další záludnost - text, který má TLabel zobrazovat, totiž nesmí být prázdný řetězec, jinak dojde k chybě (ne vždy, pouze v chráněném režimu, protože TV se pokusí přečíst řetězec na adrese NIL, a to je právě v tomto režimu zakázáno). Druhou chybičkou je, že žádný předdefinovaný objekt v TV nezmění barvu ani nic jiného, pokud je ve "vypnutém stavu". A tak se může stát, že někteří uživatelé si budou lámat hlavu nad tím, proč zrovna tenhle objekt nelze vybrat a zapsat do něj vytoužená data.

A to je vše, přátelé. Zdrojový text k perličce je přiložen v souboru SWITCH.PAS a ukázka použití objektu TSwitch v TSTSWITCH.PAS. Doufám, že nejsem sám, koho trápil výše popsaný problém a že se najde někdo, komu jsem pomohl a ušetřil nějakou tu chvilku zbytečného sezení u počítače.

{*** Switch ***}
{*** objekt umožňující vypínání a zapínání jiných ***}
{*** objektů v dialogových boxech ***}
{*** (c) SELEX 1993 ***}
{*** verze 1.0B ***}

unit Switch;
interface
uses Objects, Views, Drivers, Dialogs;

type
  PSwitch = ^TSwitch;
  TSwitch = object (TLabel)
    Constructor Init (var R: TRect; AName: String;
                             ALink: PView);
    Procedure SetData (var R); virtual;
    Procedure GetData (var R); virtual;
    Function DataSize: Word; virtual;
  end;

const
  RSwitch: TStreamRec = (
    ObjType: 64850;
    VmtLink: Ofs(TypeOf(TSwitch)^);
    Load: @TSwitch.Load;
    Store: @TSwitch.Store);

Procedure RegisterSwitch;

implementation

{*** registrace pro stream ***}

Procedure RegisterSwitch;
begin
  RegisterType (RSwitch);
end;

{*** TSwitch ***}

Constructor TSwitch.Init (var R: TRect; AName: String;
                                 ALink: PView);
begin
  if AName = '' then AName := ' ';
  {pokud by byl názvem prázdný řetězec,
   mohlo by dojít k chybě v chráněném režimu}
  TLabel.Init (R, AName, ALink);
end;
Procedure TSwitch.SetData (var R);
begin
  if (Link <> Nil) then begin
    if Boolean (R) then begin
      Link^.Options := Link^.Options or ofSelectable;
      {připojené okno je možno vybrat}
      Link^.SetState (sfDisabled, False);
      {a ono samo může přijímat události}
      SetState (sfDisabled, False);
      {připraví se na příjem událostí}
    end else begin
      Link^.Options := Link^.Options
                       and not ofSelectable;
      {nelze zvolit připojené okno}
      Link^.SetState (sfDisabled, True);
      {nesmí přijímat zprávy}
      if Owner^.Current = Link then
                          Owner^.SelectNext (False);
      {pokud je náhodou aktuální, tak se posuň
       na další (připojené okno)}
      SetState (sfDisabled, True);
      {vypne všechny události jdoucí do TLabel,
       aby nebylo možné aktivizovat
      spojené okno pomocí TLabel (myší nebo klávesou)}
    end;
  end;
end;

Procedure TSwitch.GetData (var R);
begin
  Boolean (R) := (State and sfDisabled = 0);
  {pokud není vypnut vstup událostí, tak ve stavu TRUE}
end;

Function TSwitch.DataSize: Word;
begin
  DataSize := SizeOf (Boolean);
end;
end.

{*** program testující SWITCH.PAS ***}

{*** (c) SELEX 1993 ***}

uses Objects, App, Dialogs, Switch, Drivers,
     Views, Menus;

const
  cmDialog = 1000;

type
  PApp = ^TApp;
  TApp = object (TApplication)
    Procedure InitStatusLine; virtual;
    Procedure HandleEvent (var Event: TEvent); virtual;
  end;

{*** TApp ***}

Procedure TApp.InitStatusLine;
var
  R: TRect;
begin
  GetExtent (R);
  R.A.Y := R.B.Y - 1;
  StatusLine := New (PStatusLine, Init (R,
    NewStatusDef ($0000, $ffff,
      NewStatusKey ('~Alt-X~ Konec', kbAltX, cmQuit,
      NewStatusKey ('~F1~ Test použití unity Switch',
                    kbF1, cmDialog, Nil)),
    Nil)
  ));
end;

Procedure TApp.HandleEvent (var Event: TEvent);
type
  TDataRec = record
    Jmeno: String [30];
    AnoJmeno: Boolean;
    Pohl: Word;
    AnoPohlavi: Boolean;
  end;

var
  R: TRect;
  Dlg: PDialog;
  V: PView;
const
  Data: TDataRec = (
    Jmeno: '';
    AnoJmeno: True;
    Pohl: 0;
    AnoPohlavi: True
  );
  {data pro dialog}
begin
  TApplication.HandleEvent (Event);
  if (Event.What and evMessage <> 0)
      and (Event.Command = cmDialog) then begin
    {vytvoření dialogového okna}
    R.Assign(29,5,56,16);
    New(Dlg, Init(R, 'Test unity Switch'));

    R.Assign(5,3,22,4);
    V := New(PInputLine, Init(R, 30));
    Dlg^.Insert(V);

    R.Assign(5,2,11,3);
    Dlg^.Insert (New(PSwitch, Init(R, '~J~méno', V)));
    {přepínač umožňující nebo znemožňující
     vyvolání okénka pro změnu jména}

    R.Assign(5,5,22,7);
    V := New(PRadioButtons, Init(R,
      NewSItem('Mužské',
      NewSItem('Ženské',
      Nil))
    ));
    Dlg^.Insert(V);

    R.Assign(5,4,13,5);
    Dlg^.Insert (New(PSwitch,
                 Init(R, '~P~ohlaví', V)));
    {přepínač umožňující nebo znemožňující
     vyvolání okénka pro změnu pohl.}

    R.Assign(3,8,13,10);
    V := New(PButton, Init(R, 'O~K~', cmOK,
             bfDefault));
    Dlg^.Insert (V);

    R.Assign(14,8,24,10);
    V := New(PButton, Init(R, 'Zruš', cmCancel,
             bfNormal));
    Dlg^.Insert(V);

    Dlg^.SelectNext(False);

    {umožní zadat i jméno i pohlaví}
    Data.AnoJmeno := True;
    Data.AnoPohlavi := True;
    Dlg^.SetData (Data);
    if DeskTop^.ExecView (Dlg) <> cmCancel then
                          Dlg^.GetData (Data);

    {pouze jméno}
    Data.AnoJmeno := True;
    Data.AnoPohlavi := False;
    Dlg^.SetData (Data);
    if DeskTop^.ExecView (Dlg) <> cmCancel then
                          Dlg^.GetData (Data);

    {pouze pohlavi}
    Data.AnoJmeno := False;
    Data.AnoPohlavi := True;
    Dlg^.SetData (Data);
    if DeskTop^.ExecView (Dlg) <> cmCancel then
                          Dlg^.GetData (Data);

    Dispose (Dlg, Done);
  end;
end;

{*** program ***}
var
  A: TApp;
begin
  A.Init;
  A.Run;
  A.Done;
end.



Pascal - hlavní
Překladače
Vlastní články
Převzaté články
Věci na stáhnutí
Odkazy k tématu
BP7 buglist
Chyba Run-time 200

BASIC