Pliki INI

Adam Boduch

1 Wprowadzenie
2 Budowa
3 Tworzenie i otwieranie
4 Edycja
5 Usuwanie kluczy
6 .NET

Wprowadzenie

Windows umożliwia zapisywanie konfiguracji w specjalnych plikach tekstowych o charakterystycznej budowie. Pliki te mają rozszerzenie .ini i zawierają informacje dotyczące konfiguracji danego programu. Sam możesz się o tym przekonać - chyba najpopularniejszym plikiem INI w Windows jest system.ini. Dzięki temu rodzajowi plików masz możliwość zapisania konfiguracji swojego programu. Jest to wygodny sposób na zapisanie jakiś danych. Wyobraź sobie program, który wymaga od użytkownika podania pewnych danych. Przykładowo będzie to imię. W takim przypadku po każdym uruchomieniu programu użytkownik byłby zmuszony do podania swojego imienia w celu dalszego użytkowania programu. Dzięki plikom INI po wpisaniu imienia na dysku może zostać stworzony plik, który zawierał będzie te informacje. Po następnym uruchomieniu programu będzie on sprawdzał, czy ten plik w określony miejscu istnieje - jeżeli tak odczytuje z niego informacje o imieniu - jeżeli nie istnieje to wyświetla okienko z informacją i wymaga podania.

Budowa

Budowa pliku INI nie jest skomplikowana. Plik INI można edytować za pomocą dowolnego edytora tekstu - nawet za pomocą notatnika. Każdy plik dzieli się na tzw. sekcje. Za pomocą tych sekcji możesz podzielić plik na poszczególne kategorie (opcje ogólne, konfiguracja, dane itp. ). Każda sekcja natomiast może zawierać klucze i wartości.

[Main]
Login=Bald

Sekcja zawsze wpisywana jest w nawiasie kwadratowym. Wszystkie klucze występujące w pliku wpisywane są pod spodem. Konstrukcja jest taka:

klucz=wartość

Pliki te mogą także zawierać komentarze. Komentarze mogą opisywać poszczególne wartości - do czego służy dany klucz i jaki efekt można osiągnąć zmieniając wartość danego klucza. Przykładowo w języku skryptowym PHP cała konfiguracja mieści się w pliku INI z którego program czyta potrzebne informacje. Chcąc więc zmienić ustawienia programu trzeba edytować plik php.ini. Żeby wiedzieć co edytować, co zmienić i co do czego służy każda wartość jest skomentowana. Komentować możesz tylko jedną linię za pomocą znaku ; (średnik). Plik .ini może równie dobrze wyglądać tak a program i tak poradzi sobie z jego odczytem:

;  Copyright (c) 2002 by Adam Boduch
;
;  Jeżeli chcesz zresetowac ustawienia - usun plik setup.ini z dysku
[Main]
Login=Bald	; komentarz

Zwróć jednak uwagę na jedną rzecz. Między linią konfiguracji (klucza), a komentarzem jest przerwa. Przerwa ta jest konieczna, aby program prawidłowo odczytał dane. Przerwą w tym wypadku musi być tabulator.

Tworzenie i otwieranie

Na samym początku musisz wiedzieć jedną rzecz. Mianowicie większość plików INI jest trzymana domyślnie w katalogu Windowsa. (np. C:\Windows). Więc tworząc plik INI musisz podać jego pełną ścieżkę... jeżeli oczywiście chcesz ten plik zapisać w innym miejscu niż katalog Windowsa. Nasuwa się więc pytanie jak pobrać np. ścieżkę naszego uruchomionego programu. Chyba nie będzie z tym problemu. Kompletną ścieżkę programu podaje funkcja ExeName, z klasy TApplication. Czyli:

S := Application.ExeName;

Zmienna S zawiera teraz ścieżkę do uruchomionego programu - np: C:\Moje programy\2\moj_program.exe. Teraz nasuwa się pytanie jak z tej wartości wyciągnąć tylko ścieżkę programu? Nie ma problemu. Do tego VCL udostępnia nam parę przyjemnych funkcji. (patrz tabela poniżej).

