Programowanie w języku Delphi » Gotowce

Tworzenie skórek do programu

Dynamiczne tworzenie interfejsu

Ten artykuł napisałem raczej dla mniej zaawansowanych użytkowników Delphi, którzy bez żadnych drogich komponentów pragną wbudować w swoja aplikacje obsługę skórek.

Mój pomysł różni się tym od innych, że nie opiera się na zmianie właściwości jakiegoś elementu tylko sam te elementy tworzy.

Zacznę od przedstawienia formatu pliku, który utworzy poszczególne elementy interfejsu.

KOD:
//Hyper Objects Language version 1.0.0
hol;version=100;Author=Wolverine
 
mainform;begin
region;mask=%skinroot%\mainform.msk;image=%skinroot%\mainform.bmp
label;top=7;left=20;fontname=verdana;b;fontcolor=clWhite;function=caption;fontsize=7
image;image=%skinroot%\mainmenu.bmp;top=430;left=17;function=mainmenu
image;image=%skinroot%\hidebtn.bmp;top=6;left=155;function=hide
 
form2;begin
region;mask=%skinroot%\form2.msk;image=%skinroot%\form2.bmp
label;top=11;left=22;fontname=verdana;b;fontcolor=clWhite;function=caption;fontsize=7
image;image=%skinroot%\ko\hidebtn.bmp;top=10;left=504;function=hide
image;image=%skinroot%\ko\hidebtn.bmp;top=10;left=480;function=minimize
image;image=%skinroot%\ko\send.bmp;top=358;left=470;function=send
label;top=328;left=30;fontsize=7;fontcolor=clgreen;fontname=arial;function=typing


Jak można zauważyć format tworzenia obiektu wygląda tak:
NAZWA OBIEKTU;WŁAŚCIWOŚĆ1=WARTOŚĆ1;WŁAŚCIWOŚĆ2=WARTOŚĆ2;etc

Silnik naszego programu będzie pozwalał tworzyć następujące obiekty:

