JustPaste.it

Optymalizacja kodu w Delphi

Początkujący programiści często umieszczają w swoich kodach wiele niepotrzebnych instrukcji. Dzięki temu kod staje się mniej przejrzysty, bardziej zagmatwany i przede wszystkim ? wykonuje się wolniej. Procesor musi wówczas przetworzyć więcej instrukcji do wykonania tego samego zadania. Należy się w tym momencie zastanowić, czy nie dałoby się danego problemu rozwiązać prościej. Często takie pułapki wynikają po prostu z niewiedzy początkujących programistów. W tym artykule znajdziesz kilka praktycznych wskazówek jak zwiększyć wydajność swojej aplikacji oraz większyć przejrzystość kodu.


Spis treści

     1 Kilka wskazówek
     2 Język Delphi
          2.1 Virtual czy Dynamic ?
          2.2 Instrukcje warunkowe
          2.3 Typ Boolean w tablicach
          2.4 Zbiory
          2.5 Łańcuchy
               2.5.1 Łączenie znaków w ciągach
               2.5.2 Przekazywanie parametrów przez wartość
               2.5.3 Łączenie łańcuchów
          2.6 Zmienne lokalne
          2.7 Procedury zagnieżdżone
          2.8 Liczby całkowite
          2.9 Dynamiczne tworzenie form
     3 VCL
          3.1 Dodawanie wielu elementów do list



Optymalizacja to czynność polegająca ogólnie na przyspieszeniu działania aplikacji lub zmniejszenia ilości pamięci potrzebnej do jej działania. Istnieją pewne granice optymalizacji, których nie jesteś w stanie ominąć. Program wykonujący skomplikowane operacje na bazie danych zawierającej setki tysięcy rekordów nie będzie działał szybciej (mimo dobrej optymalizacji) dopóki sprzęt na którym działa nie będzie lepszy lub dopóki wymagane będą aż tak skomplikowane operacje.

Kilka wskazówek


W programie zawsze włączaj optymalizacje kompilatora (dyrektywa {$O+}), co pozwoli na jak największą optymalizacje kodu. Niekiedy może to spowodować problemy z debugowaniem aplikacji tak więc na czas procesu testowania oraz kodowania, możesz wyłączyć tę dyrektywę ({$O-}) i włączyć ją jedynie w momencie kompilowania finalnej wersji.

Możesz zdecydować się na rozpowszechnianie aplikacji bez dodatkowych pakietów. Wówczas, należy z menu Project wybrać pozycję Options, a nastepnie kliknąć zakładkę Package i zaznaczyć opcję Build with runtime package. Wtedy rozmiar aplikacji będzie mniejszy, program będzie działał szybciej jeżeli masz kilka aplikacji wykorzystujących te same pakiety. Jednak minusem jest to, iż w momencie rozpowszechniania aplikacji, na komputerze docelowym muszą znaleźć się biblioteki wymagane przez aplikację.

Jeżeli wykorzysujesz w aplikacji kilka formularzy, wywołuj je w sposób dynamiczny. Domyślnie wszystkie formularze tworzone są w momencie uruchamiania aplikacji co zajmuje więcej miejsca w pamięci komputera. Możesz to zmienić klikając na menu Project ? Options ? Forms. Na zakładce Forms, należy usunąć formularze z listy Auto-create forms.

Rezygnacja z niektórych modułów Delphi może wpłynąc na mniejszy rozmiar aplikacji wykonywalnej i mniejsze wymagania co do pamięci RAM. Przykładowo, moduł SysUtils zwiększa rozmiar aplikacji wykonywalnej o kilkaset kB. Jeżeli chcesz wykorzystać zaledwie kilka funkcji z tego modułu i masz dostęp do kodów źródłowych biblioteki VCL, skopiuj do programu jedynie funkcje które chcesz wykorzystać (zamiast włączania całego modułu).

Zanim zdecydujesz się na pisanie własnej funkcji upewnij się czy nie została już napisana przez kogoś innego; tj. korzystaj z funkcji WinAPI zamiast pisać własne. Upewnij się iż potrzebna Ci funkcja nie znajduje się w bibliotece VCL.

Jeżeli w zasobach Twojej aplikacji znajduje się wiele obrazków, np. pod postacią bitmap, upewnij się że nie posiadają one zbyt dużych rozmiarów. Zastanów się czy nie powinieneś skompresować takich obrazów do postaci pliku JPEG.