Funkcja Opis
ExtractFileDir Funkcja zwraca katalog pliku bez jego nazwy i nie zakończoną znakiem "".
ExtractFilePath Zwraca kompletny katalog pliku ze znakiem "" na końcu.
ExtractShortPathName Funkcja zwraca skróconą nazwę pliku - np: :\Progra~1\Borland\Delphi\Bin\Delphi32.exe
ExtractFileDrive Funkcja zwraca jedynie literę dysku na której znajduje się plik.
ExtractFileName Funkcja zwraca z podanej ścieżki jedynie nazwę pliku.
ExtractFileExt Ta funkcja natomiast podaje jedynie rozszerzenie z podanej w parametrze ścieżki.
ChangeFileExt Funkcja zmienia rozszerzenie pliku.
Do operowania na plikach INI Delphi udostępnia nam wygodną klasę TINIFile. Żeby skorzystać z tej klasy należy do sekcji uses dodać moduł INIFiles.

Na sam początek, aby skorzystać z tej klasy trzeba oczywiście wywołać jej konstruktor - zapis może wyglądać tak:

var
  INI : TINIFile;
begin
  INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
  try
    { jakieś instrukcje }
  finally
    INI.Free;
  end;
end;

W konstruktorze tej klasy należy podać nazwę pliku INI. W tym parametrze podałem ścieżkę gdzie plik ma zostać stworzony. ExtractFilePath(Application.ExeName) podaje jedynie lokalizację katalogów gdzie znajduje się program. Czyli w rezultacie plik znajdzie się w katalogu {nazwa_katalogu_z_programem}\setup.ini.

Uwaga! Nie podając w tym miejscu pełnej ścieżki katalogu plik INI zostanie stworzony w katalogu z Windowsem.

Edycja

Klasa TINIFile udostępnia bardzo proste i wygodne metody, które służą do odczytu lub zapisu danych. Do pliku INI mogą być zapisane: liczby, tekst, liczby typu Double (zmiennoprzecinkowe), data i czas typu TDateTime, wartość logiczna typu Boolean oraz całe strumienie danych.

Nazwy poleceń są intuicyjne bo np. co robi metoda WriteString? Można się tego domyśleć - zapisuje tekst. A metoda, która ten tekst odczytuje? Nazywa się ReadString. Jak widzisz zamieniamy tylko słowo Write na Read a drugi człon funkcji to typ danych. Zapis nowych wartości do pliku mógłby wyglądać następująco:

INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
try
  INI.WriteString('Main', 'Login', Login); 
finally
  INI.Free;
end;

W poleceniu WriteString pierwszy parametr, który musi zostać podany to nazwa sekcji. Nie martw się - jeżeli plik nie istnieje lub nie istnieje taka sekcja w tym pliku - zostanie ona utworzona. Jeżeli natomiast istnieje - zostaną dopisane do niej nowe dane - tym nie musisz zaprzątać sobie głowy. Drugi parametr to nazwa klucza - w tym wypadku klucz nazywać się będzie Login. Ostatni parametr to wartość tego klucza, czyli wartość która będzie przypisana do danego klucza - mam nadzieje, że tutaj jest wszystko jasne.

A co z odczytem? Zobacz:

INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
try
  Login := INI.ReadString('Main', 'Login', 'Adam Boduch'); 
finally
  INI.Free;
end;

Metoda odczytująca - ReadString także posiada trzy parametry. Pierwszy to nazwa sekcji, z której dane zostaną odczytane. Drugi parametr to nazwa klucza, z którego mają być odczytane dane. Ostatni parametr oznacza wartość domyślną w przypadku, gdy klucz o podanej nazwie nie istnieje lub gdy nie istnieje sam plik INI.

Na podstawie tych poleceń możemy napisać już jakiś program, który z plików INI będzie korzystał. Np. program, który po uruchomieniu będzie sprawdzał, czy w katalogu istnieje plik setup.ini - jeżeli nie będzie wyświetlał okienko z prośbą o podanie loginu. Po wpisaniu jakiegoś loginu dane zostaną zapisane do pliku INI. Po ponownym uruchomieniu programu dane te będą widniały na formie, a żadne okienko podczas uruchamiania się nie pokaże.

W przypadku wyświetlania nowego okienka nie musimy się zajmować tworzeniem nowej formy. Moduł Dialogs.pas udostępnia do tego funkcję InputBox. Ta funkcja zwraca informację wpisaną przez użytkownika w okienku informacyjnym.

