{------------------------------------------------------------------------------} { Unit Name: kIniSave Purpose : class TIniSave to help making components that saves them automatically to Registry/Ini-file. Also some helper functions Author : Vesa Lappalainen Date: 14.09.1997 Changes: 27.12.1997 + .ini tiedostoa etsitään ensin oletushakemistosta, sitten ohjelman hakemistosta Changes: 12.03.1999 + korjattu toimimaan siten, ettei kaadu vaikka Section ja Item olisivat tyhjiä kun luetaan ja kirjoitetaan .ini-tiedostoa Changes: 03.01.2001 + reads and writes also registry (see IniReadOrder.pas for changing the read/write order) Changes: 07.01.2001 + DoLastSave that can be callad as many time as needed, but does the save only once. Usefull f.ex in Edit-controls that loses the contents before destructor if edit has been visible, but does not loose the contents if not has been visible. Then DoLastSave can be called both in WM_DESTROY handler and destructor. The first on makes the save (see kEditPnl and kinicomp). But be careful that Destroy and WM_DESTROY can come in any order! Seems to be so that in Design time Destroy comes first and in run time WM_Destroy comes first (if control has been visbile). Changed : 15.9.2001/vl + VCL/CLX compiling (define const CLX) ToDo : } {------------------------------------------------------------------------------} unit kIniSave; {------------------------------------------------------------------------------} { Esimerkki luokasta, jota voidaan käyttää koosteena ja luokan ominaisuudet näkyvät Object Inspectorissa. Classes: ======== TIniSave - luokka, joka voidaan luoda toisen controllin sisälle ja näin saadaan "suhteellisen helposti" luokkia, jotka tallettavat itsensä automaattisesti Ini-tiedostoon. TIniSave käyttö ---------------- Ks. esimerkkejä TEditIni ja TCheckBoxIni (kinicomp.pas) Tätä käyttävään luokkaan pitää kirjoittaa ainakin: Attribuutit: FIni - olio, joka on luokkaa TIniSave Published property: property Ini : TIniSave read FIni write FIni; Metodit: Create - luokan alustus ja samalla luodaan FIni Destroy - luokan hajottaja ja samalla tuhotaan FIni Loaded - kutsutaan Ini.Read Lisäksi mahdollisesti uudelleenmääritellään (override) metodi, jonka kohdalla tehdään automaattitalletus. Esimerkiksi TEdit-luokassa tämä on Change. Jos luokassa ei ole AsString-ominaisuutta, mutta siinä on jokin muu ominaisuus (property), jolla luokan sisältö saadaan tekstinä, pitää Create-metodissa vaihtaa esim: Ini.PropName := 'Text'; Properties: AutoSave Talletaanko muutokset automaattisesti. Toteutus riippu käytännössä siitä, mikä on isäkomponentti ja miten AutoSave on toteutettu. Esim. jos isäkomponentti peritään TEdit-luokasta, voidaan Change-metodiin kirjoittaa DoAutoSave-kutsu (tallettaa vain jos AutoSave on päällä). AsString Isäkomponentin arvo merkkijonona. Mikä isäkomponentin ominaisuus käsitellään, riippuu ominaisuudesta PropName IniName Komponentin PropNamessa annettu ominaisuus talletetaan ini-tiedostoon. Ini-tiedoston nimenä käytetään ohjelman_nimi.INI, mikäli ominaisuutta IniName ei ole annettu (oletus). Jos IniName on annettu, mutta ei polkua, käytetään samaa polkua kuin ohjelman nimessä. Muutettu 3.1.2001 käyttämään IniReadOrder-tiedoston metodeja. Tällöin jos IniName='', käytetään lukujärjestystä (oletuksena) uimI eli yritetään ensin rekisterin haarasta HKEY_CURRENT_USER (u), sitten lokaalista ini-tiedostosta (jolla ohjelman nimi) (i), sitten rekisterin haarasta HKEY_LOCAL_MACHINE (m) ja lopuksi ohjelmahakemistossa olevasta ini-tiedostosta (I). Vanha käytös (eli lukeminen lokaalista ja globaalista Ini-tiedostosta) voidaan palauttaa kun lisätään projektin ensimmäiseksi tiedostoksi OldIniReadOrder.pas: initialization begin GlobalIniReadOrder['U'] := 'iI'; end, IniSection Talletetaan IniSection kohdassa annettuun otsikon alle. Jos IniSection = '-', niin komponentin "arvoa" ei talleteta. Jos IniSection = '', (oletus) niin talletetaan komponentin lomakkeen nimen mukaiseen kohtaan. Jos lomakkeen nimi vaikkapa FormCounter talletetaan otsikon [FormCounter-Params] alle. SaveName Talletetaan SaveName= paikkaan. Mikäli SaveName on tyhjä, paikka selvitetään komponentin nimestä. GenName Mikäli SaveName='', talletuspaikka otetaan komponentin nimestä. Jos komponontin nimi sisältää osan GenName, niin tämä osa ja sitä edeltävä osa poistetaan. Esimerkiksi: Komponentin nimi = 'MyParamSpeed' SaveName = '' GenName = 'Param' => paikka = 'Speed' Esimerkissä tulisi MyParamSpeed jos GenName olisi tyhjä tai vaikka 'Parametri'. GenNamen oletus on komponentin luokan nimi -'T'. Esim TEditIni => GenName = 'EditIni' Esimerkki: Omistavan komponentin luokka = 'TEditIni' nimi = 'EditCarFound' GenName = 'Edit' SaveName = '' IniSection = '' IniName = 'iI' PropName = 'Text' Ohjelman nimi = 'CarCount.exe' .exe hakemistossa = 'C:\programs' Lomakkeen nimi = 'FormCounter' Ohjelman aikana kirjoitetaan edit kenttään 'Ferrari' => talletetaan ini-tiedostoon C:\programs\CarCount.ini [FormCounter-Params] CarFound=Ferrari Kun ohjelma käynnistetään seuraavan kerran, on komponentissa MyEditCarFound valmiina arvo Ferrari Functions: ========== function GetNameCaption(c:TControl; const newname,oldname,cap:string) : string; - palauttaa ehdotuksen Caption-kohtaan. Ehdotus saadaan nimestä esim. CheckBoxNimi => Nimi Käyttö: ks. esim. TCheckBoxIni.SetName Ideana on helpottaa sellaisten luokkien tekemistä, joiden Caption (tai vastaava) muuttuu kun luokan nimeä vaihdetaan. procedure IniSaveRestoreAllOptions(comp:TComponent;const order:string=''); - palauttaa kaikkien komponentilla (käytännössä lomakkeella)olevien komponenttien optiot talletettuun arvoonsa Tarkoitettu pääasiassa optiolomakkeen Cancel tai Restore nappulasta kutsuttavaksi. Jos order <> '' niin käsitellään vain ne komponentit, joiden ini.IniName = order, eli tällä tavalla voidaan käsitellä vain tietyn kategorian (esim 'P' = project) "muuttujat". procedure IniSaveSaveAllOldOptions(comp:TComponent;const order:string=''); - tallettaa kaikkien komponentilla (käytännössä lomakkeella)olevien komponenttien optiot palautusta varten Tarkoitettu pääasiassa optiolomakkeen käynistyessä kutusuttavaksi procedure IniSaveSaveAll(comp:TComponent;const order:string=''); - tallettaa kaikkien komponentilla (käytännössä lomakkeella)olevien komponenttien optiot palautusta varten Tarkoitettu pääasiassa optiolomakkeen OK nappulasta kutsuttavaksi procedure IniSaveReadAll(comp:TComponent;const order:string=''); - lukee kaikkien komponenttien arvot Problems: ========== If property AutoSave given with default false, then property changed in Object Inspector does not change hold in run time? If no default for AutoSave, then OI changes holds in runtime! } {------------------------------------------------------------------------------} interface uses classes, {$ifdef CLX} QControls,QStdctrls, {$else} controls,stdctrls, {$endif} IniFiles; {------------------------------------------------------------------------------} type TNotifyIniParamChange = procedure (const s:string); TIniSave = class(TPersistent) // Luokan pitää periytyä TPersistent-luokasta private // mikäli sen ominaisuuksien halutaan FAutoSave : boolean; // näkyvän Object Inspectorissa. Lisäksi FSaveName : string; // luokaa edustava olio pitää esitellä FIniName : string; // published propertyksi. FIniSection : string; FPropName : string; FGenName : string; FOwner : TComponent; FLoaded : boolean; // To prevent multiple loading FInLoading : boolean; // To prevent load and save FOldString : string; FLastSaveDone : boolean; protected function GetIniName : string; virtual; function GetItem : string; virtual; function GetSection : string; virtual; public constructor Create(AOwner : TComponent);virtual; procedure Read; virtual; procedure Load; virtual; procedure Save; virtual; procedure SaveCond; virtual; procedure DoAutoSave; virtual; procedure SetAsString(const s:string); virtual; function GetAsString:string; virtual; function GenNameStored : boolean; virtual; function PropNameStored : boolean; virtual; procedure Restore; virtual; procedure SaveOld; virtual; procedure DoLastSave; virtual; // call this in destructor procedure ReleaseLastSave; virtual; // To help last save done only once published property AutoSave:Boolean read FAutoSave write FAutoSave;// default false; property IniName : string read FIniName write FIniName; property IniSection : string read FIniSection write FIniSection; property SaveName : string read FSaveName write FSaveName; property PropName : string read FPropName write FPropName stored PropNameStored; property GenName : string read FGenName write FGenName stored GenNameStored; property AsString : string read GetAsString write SetAsString stored false; end; {------------------------------------------------------------------------------} function GetNameCaption(c:TControl; const newname,oldname,cap:string):string; procedure IniSaveRestoreAllOptions(comp:TComponent;const order:string=''); procedure IniSaveSaveAllOldOptions(comp:TComponent;const order:string=''); procedure IniSaveSaveAll(comp:TComponent;const order:string=''); procedure IniSaveReadAll(comp:TComponent;const order:string=''); var GlobalCBAfterSet : TNotifyIniParamChange; {------------------------------------------------------------------------------} implementation {------------------------------------------------------------------------------} uses SysUtils, {$ifdef CLX} QForms, {$else} Forms, {$endif} Typinfo,kPropFunc, IniReadOrder ; {------------------------------------------------------------------------------} function GetNameCaption(c:TControl; const newname,oldname,cap:string):string; { Palautetaan uusi ehdotus Captioniksi. Caption palautetaan seuraavin säännöin: vanha uusi nimi caption syy ------------------------------------------------------------------------------- 1: LEC1 LECNimi 1 => Nimi caption on vanhan lopussa 2: ??? EditPnlNimi => Nimi caption tyhjä ja gen.nimi alus } var i:integer; Caption,gen:string; function SetCaption(start,n:string):boolean; begin Result := false; if ( Pos(UpperCase(start),UpperCase(n)) <> 1 ) then exit; Caption := Copy(n,Length(start)+1,200); Result := true; end; begin Result := ''; Caption := cap; gen := copy(c.ClassName,2,50); i := Pos(UpperCase(Caption),UpperCase(oldname)); if ( oldname = '' ) and ( Caption = '' ) then begin SetCaption(gen,newname); Result := Caption; exit; end; if not ( ( i-1 + Length(Caption) = Length(oldname) ) and ( SetCaption(Copy(oldname,1,i-1),newname) ) ) then SetCaption(gen,Caption); Result := Caption; end; { TIniSave } {------------------------------------------------------------------------------} { TIniSave: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX} {------------------------------------------------------------------------------} constructor TIniSave.Create(AOwner : TComponent); begin inherited Create; FOwner := AOwner; FPropName := 'AsString'; FGenName := Copy(FOwner.ClassName,2,20); end; function GetOwnerPath(c:TComponent):string; begin Result := ''; if ( c = nil ) or ( c.Owner is TForm ) or ( c is TApplication ) then exit; if ( c.Owner = nil ) then exit; Result := GetOwnerPath(c.Owner) + c.Owner.Name + '.'; end; function GetFormName(c:TComponent):string; begin if ( c = nil ) or ( c.Owner = nil ) then begin Result := ''; exit; end; if ( c.Owner is TForm ) then Result := c.Owner.Name else Result := GetFormName(c.Owner); end; {------------------------------------------------------------------------------} function TIniSave.GetItem:String; // Selvitään komponentin talletusnimi. Jos SaveName annettu, niin sitä käytet. // Muuten nimi selitetään komponentin nimestä poistamalla mahdollisesti // nimen alusta GenName: esim. jos GenName = Param niin ParamSpeed => Speed // Jos automaattisesti muodostetusta nimestä tulee numerolla alkava, niin // tällöin geneeristä nimeä ei "vähennetä" var p:integer; begin Result := FSaveName; if ( Result <> '' ) then exit; if ( FOwner = Nil ) then exit; Result := FOwner.Name; If ( GenName = '' ) then exit; p := Pos(GenName,Result); if ( p > 0 ) then Delete(Result,1,p+Length(GenName)-1); if ( Result = '' ) or ( Result[1] < '@' ) then Result := FOwner.Name; Result := GetOwnerPath(FOwner) + Result; end; {------------------------------------------------------------------------------} function TIniSave.GetSection:String; begin // FormName-Params Result := FIniSection; if ( FIniSection <> '' ) then exit; if ( FOwner = nil ) or ( FOwner.Owner = nil ) then exit; // Result := FOwner.Owner.Name + '-' + 'Params'; Result := GetFormName(FOwner) + '-' + 'Params'; end; {------------------------------------------------------------------------------} (* function TIniSave.OpenIni : TIniFile; begin Result := NIL; if ( FOwner = Nil ) then exit; if ( csDesigning in FOwner.ComponentState ) then exit; if ( IniName = '-' ) then exit; if ( IniSection = '-' ) then exit; Result := TIniFile.Create(GetIniName(IniName)); end; *) function TIniSave.GetIniName : string; begin Result := '-'; if ( FOwner = nil ) or ( csDesigning in FOwner.ComponentState ) then exit; if ( IniName = '-' ) then exit; if ( IniSection = '-' ) then exit; Result := IniName; end; {------------------------------------------------------------------------------} procedure TIniSave.Read; var s:string; begin // s := IniReadString(OpenIni,GetSection,GetItem,'',true); if ( FOwner = Nil ) or ( csDesigning in FOwner.ComponentState ) then exit; s := IniOrderReadString(GetIniName,GetSection,GetItem,'§'); if ( s <> '§' ) then AsString := s; end; {------------------------------------------------------------------------------} procedure TIniSave.Load; begin if ( FLoaded ) then exit; try FInLoading := true; Read; FLoaded := true; SaveOld; finally; FInLoading := false; end; end; {------------------------------------------------------------------------------} procedure TIniSave.Save; begin if ( FOwner = nil ) or ( csDesigning in FOwner.ComponentState ) then exit; if ( FInLoading ) then exit; // IniWriteString(OpenIni,GetSection,GetItem,AsString,true); IniOrderWriteString(GetIniName,GetSection,GetItem,AsString); SaveOld; end; {------------------------------------------------------------------------------} procedure TIniSave.SaveCond; begin if ( FOwner = nil ) or ( csDesigning in FOwner.ComponentState ) then exit; if ( csLoading in FOwner.ComponentState ) then exit; Save; end; {------------------------------------------------------------------------------} procedure TIniSave.DoAutoSave; begin if ( AutoSave ) and ( AsString <> FOldString ) then SaveCond; end; {------------------------------------------------------------------------------} procedure TIniSave.SetAsString(const s:string); begin SetStringProperty(FOwner,PropName,s); end; {------------------------------------------------------------------------------} function TIniSave.GetAsString:string; begin Result := GetStringProperty(FOwner,PropName,''); end; //----------------------------------------------------------------------------- function TIniSave.GenNameStored : boolean; // virtual; begin Result := GenName <> Copy(FOwner.ClassName,2,20); end; //----------------------------------------------------------------------------- function TIniSave.PropNameStored : boolean; // virtual; begin Result := PropName <> 'AsString'; end; procedure TIniSave.Restore; begin AsString := FOldString; end; procedure TIniSave.SaveOld; begin FOldString := AsString; end; procedure TIniSave.DoLastSave; begin if ( FLastSaveDone ) then exit; DoAutoSave; FLastSaveDone := true; end; procedure TIniSave.ReleaseLastSave; begin FLastSaveDone := false; end; { TIniFunc } //----------------------------------------------------------------------------- type TIniFunc = procedure (ini:TIniSave); procedure IniSaveSaveOld(ini:TIniSave); begin ini.SaveOld; end; procedure IniSaveRestore(ini:TIniSave); begin ini.Restore; end; procedure IniSaveSave(ini:TIniSave); begin ini.Save; ini.SaveOld; end; procedure IniSaveRead(ini:TIniSave); begin ini.Read; ini.SaveOld; end; procedure ComponentDoAllIniSave(comp:TComponent;inifunc:TIniFunc;const order:string); var i:integer; c: TComponent; o: TObject; ini : TIniSave; begin for i:=0 to comp.ComponentCount-1 do begin c := comp.Components[i]; o := GetObjProperty(c,'Ini'); if not ( o is TIniSave ) then begin ComponentDoAllIniSave(c,inifunc,order); continue; end; ini := TIniSave(o); if ( order = '' ) or ( order = ini.IniName ) then inifunc(ini); end; end; procedure IniSaveSaveAllOldOptions(comp:TComponent;const order:string); begin ComponentDoAllIniSave(comp,IniSaveSaveOld,order); end; procedure IniSaveRestoreAllOptions(comp:TComponent;const order:string); begin ComponentDoAllIniSave(comp,IniSaveRestore,order); end; procedure IniSaveSaveAll(comp:TComponent;const order:string); begin ComponentDoAllIniSave(comp,IniSaveSave,order); end; procedure IniSaveReadAll(comp:TComponent;const order:string); begin ComponentDoAllIniSave(comp,IniSaveRead,order); end; end.