Język Delphi


W większości przypadków spowolnienie działania aplikacji wynika z blędnej implementacji algrorytmu. W takich przypadkach na kod aplikacji trzeba spojrzeć indywidualnie. Poniższe wskazówki można traktować bardziej jako ciekawostkę, lecz z pewnością pozwolą Ci pisać lepsze programy.

W większości przypadków kompilator Delphi optymalizuje kod możliwe jak najbardziej, tak, aby program był wydajny. Jednak najlepszy nawet kompilator nie będzie w stanie "obronić" nas przed ewidentnymi błędami programistów.

Virtual czy Dynamic ?


Wykorzystując przedefiniowane metody masz wybór. Możesz skorzystać z dyrektywy Virtual i uczynić metodę wirtualną lub dynamiczną (dyrektywa Dynamic). Informacje na temat przedefiniowanych metod znajdują się w tablicy VMT, dzięki użyciu klauzuli virtual, wywołanie metody będzie szybsze. Użycie klauzuli dynamic powoduje, iż metody będą wykonywane wolniej, lecz będą również wykorzystywały mniej pamięci.

Instrukcje warunkowe


Częstym niedociągnięciem (nie można nazwać tego błędem) jest sprawdzanie w warunku if, czy zmienna typu Boolean ma wartość True:

if X = True then { kod } 


Taki zapis nie jest konieczny, gdyż domyślnie warunek w tym przypadku sprawdza, czy zmienna nie ma wartości True. Można więc taki kod zastąpić następującym:

if X then { kod }


Zwiększana jest tym samym czytelność kodu.

Spójrzmy natomiast na poniższy kod:

if X = True then
  Y := True
else
  Y := False;


Czytelnikowi taki fragment kodu może wydać się śmieszny, ale niestety wielu początkujących programistów stosuje taki niepotrzebny zapis, który spowalnia wykonywanie programu. Procesor musi bowiem wykonać instrukcję porównującą zawartość danej zmiennej i dopiero później przypisać nową wartość do zmiennej. Taki zapis, choć z punktu widzenia kompilatora jest poprawny, może zostać uproszczony do takiej postaci:

Y := X;


Sytuacja odwrotna:

if X = True then
  Y := False
else
  Y := True;


Taki z kolei zapis można skrócić do jednej linii kodu, używając operatora not:

Y := not X;


W obu przypadkach zmienne muszą być oczywiście typu Boolean.

Wyobraźmy sobie program, w którym użytkownik musi podać hasło dostępu (wpisać je do kontrolki Edit) i nacisnąć przycisk TButton, aby się zalogować. Chcemy jednak, aby przycisk pozostał nieaktywny (właściwość Enabled), dopóki użytkownik nie wpisze w kontrolce choćby jednego znaku. Oto przykładowy kod z użyciem instrukcji if:

var
  Name: String;
  Accept: Boolean;

begin
  Accept := False;
  Writeln('Podaj imię');
  Readln(Name)// odczyt wartości

  if Length(Name) = 0 then // sprawdzenie długości ciągu
    Accept := False
  else
    Accept := True;
end.


Funkcja Length w tym przykładzie sprawdza, czy rzeczywiście długość wprowadzonego tekstu równa się 0. Taki kod można skrócić do takiej postaci:

var
  Name: String;
  Accept: Boolean;

begin
  Accept:= False;
  Writeln('Podaj imię');
  Readln(Name)// odczyt wartości

  Accept := (Length(Name) <> 0);
end.


Taki zapis może wydawać się niezrozumiały. Warto jednak wiedzieć, że Delphi daje takie możliwości. Zmienna AcceptTrue, jeśli długość zmiennej Name będzie różna od 0. W ten sposób osiągamy taki sam rezultat jak w poprzednim przykładzie.

Jeżeli w instrukcji warunkowej znajduje się kilka warunków logicznych, może warto zamienić te warunki miejscami. Na początku umieść warunek, którego prawdopodobieństwo wystąpienia jest większe. W przypadku, gdy posiadasz wiele warunków do sprawdzenia, być może korzystniejsze będzie rozbicie takiej jednej instrukcji warunkowej na kilka mniejszych.

zmieni wartość na

Typ Boolean w tablicach