Oczywiście my sami możemy dokonać wyboru co ma się znaleźć na belce tytułowej lub wewnątrz tego okna. Można by także było w tym wypadku tworzyć nową formę, ale w tym wypadku trzeba by było oprogramować parę zdarzeń, a wiadomo - programista to człowiek leniwy...

Wywołanie funkcji InputBox może wyglądać tak:

Login := InputBox('Rejestracja...', 'Podaj login', '');

Zmienna Login od tej pory przechowywać będzie informację wpisaną w tym oknie. Pierwszy parametr tego polecenia to tekst, który wyświetlony zostanie na belce tytułowej. Drugi parametr to tekst, który zostanie wyświetlony na komponencie Label znajdującym się w tym oknie. Ostatni parametr to tekst domyślny w kontrolce edycyjnej Edit. Ja w tym wypadku wpisałem '' co oznacza tekst pusty. Wygeneruj zdarzenie OnCreate formy i wpisz w niej taki kod:

procedure TMainForm.FormCreate(Sender: TObject);
var
  INI : TINIFile;
  Login : String; // login wczytany z pliku INI
begin
  { na samym początku należy sprawdzić, czy plik jest utworzony }
  if not FileExists(ExtractFilePath(Application.ExeName) + 'setup.ini') then
  begin
    { jeżeli nie można znaleźć pliku - wyświetl okienko z informacją z prośbą o
    wpisanie swojego loginu }
    Login := InputBox('Rejestracja...', 'Podaj login', '');

    if Login <> '' then // sprawdź, czy login nie jest pusty
    begin
      { jeżeli tak nie jest - utwórz plik }
      INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
      try
        INI.WriteString('Main', 'Login', Login); // zapisz do pliku login
        lblMain.Caption := 'Witaj ' + Login;
      finally
        INI.Free;
      end;

      Exit;  // nie rób już nic...
    end
    else
      Application.Terminate; // jeżeli użytkownik nie wpisał loginu - zakończ aplikacje
  end;

  { ten kod zostanie wykonany tylko wtedy, gdy plik INI został znaleziony }
  INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
  try
    Login := INI.ReadString('Main', 'Login', 'Adam Boduch'); // następuje odczyt loginu
    lblMain.Caption := 'Witaj ' + Login; // następnie wyświetl login na komponencie
  finally
    INI.Free;
  end;
end;

Przyznam się, że parę warunków If tutaj się znajduje... Użytkownik w oknie może przecież równie dobrze wpisać pusty tekst więc trzeba się przed tym zabezpieczyć i wykonać kod pod warunkiem iż tekst wpisany w kontrolce będzie różny od '', czyli pustego łańcucha. Ten kod zostanie wykonany w przypadku, gdy plik w katalogu z programem się nie znajduje (funkcja FileExists).

Polecenie Opis
ReadBinaryStream/WriteBinaryStream odczytuje (ReadBinaryStream) i zapisuje WriteBinaryStream) z pliku dane w postaci strumienia, trzeci parametr musi być zmienną typu TStream
ReadBool/WriteBool odczytuje i zapisuje dane typu Boolean, czyli True lub False
ReadDate/WriteDate odczytuje (ReadDate) i zapiusuje (WriteDate) wartość typu TDate
ReadDateTime/WriteDateTime odczytuje i zapisuje dane typu TDateTime, czyli datę i czas
ReadTime/WriteTime odczytuje i zapisuje w pliku jedynie czas (zmienna typu TTime)
ReadFloat/WriteFloat odczytuje i zapisuje dane zmiennoprzecinkowe, czyli typu Double
ReadInteger/WriteInteger odczytuje i zapisuje liczby (typ Integer)
ReadString/WriteString zapisuje i odczytuje tekst (typ String)
W tabeli powyżej przedstawiono wszystkie metody służące do zapisywania i odczytywania danych różnych typów z pliku INI. Metody ReadBinaryStream i WriteBinaryStream są nowością w Delphi 6.

Usuwanie kluczy

Klasa [delphi/TINIFile]] umożliwia także odczytywanie nazw sekcji lub wszystkich kluczy zawartych w sekcji. Także usuwanie nie powinno sprawić problemu, gdyż są do tego odpowiednie metody. Odczytywanie wszystkich wartości w sekcji następuje za pomocą metody ReadSections. Metoda ta odczytuje wszystkie sekcje w pliku INI do zmiennej typu TStrings. Czyli kod odczytania wszystkich sekcji mógłby wyglądać tak:

INI := TINIFile.Create('setup.ini');
Sections := TStringList.Create;
Try
  INI.ReadSections(Sections);
finally
  INI.Free;
end;

Po wykonaniu takiego kodu zmienna Sections zawierać będzie wszystkie sekcje znajdujące się w danym pliku. W tym momencie każda linia zmiennej Sections to jedna sekcja tego pliku. Odczytać ją można np. tak:

ShowMessage(Sections[0]);

Tutaj odczytujemy pierwszą linię zmiennej, czyli pierwszą sekcję.

Uwaga! W przypadku próby odczytania linii która to nie znajduje się w zmiennej wyświetlone zostanie okienko z błędem. Jeżeli np. linii w zmiennej jest 10, a Ty próbujesz odczytać 11 program wyświetli błąd: "List index out of bounds [11]".

Równie dobrze zamiast tworzyć nowy obiekt typu TStrings można od razu odczytać sekcję do komponentu - np. TListBox:

procedure TMainForm.FormCreate(Sender: TObject);
var
  INI : TINIFile;
begin
  INI := TINIFile.Create('win.ini');  // odczytaj plik
  try
    INI.ReadSections(lblSections.Items); // wyświetl w obiekcie wszystkie sekcje
  finally
    INI.Free;
  end;
end;

lblSections to w tym wypadku nazwa komponentu typu ListBox. Po kliknięciu na daną pozycję w tym komponencie można odczytać wszystkie wartości znajdujące się w tej sekcji. Wygeneruj w tym celu zdarzenie OnClick komponentu ListBox, a następnie wpisz:

procedure TMainForm.lblSectionsClick(Sender: TObject);
var
  INI : TINIFile;
begin
  INI := TINIFile.Create('win.ini');
  try
    lbSection.Caption := 'Sekcja: ' + lblSections.Items[lblSections.ItemIndex];
    { odczytaj do komponentu wartości wszystkich sekcji }
    INI.ReadSectionValues(lblSections.Items[lblSections.ItemIndex], lblSection.Items);
  finally
    INI.Free;
  end;
end;

Klasa udostępnia także metodę ReadSections, które to odczytuje jedynie nazwy kluczy w danej sekcji. Pierwszym parametrem w tej metodzie musi być nazwa sekcji do odczytania, a drugi parametr to zmienna typu TStrings. Ja użyłem tutaj metodę ReadSectionsValues, która odczytuje nie tylko nazwy kluczy, ale także wartości w danym kluczu zawarte.

Komponent ListBox posiada właściwość ItemIndex określającą numer pozycji w która jest aktualnie zaznaczona. Korzystając z tej wartości można pobrać tekst zaznaczonej w tym komponencie pozycji. W przypadku, gdy napiszesz tak:

lblSections.Items[0];

Odczytujesz pierwszą pozycję w komponencie, a żeby odczytać zaznaczoną pozycję należy w tym miejscu podstawić wartość właściwości ItemIndex, czyli całość może wyglądać tak:

lblSections.Items[lblSections.ItemIndex]

Jak to to teraz usunąć? Jeżeli chcesz usunąć całą sekcję należy wywołać metodę EraseSection podając w parametrze nazwę sekcji - np:

INI.EraseSection('Moja sekcja');

Po takim zabiegu wszystkie klucze z danej sekcji zostaną usunięte. Możesz także usunąć pojedynczy klucz wywołując metodę DeleteKey. Ta metoda musi jedynie zawierać w parametrze nazwę klucza do usunięcia - to wszystko!

.NET

Od Delphi 8 pliki INI już nie są w tak powszechnym użyciu i ich używanie nie jest zalecane. Zamiast tego zalecane jest używanie plików XML do przechowywania danych.

24 komentarzy

Artykuł the best!

a:

TColor = TGraphicsColor;
TGraphicsColor = -$7FFFFFFF-1..$7FFFFFFF;

Nie jest to do końca to samo co integer, spróbujcie wpisać na przykłąd do TColor wartość większą od $7FFFFFFF!
Po za tym można by zadeklarować coś takiego:

TMyInteger = Integer;

Taki zapis jak wyżej to jest to samo :)

Jezu, TColor to Integer. Zrub sobie kiedyś tak:
var
I : Integer;
C : TColor;
begin
C := Form1.Color;
I := C;
end;

