Tworzenie skórek do programu

Wolverine

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

</td></tr></table>

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 &#8211; tworzy nieregularna formę (wykorzystuje do tego komponent TCoolForm
Label &#8211; Nazwa mówi sama za siebie, tworzy komponent typu TLabel
Image &#8211; Jak w nazwie &#8211; tworzy komponent TImage
HOL &#8211; 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ą:

<table border="0px" bgcolor="#EEEEEE"><tr><td bgcolor="#DDDDDD"><b>DELPHI:</b></td></tr><tr>
<td>
```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;
</tr></table>

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:</td></tr> ```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; ``` </td></tr></table>

Teraz główna część programu – kod przetwarzający plik HOL:

DELPHI:</td></tr> ```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;

</td></tr></table>

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:

<table border="0px" bgcolor="#EEEEEE"><tr><td bgcolor="#DDDDDD"><b>DELPHI:</b></td></tr><tr>
<td>
```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;
</td></tr></table>

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

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.

ja to bym na bitmapach zrobił...

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 ?

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

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

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

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 ;)

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

[url]http://4programmers.net/view_file.php?id=1506[/url] - klasa ulatwiajaca uzywanie jezyka HOL

GOOD!! PRZYDA SIĘ!!

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

teraz to samo w C++

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

Ż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 :]

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 )

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

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 :)

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

Na C to daj to bedzie ok.