Według wielu opinii programowanie jest sztuką, umiejętnym tworzeniem kodu. W Delphi co prawda nie mamy tylu możliwości prezentowania ?sztuczek? co w języku C, lecz warto skorzystać z paru ułatwień. Oto przykład kodu nawiązującego do poprzedniego listingu:

program Foo;

{$APPTYPE CONSOLE}

var
  Name: String;
  Accept: Boolean;

begin
  Accept := False;
  Writeln('Podaj imię');
  Readln(Name)// odczyt wartości

  if Length(Name) = 0 then
  begin // sprawdzenie długości ciągu
    Accept := False;
    Writeln('Imię jest za krótkie!');
  end else
  begin
    Accept := True;
    Writeln('Imię ma odpowiednią długość!');
  end;

  Readln;
end.


Oprócz zmiany wartości zmiennej Accept na ekranie konsoli jest wyświetlany tekst informacyjny. Korzystając z tablic, można skrócić ten kod do kilku linijek. Przede wszystkim należy zadeklarować tablicę dwuelementową:

const
  Msg : array[Boolean] of String = ('Imię jest za krótkie!', 'Imię ma odpowiednią długość!');


Konstrukcja tej tablicy jest specyficzna, bo zamiast zakresu podałem typ Boolean #_. Oznacza to, że tablica może mieć dwa elementy, True lub False, tak więc odwołanie się do danego elementu wygląda następująco:

Writeln(Msg[True]);


Łatwo już się domyślić, jak będzie wyglądał zapis naszego przykładu w zmienionej formie:

program Foo;

{$APPTYPE CONSOLE}

var
  Name : String;
  Accept : Boolean;

const
  Msg : array [Boolean] of String = ('Imię jest za krótkie!', 'Imię ma odpowiednią długość!');

begin
  Accept := False;
  Writeln('Podaj imię');
  Readln(Name)// odczyt wartości

  Accept := (Length(Name) <> 0);
  Writeln(Msg[Accept]);

  Readln;
end.


Dzięki temu skróciliśmy zapis kodu, zwiększyliśmy jego czytelność oraz przyspieszyliśmy działanie programu.

Zbiory


Zastosowanie zbiorów często upraszcza zapis związany z instrukcjami warunkowymi. Przykładowo, trzeba sprawdzić, czy wartość zawarta w zmiennej jest liczbą czy ciągiem znakowym. Można także sprawdzić, czy dany ciąg zawiera określone znaki:

  if ((Znak = 'A') or (Znak = 'a') or (Znak = 'B')) then
  { kod }


Jeżeli chcemy porównać wiele znaków, to taki zapis jest mało profesjonalny. Właśnie dzięki zbiorom oraz operatorowi in

można go zastąpić w ten sposób:
  if Znak in ['A', 'a', 'b', 'B'] then { kod }


Niestety, z użyciem zbiorów i operatora in nie można porównywać całych ciągów, lecz tylko pojedyncze znaki. I tak jednak daje to pewne ułatwienie w porównaniu z zastosowaniem operatora and.

Innym prostym sposobem na sprawdzenie, czy podany znak jest alfanumeryczny, jest zastosowanie poniższego kodu:

  if Key in ['a'..'z', 'A'..'Z', '0'..'9'] then
   { kod }


Warto też pamiętać o możliwości stosowania zakresów w przypadku zbiorów! W takim przypadku następuje sprawdzenie, czy zmienna Key należy do przedziału znaków od a do z lub od A do Z, a także, czy zmienna Key zawiera liczbę.

W celu dodania lub usunięcia elementu ze zbioru, unikaj stosowania operatorów + oraz -. O wiele szybsze działanie zapewnią procedury Include oraz Exclude.

Łańcuchy


Łańcuchy w Delphi są niemalże najczęściej wykorzystywanym typem danych. W tym tekście nie będziemy omawiać typu z zerowym ogranicznikiem (PChar), skupimy się raczej na typie String. Łańcuch danych w rzeczywistości jest tablicą znaków. W Delphi nie musimy skupiać się na długości łancucha oraz ograniczeniach z tego wynikających. Długość łańcucha jest ograniczona teoretycznie zasobami komputera, kompilator zajmuje się przydzielaniem oraz zwalnianiem pamięci.

Co najciekawsze typ danych String zmieniał się z upływem czasu, dlatego też warto wiedzieć jak jego użycie w różnych wersjach kompilatora, wpływa na prędkość działania aplikacji. W Delphi 1 typ String wskazywał na krótki łańcuch ograniczony 255 znakami (ShortString). W Delphi dla Win32, typ String jest urożsamiany z typem AnsiString, natomiast w Delphi dla .NET - z typem WideString.

