Witam wszystkich.
O czym będzie ten artykuł? Myślę, że tytuł przemawia sam za siebie
jednak sprecyzuję. Zajmiemy się hintami czyli dymkami podpowiedzi a
konkretniej ich "ulepszaniem". Ostatnio byłem na Torry'm i zaciekawił
mnie jeden z komponentów - HTMLHint - jest to komponent, który
umożliwia m.in. stosowanie w hintach różnych kolorów czcionki, stylów,
wstawianie obrazków itp. Wszystko dobrze, tylko nie darmowe...
O hintach pisał już Adam Boduch, więc nie będziemy się zajmowali samymi
hintami jako takimi, lecz właśnie tymi właściwościami które wcześniej
wymieniłem a więc kolorowaniem składni i stylami czcionki czyli
pogrubianiem, kursywą, podkreśleniem i przekreśleniem.
Na samym początku należałoby się zastanowić nad tym w jaki sposób
chcemy wykonać nasze zadanie. Oczywiście musimy stworzyć klasę pochodną
od THintWindow i zająć się własnym "malowaniem" płótna hinta. Jednak
wcześniej musimy zastanowić się nad tym w jaki sposób będziemy chcieli
by kolory czcionki zmieniały się zgodnie z tym jak chcemy. Powiedzmy,
że chcesz napisać "To jest przykładowy hint" przy czym "To" ma być
czerwone "jest" fioletowe, "przykładowy" żółte i wreszcie "hint"
zielone. Można by kazać programowi kodować poszczególne wyrazy właśnie
tymi kolorami ale jest to bezsensowne, gdyż na przykład hint dotyczący
innego komponentu chcemy kolorować w inny sposób. Musimy więc
zastosować "kodowanie" hinta, które później w czasie wyświetlania
będzie "rozkodowywane".
Dla naszych rozważań przyjmiemy następujące kodowanie:
<0> - kolor zerowy
<1> - kolor pierwszy
...
<9> - kolor dziewiąty
<b> - włączenie / wyłączenie pogrubienia
<u> - włączenie / wyłączenie podkreślenia
<i> - włączenie / wyłączenie kursywy
<s> - włączenie / wyłączenie przekreślenia
Jak już pewnie zdążyliście się domyślić następujące kodowanie będzie
się wpisywało we właściwość HINT danego komponentu w postaci np.
"<1>To <2>jest <3>przykładowy
<5><b><u>hint". W porządku teraz zajmijmy się
tworzeniem naszego "dymka".
Zajmijmy się najpierw rysowaniem czyli procedure Paint ponieważ to
przede wszystkim tutaj rozgrywa się cały główny problem. Zadeklarujemy
sobie zmienną licznik typu integer, która będzie odpowiedzialna za
ustalenie pozycji znaku, który obecnie analizujemy a także zmienną
pozycja typu TPoint, która będzie odpowiadała za pozycję pisanego
znaku na canvas hint'a.
Najpierw napiszę kod a później go omówię:
procedure TCodeHint. Paint; var pozycja: TPoint; licznik: integer; begin licznik:= 0; with Canvas do begin Font. Color := clBlack; // domyślny kolor jeżeli nie wpisano kodu Font. Style:= []; // domyślny styl jeżeli nie wpisano kodu repeat // powtarzanie pętli tak długo aż skończą się znaki w hint licznik := licznik+ 1; if Caption [licznik ] <> '<' then begin if (Caption [licznik ]= Chr(13)) and (Caption [licznik+ 1]= Chr(10)) then // następna linijka hinta begin pozycja. y := pozycja. y+TextHeight ('X'); pozycja. x := ClientRect. Left+ 2; // 2 - margines licznik := licznik+ 1; end else begin TextOut (pozycja. x, pozycja. y, Caption [licznik ]); pozycja. x:=pozycja. x+TextWidth (Caption [licznik ]); // ustal nowa pozycje end; end else // pojawił się znacznik begin if licznik+ 2<= Length(Caption ) then begin case Caption [licznik+ 1] of // ustawienie stylu zależnie od znacznika 'b': if Font. Style=Font. Style+ [fsBold ] then Font. Style:=Font. Style- [fsBold ] else Font. Style:=Font. Style+ [fsBold ]; 'i': if Font. Style=Font. Style+ [fsItalic ] then Font. Style:=Font. Style- [fsItalic ] else Font. Style:=Font. Style+ [fsItalic ]; 'u': if Font. Style=Font. Style+ [fsUnderline ] then Font. Style:=Font. Style- [fsUnderline ] else Font. Style:=Font. Style+ [fsUnderline ]; 's': if Font. Style=Font. Style+ [fsStrikeOut ] then Font. Style:=Font. Style- [fsStrikeOut ] else Font. Style:=Font. Style+ [fsStrikeOut ]; '0': Font. Color:=clBlack; '1': Font. Color:=clRed; '2': Font. Color:=clBlue; '3': Font. Color:=clGreen; '4': Font. Color:=clAqua; '5': Font. Color:=clFuchsia; '6': Font. Color:=clLime; '7': Font. Color:=clPurple; '8': Font. Color:=clMaroon; '9': Font. Color:=clWhite; end; licznik:=licznik+ 2; end; end; until licznik>= Length(Caption ); end; end;
Wpierw ustalamy domyślny kolor czcionki i jej styl - gdybyśmy tego nie
zrobili to hint przyjmowałby taki kolor jaki był używany przy ostatnim
wyświetleniu. Możesz to sprawdzić kładąc np. dwa Buttony i w hint
jednego wpisać hint zakodowany a w drugim bez kodowania. Między pętlą
repeat a until wykonuje się kod tak długo aż zostaną przeanalizowane
wszystkie znaki. Przy każdym przejściu przez pętlę zostaje zwiększony
licznik - pozycja znaku. Jeżeli napotkamy na znak "<" analizujemy
czy pojawił się kod czy nie. Rozwiązujemy to sprawdzając jaki jest
następny znak. Jeżeli jest to jeden z uwzględnionych przez nas znaków
wówczas dokonujemy zmian w stylu czcionki lub w jej kolorze. Następnie
przeskakujemy o dwa znaki (pomijamy znak ">"). Jeżeli jednak to nie
jest znak z naszego kodowania lub nie trafiliśmy na znak "<" wówczas
rysujemy go na płótnie hint'a. Tłumaczenia wymaga ta linia:
pozycja.x := pozycja.x + TextWidth(Caption[licznik]);
Jest ona po to abyśmy wiedzieli gdzie mamy rozpocząć rysowanie kolejnego znaku. Analogicznie jest z:
if (Caption [licznik ]= Chr(13)) and (Caption [licznik+ 1]= Chr(10)) then // następna linijka hinta begin pozycja. y := pozycja. y+TextHeight ('X'); ...
Sprawdzamy czy znaki nie tworzą znaku następnej linii (jeżeli tego nie
uwzględnimy to hint będzie wprawdzie wyświetlał dwie i więcej linijek
ale tekst pojawi się tylko w pierwszej linijce).
Myślę, że powinno to być zrozumiałe. Teraz powinniśmy się zająć
procedurą ActivateHint. Jednak proponuję ci dać na razie tę procedurę
jako komentarz w np. w znaki {} lub //
(czyli
)
W FormCreate uaktywnij nasz stworzony hint:
HintWindowClass := TCodeHint; Application. ShowHint := false; Application. ShowHint := true;
Daj na formę jakiś Button i wpisz mu we właściwość hint: "<b>Tego
hinta niestety nie widać poprawnie" i nie zapomnij ustawić mu ShowHint
na True. Uruchom program. Zauważyłeś coś? Nasz hint wyświetla się ale
tekst nie jest cały. Wyłącz program i wpisz we właściwość hint Buttona:
"<1>To <2>jest <3>przykładowy
<5><b><u>hint".
Uruchom. Super, koloruje składnię tak jak chcieliśmy. Teraz widzimy
cały tekst ale niestety pojawia się duży margines z prawej strony.
Dlaczego? Ponieważ Windows dobrał wielkość hinta do długości wpisanego
tekstu. Zapytasz czemu więc raz jest za mały a drugim razem za długi.
Już tłumaczę - za pierwszym razem pogrubialiśmy tekst a za drugim
kolorowaliśmy (no i na końcu też pogrubiliśmy i podkreśliliśmy). Jak
więc pewnie się domyśliłeś zależy to od rodzaju czcionki a konkretniej
jej stylu. I o to chodzi. Czcionki pogrubione, kursywa mają inną
szerokość niż ta sama czcionka bez pogrubienia. To jeżeli chodzi o za
mały rozmiar hinta. Zaś dodatkowy margines brał się z tego, że
wpisywaliśmy dodatkowe znaki, które nie były przecież później rysowane.
Musimy więc jakoś rozwiązać ten problem - musimy znać rozmiar potrzebny
i taki wyświetlić. Stwórzmy więc funkcję, która nam to określi i
umieśćmy ją w sekcji
private: function MaxHW (Tekst: string): TPoint; function TCodeHint. MaxHW(Tekst: string): TPoint; var pozycja: integer; licznik: integer; Max: TPoint; Plotno: TBitmap; begin // ustalamy maksymalna szerokość i wysokość tekstu w hint Plotno:=TBitmap. Create; try Plotno. Width:= 10; // rozmiary nie są istotne Plotno. Height:= 10; Plotno. Canvas. Font. Assign(Form1. Font); // przypisanie czcionki pozycja:= 2; licznik:= 0; Max. x:= 0; // wyzerowaie pozycji x Max. y:= 0; // wyzerowanie pozycji y with Plotno. Canvas do // symulacja rysowania begin Font. Color:=clBlack; Font. Style:= []; Max. y:= 2+TextHeight ('X'); // wstępne ustalenie wysokości repeat licznik:=licznik+ 1; if Tekst [licznik ] <> '<' then begin if (Tekst [licznik ]= Chr(13)) and (Tekst [licznik+ 1]= Chr(10)) then begin if Max. x<pozycja then Max. x:=pozycja; Max. y:= Max. y+TextHeight ('X'); pozycja:= 2; // wyzerowanie pozycji x licznik:=licznik+ 1; end else begin pozycja:=pozycja+TextWidth (Tekst [licznik ]); // ustal nowa pozycje end; end else // pojawił się znacznik begin if licznik+ 2<= Length(Tekst ) then begin case Tekst [licznik+ 1] of // ustawienie stylu zależnie od znacznika 'b': if Font. Style=Font. Style+ [fsBold ] then Font. Style:=Font. Style- [fsBold ] else Font. Style:=Font. Style+ [fsBold ]; 'i': if Font. Style=Font. Style+ [fsItalic ] then Font. Style:=Font. Style- [fsItalic ] else Font. Style:=Font. Style+ [fsItalic ]; 'u': if Font. Style=Font. Style+ [fsUnderline ] then Font. Style:=Font. Style- [fsUnderline ] else Font. Style:=Font. Style+ [fsUnderline ]; 's': if Font. Style=Font. Style+ [fsStrikeOut ] then Font. Style:=Font. Style- [fsStrikeOut ] else Font. Style:=Font. Style+ [fsStrikeOut ]; // '0' .. '9' są nieistotne z pkt widzenia szerokości end; licznik:=licznik+ 2; end; end; if Max. x<pozycja then Max. x:=pozycja; until licznik>= Length(Tekst ); end; finally Plotno. Free; // zwolnienie bitmapy end; Result:= Max; end;
Myślę, że powyższy kod powinien być wystarczająco czytelny -
przeprowadzamy symulację jakbyśmy rysowali na hincie, tyle, że bez
pokazywania efektu - w wyniku otrzymujemy wymiary nam potrzebne
Przystąpmy więc do oprogramowania ActivateHint:
procedure TCodeHint. ActivateHint (ARect: TRect; const AHint: string); var Rozmiar: TPoint; PozycjaHinta: TPoint; begin // ustalamy rozmiar i właściwości hinta Canvas. Font. Assign(Form1. Font); Caption := AHint; Rozmiar := MaxHW (AHint ); ARect. Right := ARect. Left + Rozmiar. x+ 4; // 4 - margines lewi i prawy ARect. Bottom:= ARect. Top + Rozmiar. y+ 4; // 4 - margines górny i dolny BoundsRect := ARect; PozycjaHinta := ClientToScreen (Point(0, 0)); SetWindowPos(Handle, HWND_TOPMOST, PozycjaHinta. X, PozycjaHinta. Y, 0, 0, SWP_SHOWWINDOW or SWP_NOACTIVATE or SWP_NOSIZE ); // wyświetlamy hintaend;
Nie ma tu wiele do tłumaczenia - ustalamy rozmiar ARect na podstawie
funkcji która nam wyliczyła potrzebne wymiary i zwiększamy te rozmiary
o 4 (po 2 piksele z każdej strony)
Następnie wyświetlamy hinta.
Tak wygląda cały kod:
(***********************************************) (* *) (*TCodeHint *) (* Copyright (c) 2003 by Rafał J. Łabudek <21.04.2003> *) (* E - mail: mrrafi@interia.pl *) (* *) (***********************************************)unit ColorHint; interfaceuses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1= class(TForm ) procedure FormCreate (Sender:
|