Pewnie pomyślisz żę wywali błąd
Incopatible types "Tcolor" and "Integer", a tu zonk. Zadziała dobrze.

Ps; możesz nawet looknąć na funkcję RGB;
Zwraca wynik w Integer;

... lub WriteColor:

if Label1.Color = clBlue then
begin
PlikINI.WriteString('Sekcja','ZapisanyKolor','Blue');
end;

:P

Mozna zrobic ReadColor IFem

if PlikINI.ReadString('Sekcja','Kolor','') = 'White' then
begin
Label1.Color := clWhite;
end;

Jesli to Integer to jestem eukaliptus....

TColor to przeciez Integer... nie?

FILL ->
" INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'program.ini');
try
Form1.Left := INI.ReadInteger('00', 'X', 826);
Form1.Top := INI.ReadInteger('00', 'Y', 0);
Form1.Width := INI.ReadInteger('00', 'Xsize', 199);
Form1.Height := INI.ReadInteger('00', 'Ysize', 488);
finally
INI.Free;
end;
"

Odpowiedź na Twoje pytanie dużo niżej.

tomalla ->
"INI.WriteString('sekcja','klucz',Memo1.Lines.Text);"

Mi się podoba ten artykuł.

:P

Procek ->

"ExtractFilePath(Application.ExeName);"

Mam pytanie. Jak zapisać kolor do pliku INI. ReadColor nie istnieje. Nie ma czegoś takiego jak ColorToStr :/

???

@mr_Zola
a co za różnica czy Windows czy biblioteki? Istnieje jakiś format plików czy to mp3 czy doc i są programy je obsługujące ja podobnie postrzegam pliki ini.. czyżbym się mylił ?? :P
@tomalla
"ReadColor" nie ma .. ale możesz sama napisać ;P
@procek
raczej nie lepiej... bo zwraca aktualny katalog nie katalog programu, więc np. odpalając ten program w dosie lub zmieniając w skrócie właściwość "Rozpocznij w" zwróci inną ścieżkę

A mi nie działa 'Application.ExeName', ale wpisalem 'ParamStr(0)' i jest :P

A nie lepiej GetCurrentDir ??

A mam jeszcze jedno pytanko - Czy jest coś w stylu ReadColor? Otóż chcę żeby mi wczytał kolor z pliku INI. Na przykład:
...
Form1.Color := INI.Read...?...('General', 'Color', clBtnFace);
...

Proszę o szybką odpowiedź

"Windows umożliwia zapisywanie konfiguracji w specjalnych plikach tekstowych o charakterystycznej budowie..."
czy to windows umożliwia czy może jakaś biblioteka?

Ja tam zadnej tabelki nie widze :P
W kazdym razie tutaj (Rozdział 6) masz bardziej rozbudowany tekst o INI. Moze Ci pomoze.

jest tu tabelka ze zmiennymi, jakie można wpisać do tego ini, np. writeinteger itd, ale jak można tam wpisać zmienną tstring? dokładniej z memo?!?!

odswiezam art jak dla mnie super pomocny Panie Adamie tak trzymac!!!

A mi nie działa 'Application.ExeName', ale wpisalem 'ParamStr(0)' i jest :P

Snowak: no to sie troche spóźniełeś.... jakieś 2 lata

buuuuuuuu! A ja wlasnie chcialem o tym arta napisac!

no...<ort>artykół</ort> nie taki zły.
Dzienki temu <ort>artykółowi</ort>, moje progi mają ustawienia, własciwości, konfiguracje....
Tylko nie wiem czy to ten sam co kiedyś czytałem, bo w tamtym była tabelak z możliwościami innymi niż tylko WRITE i READ strin i integer.
Dużo też sam doszedłem, ale bardzo szybko.
Wystarczy zrozumieć o co chodzi i ze wyszystkim innym (readbool, i inne) jest tak samo.

Artykul do bani. Jezuuu, ja to pisalem????

W której procedurze wczytać położenie formy wcześniej zapisane w ini?

'01' to nazwa klucza - w programie nie ma to takiego znaczenia, gdyz moze byc jakakolwiek.

czy w pliku muszą być '01' itd. ?

a jak przeszukac plik, jesli zawiera w sobie dpa=dpsko to zeby odczytalo d*psko do zmiennej ?? napisz na szkola81@go2.pl
narq