Typ WideString wskazuje na klasę .NET - System.String.

Każda zmienna typu String przy deklarowaniu jest inicjowana przez kompilator jako pusty ciąg (chyba, że nadajemy domyślną wartość zmiennej), nie ma więc konieczności przypisywania w bloku begin pustego ciągu do naszej zmiennej.

Łączenie znaków w ciągach


Może nie jest to zbyt pomocna wskazówka, ale pozwala na zaoszczędzenie miejsca w kodzie źródłowym poprzez usunięcie operatora +. Chodzi o łączenie danych w ciągach znaków, a konkretniej ? kodów ASCII poprzedzanych znakiem #. Poniższy kod:

MessageBox(0, 'To jest ciąg znakowy... ' + #13 + #13 + '...druga linia', '', MB_OK);


Może być równie dobrze zastąpiony następującym:

MessageBox(0, 'To jest ciąg znakowy... '#13#13'...druga linia', '', MB_OK);


Oba zapisy mają takie same działanie ? tworzenie podwójnej linii w oknie informacyjnym.

Przekazywanie parametrów przez wartość


Przypomnijmy, iż parametry funkcji/procedury/metody mogą być przekazywane przez wartość (najczęściej wykorzystywana metoda), stałą oraz referencję. Przekazywanie parametrów przez stałą pozwala kompilatorowi na zapewnienie możliwie jak najlepszej optymalizacji kodu. Spójrzmy na dwie funckje poniżej:

function GetLength(S : String) : Integer;
begin
  Result := Length(S);
end;

function GetLength2(const S : String) : Integer;
begin
  Result := Length(S);
end;


Obie zwracają długość łańcucha, z tą różnicą iż parametr funkcj GetLength jest przekazywany przez wartość natomiast GetLength2 - przez stałą. Szybkość działania tych funkcji w pojedyńczym wywołaniu nie robi większej różnicy. Jeżeli skorzystamy z takiej funkcji, powiedzmy - 1 mln. razy wyraźnie widać różnicę prędkości. Pierwszej z nich, wykonanie tego zadania zajmuje ok. 100 milisekund natomiast drugiej - około 15 milisekund. W przypadku kompilatora .NET szykość działania tych dwóch funkcji jest zbliżona.

Przyjrzyjmy się kolejnemu przykładowi. Technika przekazywania parametrów przez stałą sprawdza się w momencie, gdy nie musimy modyfikować oryginalnej wartości łańcucha. Spójrzmy na poniższą procedurę oraz funkcję:

function Add(const S : String) : String;
begin
  Result := S + ' Ala ma kota ';
end;

procedure Add2(var S : String);
begin
  S := S + ' Ala ma kota ';
end;


Pierwsza z nich (funkcja) zwraca wartość łańcucha, powiększoną o tekst Ala ma kota. Procedura natomiast modyfikuje zawartość parametru (parametr przekazywany jest przez referencję).

50 tyś. wywołań pierwszej funkcji, w postaci:

for I := 0 to 50000 do
  Foo := Add(Foo);


zajmuje aplikacji mnóstwo czasu (w teście 114000 milisekund). Natomiast, tyle samo iteracji procedury Add2 trwa około 15 milisekund. Różnice są jak widać znaczne. Podsumowując: jeżeli musisz w swoim programi wielokrotnie manipulować danymi łańcucha, warto pomyśleć o przekazywaniu parametrów przez wartość.

Łączenie łańcuchów


Bardzo istostne zagadnienie, operacja często wykorzystywana w trakcie programowania, a mianowicie - łączenie łańcuchów. Skupmy się na łańuchach String i sposobów na łączenie łańcuchów. Najprostszy, najbardziej przejrzysty, to operator + (dodawanie), który jednocześnie jest najbardziej zalecanym sposobem łączenia łańcuchów. Innym, mniej zalecanym sposobem jest użycie funkcji Concat lub AppendStr, która jest procedurą przestarzałą i niezalecaną do użycia. 500 tyś. iteracji poniższych pętli dało mniej więcej podobne rezultaty czasowe (różnica kilku milisekund, czyli na granicy błędu):

for I := 0 to 500000 do
  Foo := Concat(Foo, ' Ala ma kota ');

for I := 0 to 500000 do
  Foo := Foo + ' Ala ma kota ';

for I := 0 to 500000 do
  AppendStr(Foo, ' Ala ma kota ');


Sytuacja wygląda inaczej na platformie .NET. Działanie operatora + jest niezwykle wolne (procedura AppendStr jest niedostępna). Łączenie łańcuchów powoduje de facto utworzenie nowej zmiennej, do której zostaną przypisane wartości dwóch dodawanych łańcuchów. W wypadku gdy musimy dokonać wielu operacji na łancuchach, korzysniejsze jest skorzystanie z klasy StringBuilder. Spójrz na poniższy listing:

program Foo;

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils;

var
  Str : String;
  I : Integer;
  Start, Stop : Integer;
begin
  Str := '';
  Start := GetTickCount;

  for I := 0 to 20000 do
    Str := Str + 'Ala ma kota';

  Stop := GetTickCount;
 
  Writeln('Czas wykonywania: ' + IntToStr(Stop - Start));
  Readln;

end.


Program jest dość prosty, napisany tak, aby mógł zostać skompilowany zarówno w Delphi dla .NEt oraz w Delphi dla Win32. Jego zadaniem jest dodawanie w pętli (20,000 iteracji) napisu Ala ma kota. Delphi 7 poradzi sobie z takim programem w 16 milisekund natomiast Delphi dla .NET potrzebuje na to aż 40 milisekund! Aby przyspieszyć działanie aplikacji, możesz skorzystać z klasy StringBuilder, która znajduje się w przestrzeni System.Text. Użycie tej klasy spowoduje przyspieszenie działania aplikacji do 15 milisekund, czyli do wartości porównywalnej z Delphi dla Win32. Warto o tym wiedzieć, w sytuacjach, gdy Twoja aplikacja silnie korzysta z typu String:

program Foo;

{$APPTYPE CONSOLE}

uses
  Windows,
  System.Text,
  SysUtils;

var
  Str : StringBuilder;
  I : Integer;
  Start, Stop : Integer;
begin
  Str := StringBuilder.Create;

  Start := GetTickCount;

  for I := 0 to 20000 do
    Str.Append('Ala ma kota');

  Stop := GetTickCount;
  Writeln('Czas wykonywania: ' + IntToStr(Stop - Start));
  Readln;

  Str.Free;

end.


W klasie StringBuilder korzystamy z metody Append, która jest po prostu szybsza niż operator +. W przypadku pracy z większą ilością danych, zalecam więc użycie klasy StringBuilder. Wadą takiego rozwiązania jest to, iż taki program nie będzie kompatybilny ze starszymi wersjami Delphi (klasa StringBuilder jest charakterystyczna dla .NET).

Zmienne lokalne


Używaj zmiennych globalnych tylko wówczas jeśli jest to naprawdę konieczne! Dobrym zwyczajem jest dzielenie programu na procedury i funkcje, w których wewnątrz deklarowane są zmienne (tzw. zmienne lokalne). Zmienne globalne są umiesczane na stosie w momencie uruchamiania aplikacji - te znajdujące się wewnątrz procedur/funkcji czy też metod - dopiero w momencie wywolywania tejże. W momencie zakończenia działania procedur, są one zdejmowane ze stosu. Poniżej przedstawiono dwie wersje tego samego "algorytmu" (właściwie nie robi on nic konkretnego). W jednej wersji zastosowano zmienne globalne:

var
  Start, Stop : Integer;
  I, J : Integer;

procedure Global;
begin
  Start := GetTickCount;

  for I := 0 to MaxInt do
  begin
    J := J + 1;
  end;
  Stop := GetTickCount;

  Writeln('Czas wykonania kodu: ', Stop - Start);
end;

begin
  Global;
end.


Czas wykonania takiego kodu to 18234 milisekund. Mała modyfikacja takiego programu, tj. użycie zmiennych jako zmiennych lokalnych, skraca czas wykonania aplikacji aż do 4625 milisekund!

Deklarując zmienną kontrolną I, jako zmienną globalną, w trakcie kompilacji Delphi wyświetli komunikat: For loop control variable must be simple local variable

Procedury zagnieżdżone


Procedury lokalne, czyli procedury zagnieżdżone (procedury w procedurach) są wolniejsze niż tradycyjne wywołanie procedur/funkcji. Zaletą procedur zagnieżdżonych jest to, iż zmienne lokalne procedury zewnętrznej są również "widoczne" w procedurze zgnieżdżonej, lecz działanie takiego kodu jest wolniejsze. Przykładowe wkorzystanie procedury zagnieżdżonej:

procedure Bar1;
var
  LoopCounter : Integer;
  Start, Stop : Integer;

  procedure BarInside;
  var
    I, J : Integer;
  begin
    J := 0;

    for I := 0 to LoopCounter do
    begin
      J := J + 1;
    end;
  end;

begin
  LoopCounter := MaxInt;
  BarInside;
end;


Taki kod można rozbić na dwie mniejsze procedury:

procedure Foo(var Value : Integer);
var
  I, J : Integer;
begin
  for I := 0 to Value do
  begin
    J := J + 1;
  end;
end;

procedure Bar2;
var
  LoopCounter : Integer;
  Start, Stop : Integer;
begin
  LoopCounter := MaxInt;
  Foo(LoopCounter);
end;


Liczby całkowite


O wiele lepiej jest stosować typy 32bitowe w miejsce 16bitowych (Word, ShortInt). Procesor na chwile musi przełączyć się w tryb 16bitowy co oczywiście ma wpływ na prędkość działania aplikacji. Typy 8-bitowe nie są specjalnie spowalniające szczególnie gdy nie mieszamy tych typów z typami 32-bitowymi.

Mowiąc o typach 16-bitowych, należy wspomnieć o tym, aby unikać typów o ograniczonym zasięgu - np:

type
  TFoo = 1000..2000;


Ten typ danych zajmuje 16-bitów a jak wspomnieliśmy - taki typ danych jest wolniejszy.

W tym artykule wspomnieliśmy o korzyściaj wynikających z użycia zbiorów. Spójrz na poniższy przykład:

if (X > 0) and (X < 20) then { ... }


Mamy tutaj dwa warunki logiczne do sprawdzenie. O wiele lepszym byłoby zastosowanie w to miejsce zbiorów:

if X in [0..20] then { ... }


Mówiąc o liczbach całkowitych nie sposób wspomnieć o typach danych. Delphi oferuje nam wiele typów danych mogących operować na liczbach całkowitych (rzeczywistych również) dlatego należy rozsądnie dokonywać ich wyboru. Należy zastanowić się jak duży zakres będzie nam potrzebny i czy do danej operacji nie wystarczy użycie typu Integer? Ten typ danych jest najoptymalniejszy we wszelkich operacjach dokonywanych na liczbach całkowitych (mnożenie, dzielenie, dodawanie). Natomiast bardzo wolnym typem jest typ Int64 lub Comp (typ Comp uważany jest za przestarzały, zaleca się używanie typu Int64), lecz zapewnia on największą precyzję oraz zakres z pośród typów obecnych w Delphi.

Jeżeli dokonujesz operacji dodawania lub odejmowania, o wiele lepszym rozwiązaniem będzie zastosowanie procedur Inc oraz Dec w miejsce operatorów + i -.

Zobacz też:


[1] Więcej informacji o tablicach znajdziesz w tekście Tablice


Dynamiczne tworzenie form


Jak przekonał się każdy, kto próbował napisać (bądź napisał) jakąkolwiek większą aplikację, ciężko jest pozostać przy jednej formie. Jeśli już zdecydowaliśmy się na więcej form w naszym programie to aby mieć jakąkolwiek nad nimi kontrolę zmuszeni jesteśmy tworzyć je ręcznie. Większość programistów robi to w miejscu, w którym "potrzebują" danej formy. Np. mamy przycisk "Opcje", którego zadaniem jest wyświetlenie formy z opcjami. Kod wygląda tak:

procedure TForm1.btnOptionsClick(Sender: TObject);
var
  fOpcje: TfOpcje;
begin
  fOpcje := TfOpcje.Create(self);
  //tu ustawiamy formę
  if fOpcje.ShowModal = mrOK then
  begin
    //tu np. zapisujemy zmienione opcje
  end;
  FreeAndNil(fOpcje);
end;

i jest poprawny. Ale teraz przyjmijmy taki scenariusz - Twoja aplikacja się rozrosła, dodałeś do niej MainMenu z pozycją "Opcje" oraz inną formę, na której tez jest przycisk "Opcje".
Jedno rozwiązanie to powielić jeszcze dwa razy powyższą procedurę. Drugie to z pozostałych dwóch wywoływać tą pierwszą.
Oba zadziałają i oba są poprawne ale prędzej czy później staną się kłopotliwe. Wadą pierwszego jest to, że mamy kod w trzech różnych miejscach - jeśli zmienimy w jakiś sposób formę "Opcje" musimy zmieniać kod w trzech miejscach i łatwo coś pominąć. Wadą drugiego jest to, że ten kod może się nam "zgubić" jeśli unit głównej formy się rozrośnie.
Istnieje trzecie rozwiązanie - w module formy, którą chcemy wywołać piszemy prostą funkcję, której ciało jest prawie takie samo jak ciało powyższej funkcji.

unit Unit2;

interface

uses
  ...

type
  TfOptions = class(TForm)
    ...
  private
    { Private declarations }
  public
    { Public declarations }
  end;

function ShowOptions(AOwner: TComponent): TModalResult;

implementation

function ShowOptions(AOwner: TComponent): TModalResult;
var
  fOptions: TfOptions;
begin
  fOptions := TfOptions.Create(AOwner);
  try
    with fOptions do
    begin
      //ustawienie formularza
      Result := ModalResult;
    end;
  finally
    FreeAndNil(fOptions);
  end;
end;


dodatkowo do funkcji tej możemy przekazać parametry. Załóżmy, że forma "Opcje" wyświetla nam takie dane jak
  • Nazwa użytkownika
  • Ścieżkę do pliku
  • Kolor głównej formy


powyższą funkcję możemy trochę zmodyfikować

function ShowOptions(var AName, APath: string; var AColor: TColor; AOwner: TComponent): TModalResult;

implementation

function ShowOptions(var AName, APath: string; var AColor: TColor; AOwner: TComponent): TModalResult;
var
  fOptions: TfOptions;
begin
  fOptions := TfOptions.Create(AOwner);
  try
    with fOptions do
    begin
      edtName.Text := AName;
      edtPath.Text := APath;
      cpColor.Color := AColor;
      Result := ModalResult;
      if Result = mrOK then
      begin
        AName := edtName.Text;
        APath := edtPath.Text;
        AColor := cpColor.Color;
      end;
    end;
  finally
    FreeAndNil(fOptions);
  end;
end;


Teraz w miejscu, z którego "wywołujemy" dodatkową formę piszemy tylko

if ShowOptions(Name, Path, Color, Self) then
  //tu zapisanie opcji, które mamy w zmiennych Name, Path i Color.
  //jeśli użytkownik kliknął anuluj to wartość zmiennych się nie zmieniła
 


Zalety tego rozwiązanie
  • kod tworzący i ustawiający daną formę jest zawsze w tym samym miejscu - pod słowem implementation danej formy
  • tworzenie formy odbywa się w tylko jednym miejscu
  • do funkcji tworzącej formę możemy przekazać (i zwrócić) dowolną ilość parametrów


VCL


Dodawanie wielu elementów do list


Często początkujący programiści chcąc dodać wiele elementów do list TStrings używają takiego kodu:

var
  i: Integer;
begin
  for i := 0 to 10000 do
    ListBox1.Items.Add('Liczba' + IntToStr(i));
end;


Kod ten wykonuje się dość wolno, ponieważ po każdym dodaniu elementu do listy jest on wyświetlany.

Przy dodawaniu elementów do list, czy w ogóle modyfikacji treści jakiegoś komponentu, powinno się korzystać z metod, które służą do wyłączania odświeżania graficznego komponentu.
var
  i: Integer;
begin
  ListBox1.Items.BeginUpdate;
  for i := 0 to 10000 do
    ListBox1.Items.Add('Liczba' + IntToStr(i));
  ListBox1.Items.EndUpdate;
end;



Dzięki temu moc obliczeniowa nie jest marnowana na operacje, które i tak nie dają żadnego efektu.

Pierwszy kod wykonywał się ok. 3000ms, drugi natomiast: 820ms (na komputerze z procesorem 266MHz).

Jak widać, róznica jest znaczna i nie można zapominać o używaniu tych metod z komponentami typu: TMemo, TListBox, TComboBox, TTreeView itp.

 

Źródło: 4programmers.net. Treść udostępniona na zasadach licencji Creative Commons Attribution

 

Autor: Misiekd

Licencja: Creative Commons