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

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

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

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

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

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.