Region – tworzy nieregularna formę (wykorzystuje do tego komponent TCoolForm
Label – Nazwa mówi sama za siebie, tworzy komponent typu TLabel
Image – Jak w nazwie – tworzy komponent TImage
HOL – Jest niewidocznym elementem, przedstawiającym istotę skórki. Zawiera informacje o autorze, wersji etc

Teraz zajmiemy się napisaniem enginu, który zinterpretuje nasz kod. Zaczne od funkcji, którą każdy z was powinien mieć zawsze pod ręką:

DELPHI:
function TMainForm.ParseCmd(Commands: String; Dot: Char): TStringList;
var
  Cmd: String;
begin
  Result := TStringList.Create; //Wyniki beda trzymane w TStringList
  Cmd := Commands; //Tymczasowy string
  while Pos(Dot, Cmd) <> 0 do begin //Dopuki w komendach znajduje się dzielnik
    Result.Add(Copy(Cmd, 1, Pos(Dot, Cmd) - 1)); //Dodaj do listy kawałek od początku do dzielnika
    Cmd := Copy(Cmd, Pos(Dot, Cmd) + 1, Length(Cmd) - Pos(Dot, Cmd)); //Usuń ze stringa dodany kawałek
  end;
  if Length(Cmd) > 0 then Result.Add(Cmd); //Jeżeli za ostatnim dzielnikiem cos zostało, umieść to na liście
end;


Funkcja jak można zauważyć dzieli stringa na TStringList wg danego znaku. Będzie nam potrzebna jeszcze jedna funkcja, tworząca ścieżkę do katalogu ze skinami:

DELPHI:
function GetPath(Path: String): String;
begin
  Result := Path;
  if Pos('%skinroot%', Path) <> 0 then
    Result := ExtractFilePath(Application.ExeName) + '\skins' + Copy(Path, 11, Length(Path) - 10);
end;


Teraz główna część programu &#8211; kod przetwarzający plik HOL:

DELPHI:
procedure TMainForm.LoadSkin(FileName: String);
var
  Code: TStringList;
  q, w, te: Integer;
  TempString, Temp: String;
  //kontrolki
  Image: TImage;
  CoolForm: TCoolForm;
  Text: TLabel;
begin
  Code := TStringList.Create; //Caly kod 
  MainFormElements := TStringList.Create;
  Form2Script := TStringList.Create; //Skrypt zawierajacy kod interfejsu głównej formy (zmienna globalna)
  MainFormScript := TStringList.Create; //Skrypt zawierajacy kod interfejsu form2 (zmienna globalna)
  Code.LoadFromFile(FileName);
 
  for q := 0 to Code.Count -1 do begin //Ta pentla rozdzieli elementy nalerzące do MainForm i Form2
    if LowerCase(Code[q]) = 'mainform;begin' then te := 1;
    if LowerCase(Code[q]) = 'form2;begin' then te := 2;
    if te = 1 then MainFormScript.Add(Code[q]);
    if te = 2 then Form2Script.Add(Code[q]);
  end;
 
  for q := 0 to MainFormScript.Count -1 do begin
    if Pos('//', MainFormScript[q]) <> 0 then Continue; //Jeśli to komentarz, przejdź do następnej linijki
    if Length(MainFormScript[q]) = 0 then Continue; //Podobnie jeśli linia jest pusta
    MainFormScript[q] := LowerCase(MainFormScript[q]);
 
    //Image
    if ParseCmd(MainFormScript[q], ';')[0] = 'image' then begin
      Image := TImage.Create(MainForm); //Tworzymy komponent
      Image.Parent := MainForm;
      Image.AutoSize := True;
      for w := 1 to ParseCmd(MainFormScript[q], ';').Count -1 do begin //I nadajemy wlaściwości
        Temp := ParseCmd(MainFormScript[q], ';')[w];
        if ParseCmd(Temp, '=')[0] = 'top' then
          Image.Top := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'left' then
          Image.Left := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'image' then
          Image.Picture.LoadFromFile(GetPath(ParseCmd(Temp, '=')[1]));
        if ParseCmd(Temp, '=')[0] = 'function' then
          TempString := ParseCmd(Temp, '=')[1];
      end;
 
      if TempString = 'mainmenu' then //Jesli obiekt ma jakąś funkcje to przypisz odpowiednie zdarzenia
        Image.OnClick := MainMenuClick;
      if TempSTring = 'statusmenu' then
        Image.OnClick := StatusMenuClick;
      if TempSTring = 'hide' then
        Image.OnClick := HideClick;
 
      MainFormElements.AddObject(TempString, Image); //Robimy liste elementów, aby móc później je zniszczyć
      Continue; //Następna linijka
    end;
 
    //Region
    if ParseCmd(MainFormScript[q], ';')[0] = 'region' then begin
      CoolForm := TCoolForm.Create(MainForm);
      CoolForm.Parent := MainForm;
      CoolFOrm.Align := alClient;
      for w := 1 to ParseCmd(MainFormScript[q], ';').Count -1 do begin
        Temp := ParseCmd(MainFormScript[q], ';')[w];
        if ParseCmd(Temp, '=')[0] = 'top' then
          CoolForm.Top := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'left' then
          CoolForm.Left := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'image' then
          CoolForm.Picture.LoadFromFile(GetPath(ParseCmd(Temp, '=')[1]));
        if ParseCmd(Temp, '=')[0] = 'mask' then
          CoolForm.LoadMaskFromFile(GetPath(ParseCmd(Temp, '=')[1]));
      end;
      MainFormElements.AddObject('None', CoolForm);
      Continue;
    end;
 
    //Label
    if ParseCmd(MainFormScript[q], ';')[0] = 'label' then begin
      Text := TLabel.Create(MainForm);
      Text.Parent := MainForm;
      Text.Transparent := True;
      Text.OnMouseDown := FormCaptionMouseDown;
      for w := 1 to ParseCmd(MainFormScript[q], ';').Count -1 do begin
        Temp := ParseCmd(MainFormScript[q], ';')[w];
        if ParseCmd(Temp, '=')[0] = 'top' then
          Text.Top := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'left' then
          Text.Left := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'fontcolor' then
          Text.Font.Color := StringToColor(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'fontname' then
          Text.Font.Name := ParseCmd(Temp, '=')[1];
        if ParseCmd(Temp, '=')[0] = 'fontsize' then
          Text.Font.Size := StrToInt(ParseCmd(Temp, '=')[1]);
        if ParseCmd(Temp, '=')[0] = 'b' then
          Text.Font.Style := Text.Font.Style + [fsBold];
        if ParseCmd(Temp, '=')[0] = 'i' then
          Text.Font.Style := Text.Font.Style + [fsItalic];
        if ParseCmd(Temp, '=')[0] = 'u' then
          Text.Font.Style := Text.Font.Style + [fsUnderline];
        if ParseCmd(Temp, '=')[0] = 'function' then
          TempString := ParseCmd(Temp, '=')[1];
      end;
      MainFormElements.AddObject(TempString, Text); //Tutaj nasz TstringList ma jeszcze większe znaczenie - patrz dalej
      Continue;
    end;
  end;
end;


W Form2 sprawa ma się analogicznie jak w przypadku MainForm, z tym, że kod jest przechowywany w Form2Stript i odciąć należy pętle dzielącą kod na dwie formy

A teraz obiecane przedstawienie znaczenia MainFormElements. DO StringLista dodaliśmy element i jego funkcje. Label z założenia powyższego kodu ma funkcje jedynie informacyjna (nie można na niego klikać), i wlaśnie do tego potrzebujemy nazwe jego funkcji:

DELPHI:
procedure TMainForm.SetCaption(Caption: String);
var
  q: Integer;
begin
  for q := 0 to MainFormElements.Count -1 do
    if MainFormElements[q] = 'caption' then
      (MainFormElements.Objects[q] as TLabel).Caption := Caption;
end;


Procedura szuka Labela, który spełnia funkcje Caption i ustawia w nim odpowiednią właściwość.

Język może również posłużyć innym celom takim jak np. przeglądarka internetowa, więc mam nadzieję, że wykorzystacie go jak tylko się da. Powodzenia w pisaniu! Z powodu braku czasu nie napisałem żadnego źródła, które wystarczyłoby skompilować, ale postaram się to jak najszybciej nadrobić.

19 komentarzy

Terrmit 2008-11-23 17:46

W TMainForm.ParseCmd tworzony jest obiekt TStringList:
  Result := TStringList.Create; //Wyniki beda trzymane w TStringList
a gdzie jest zwalniany?
W programie są odwołania do tej funkcji np:
        if ParseCmd(Temp, '=')[0] = 'fontsize' then
Mnie to pachnie wyciekiem pamięci. Jeśli się mylę to proszę o wytłumaczenie.

InFoL 2006-06-02 19:44

ja to bym na bitmapach zrobił...

TurBodzio 2005-09-28 16:36

Mam pytanie na śniadanie:
Czy tak sworzony obiekt po tym jak się go przykryje innym oknem a potem odkryje to on się pojawia piękny i kolorowy, czy też może jest lekko wyczyszczony w miejscu gdzie był przykryty ?

vegat 2004-07-29 13:15

art jest ok. Może można by jednak troszkę na strumieniach pooperować? tyż można, tylko plików będzie więcej

ORanGE 2004-05-07 14:49

nie przesadzasz troche z... ???!!! :-)

ORanGE 2004-05-07 14:48

nie przesadzasz troche z... ???!!! :-)

Wolverine 2004-04-16 15:53

Jak tera patrze na ten kod, to wydaje mi sie beznadzijny :), lepiej korzystac z THolFile, w arcie go nie ma, bo powstal po jego napisaniu. Zapraszam do downloadu ;)

Snowak 2004-04-15 20:22

Heh... Duzo, gratuluje arta, bardzo fajny, chociaz trochę nierozumiem(z kodu)... LOL

kujawiak 2004-04-15 19:01

GOOD!! PRZYDA SIĘ!!

Wolverine 2004-01-22 21:17

[quote]Na C to daj to bedzie ok.[/quote] dal bym, ale na c sie kompletnie nie znam :/

vixen03 2004-01-05 09:33

teraz to samo w C++

Wolverine 2004-01-03 15:32

CoolForm.LoadMaskFromFile(GetPath(ParseCmd(Temp, '=')[1])); - wczytuje maske z pliku, ale thx za sposob, ktory sam tworzy maske

Waldi__17 2004-01-03 15:30

Żeby forma miałą nieregularny kształt trzeba wrzucić taką linijkę:
ExtGenerateMask(CoolForm.Picture.Bitmap, clWhite, filename);
gdzie <filename> jest nazwą pliku do którego zapisywane są jakieś tam ustawienia :>; trzeba jeszcze dodać moduł w sekcju uses <ExtMaskGenerator>

przynajmniej u mnie tak działa... bez tej linijki wychodzi zawsze zwykły prostokąt :]

brodny 2004-01-03 11:57

Podoba mi się (chociaż jak Adam całego nie przeczytałem). Widać, że jest jeszcze ktoś w tym serwisie, kogo interesuje rozwój działu Delphi (od doś dawna zresztą nieaktualizowanego, również przeze mnie :-P )

Wolverine 2004-01-02 17:50

planowalem zrobic alternatywe dla xml'a, nie bynajmniej zeby wypuscic nowy/lepszy jezyk (bo to niemozliwe) ale zeby zaimponowac samemu sobie :d

hmm ponad 70 odslon w ciagu 6 godzin, to duzo czy malo? :d

Adam Boduch 2004-01-02 17:39

Nie czytałem całego ani nie przyglądałem się kodu (Sorki ;)) ale całkiem niezle to wyglada. Aha... jako formatu skorek i ogolnie - danych - uzywa sie teraz XML :)

Wolverine 2004-01-02 16:54

Jest to moj pierwszy artykul i prosil bym o ocene lub komentarz :P

migajek 2004-01-14 12:16

Na C to daj to bedzie ok.