CSS-DIV-Slicer
CSS-DIV-Slicer-Tutorial von Daniel Schwamm (13.03.2010 - 20.03.2010)
Inhalt
Web-Designer sind keineswegs frei von (unsinnigen) Modeströmungen.
Z.B. heisst es seit einiger Zeit, gestylte DIVs seien besser
geeignet als HTML-TABLEs, um auf Webseiten Strukturen abzubilden.
Warum das so sein soll, hat mir bisher allerdings keiner überzeugend darlegen
können.
Tatsache ist, TABLEs sind logisch, einfach und sehr individuell gestaltbar.
Eine halbe Stunde intensiv gelernt, dann hat man die Dinger drauf.
DIVs wirken auf den ersten Blick ebenfalls simple, aber nur so lange sie
alleine auftreten. Kombiniert/verschachtelt man mehrere
DIVs, dann passieren unerwartete Dinge. Insbesondere wenn auf so merkwürdige
Style-Eigenschaften wie "float" zurückgegriffen wird, um Hintergrundfarben
innerhalb der DIVs "aufzufüllen". Richtig spassig wird es aber erst, wenn
man sich dann das Ergebnis noch in verschiedenen Browsern verschiedener
Versionen anschaut ...
Aber okay, gestylte DIVs sind nun einmal hip, also beschäftigen wir uns etwas
mit ihnen.
Ziel sei es, einen klassischen, dreispaltigen Webseiten-Aufbau über DIVs
zu realisieren (statt per TABLE). Es gibt einen Kopf-, einen Rumpf- und
einen Fuss-Teil. Oben befinden sich Logo und Titel, in der Mittel platzieren
wir die Navigation und den Inhalt, und im Fuss lungern noch ein paar
Zusatzinformationen herum. That's it!
Klassischer Web-Aufbau: Drei Zeilen, drei Spalten - mehr braucht es
für gewöhnlich nicht, um den Grundaufbau einer Webseiten abzudecken.
Was mit TABLEs kein Problem ist, ist mit DIVs schwer zu realisieren.
Und dies besonders, wenn der Content-Teil in der Höhe variieren kann.
Höhe und Breite der Seite dynamisch an die Bildschirmauflösung anzupassen
ist für kundige Webseiten-Bauer mithilfe von TABLEs kein Problem.
Bei ausschliesslicher Verwendung von DIVs allerdings schon. Daran habe ich
mir mehrfach die Zähne ausgebissen. Habe den Plan totaler Dynamik daher
gänzlich aufgegeben. Macht ja keinen Sinn, sich ständig eine blutige Nase zu
holen. Wir beschränken uns also auf reine Höhen-Dynamik, d.h. ganz schlicht,
die Gesamthöhe der Page orientiert sich am Inhalt, die Breite bleibt fix. Und
das ist mit verschachtelten DIVs kompliziert genug.
Sehen wir uns zunächst einen HTML-Source an, der das oben beschriebene klassisches
Drei-Spalten-Design mithilfe von DIVs wiedergibt.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'
'http://www.w3.org/TR/html4/loose.dtd'>
<html>
<head>
<link rel=stylesheet type=text/css href='css_div_slicer.css'>
</head>
<body>
<div id='slice_div'>
<div id='slice_0_div'>
<div id='slice_0_l_div'></div>
<div id='slice_0_m_div'></div>
<div id='slice_0_r_div'></div>
</div>
<div id='slice_1_div'>
<div id='slice_1_l_div'></div>
<div id='slice_1_m_div'>
Content_1<br>
Content_2<br>
[..]
Content_15<br>
</div>
<div id='slice_1_r_div'></div>
</div>
<div id='slice_2_div'>
<div id='slice_2_l_div'></div>
<div id='slice_2_m_div'></div>
<div id='slice_2_r_div'></div>
</div>
</div>
</body>
</html>
Das Ganze ist recht übersichtlich und bedarf wohl nur wenig Erläuterung.
Ein DOC-Type wird angegeben, um die HTML-Datei von allen Browsern gleich
behandeln zu lassen.
Per Link-Tag wird die CSS-Datei "css_div_slicer.css" includiert (siehe
nächstes Kapitel).
Die Division "slice_div" enthält die gesamte "Tabelle"; sie umschliesst
alle weiteren DIVs. Kopf, Rumpf und Fuss bekommen jeweils eine eigene
"Zeilen"-DIV spendiert, die ihrerseits die drei gewünschten "Spalten"-DIVs
einschliesst.
"Leben" kommt in die Sache allerdings erst mit den "Definitionen" im
CSS-File. Die sind deutlich schwerer nachzuvollziehen. Wovon man sich
im nächsten Kapitel überzeugen kann ...
Wie wir eben gesehen haben, includiert die HTML-Datei das CSS-File
"css_div_slicer.css". Hier drin wird definiert, wie die einzelnen
DIVs aufgebaut sein sollen. Das allerdings geschieht in bisweilen
nicht ganz logisch nachzuvollziehender Weise. So empfinde ich
zumindest. Ich bitte daher um respektvolle Würdigung der folgenden
paar Source-Zeilen, denn die haben mich viel, viel Zeit gekostet, bis
sie ihre .... mh, "universell-einsetzbare Grundgerüst-Natur" erreicht
hatten. Doch seht selbst:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
#slice_div
{
position:relative;
left:0px;
top:0px;
width:900px;
margin:0px auto;
}
#slice_0_div
{
position:relative;
left:0px;top:0px;width:100%;height:133px;
margin: 0px auto;
}
#slice_0_l_div
{
position:relative;
left:0px;top:0px;width:243px;height:100%;
background-color:#FFFF80;
}
#slice_0_m_div
{
position:relative;
left:243px;top:-133px;width:582px;height:100%;
background-color:#FFFF00;
}
#slice_0_r_div
{
position:relative;
left:825px;top:-266px;width:75px;height:100%;
background-color:#FF8040;
}
#slice_1_div
{
position:relative;
top:0px;width:100%;
float:left;
}
#slice_1_l_div
{
position:absolute;
left:0px;top:0px;width:243px;height:100%;
background-color:#FF8040;
}
#slice_1_m_div
{
position:relative;
left:243px;top:0px;width:582px;
float:left;
background-color:#FFFF80;
}
#slice_1_r_div
{
position:absolute;
left:825px;top:0px;width:75px;height:100%;
background-color:#FFFF00;
}
#slice_2_div
{
clear:left;
position:relative;
top:0px;left:0px;width:100%;height:89px;
margin:0px auto;
}
#slice_2_l_div
{
position:absolute;
left:0px;top:0px;width:243px;height:100%;
background-color:#FFFF80;
}
#slice_2_m_div
{
position:relative;
left:243px;top:0px;width:582px;height:100%;
float:left;
background-color:#FFFF00;
}
#slice_2_r_div
{
position:absolute;
left:825px;top:0px;width:75px;height:100%;
background-color:#FF8040;
}
Okay, so viel vorweg: Die Page ist auf eine Breite von 900 Pixel "berechnet".
Der Kopf hat eine Höhe von 133 Pixel, der Fuss hat eine Höhe von 89 Pixel.
Das ist einfach.
Die Rumpf passt sich hingegen dynamisch an die Höhe des Inhalts an.
Es gilt also: Je mehr Inhalt, desto höher wird die Gesamttabelle. Bei
der im HTML-File vorgegebenen Anzahl von 15 Zeilen Inhalt schaut die Page
in einem Webbrowser folgendermassen aus:
Quietsche-bunt, was?
Tja, auch wenn es völlig gewöhnlich wirkt: Der Clou der Seite ist,
dass die Hintergrundfarben der einzelnen DIVs die Farbe der Webseite (weiss)
komplett abdecken (ausser an den Browser-Rändern). Und das gilt auch dann noch,
wenn sich die Anzahl der Inhalts-Zeilen erhöht bzw. erniedrigt! "Normale"
DIVs machen das nämlich nicht; die füllen sich nur so weit auf, wie sie Platz
für ihren eigenen Content benötigen.
Kopf und Fuss sind hier relativ uninteressant durch ihre statische Höhenangabe.
Deren Spalten-DIVs bekommen eine Background-Color zugewiesen und die
Höhe von "100%" verpasst. Und damit hat es sich auch schon.
Beim Rumpf haben wir dagegen keine fixe Höhe. Beziehungsweise, die Höhe ergibt
sich erst durch den Platz, den der Inhalt in der DIV "slice_1_m_div" benötigt.
Ergo kann für die mittlere Zeilen-DIV "slice_1_div" keine konkrete Höhe
angegeben werden.
Die Definition der Inhalts-DIV "slice_1_m_div" ist nur einigermassen
nachvollziehbar. Schauen wir sie uns noch einmal gesondert an:
00001
00002
00003
00004
00005
00006
00007
#slice_1_m_div
{
position:relative;
left:243px;top:0px;width:582px;
float:left;
background-color:#FFFF80;
}
Das Ding wird relativ positioniert. "Relativ" meint hier, dass der
"Grafik-Pinsel" des Browsers aktuell im oberen linken Eck von "slice_1_div"
hängt (wobei mir schon hier nicht ganz klar ist, warum das so ist). Nun
wird um "left" Pixel (243px) nach links gewandert und ein Rechteck mit einer
Breite von "width" (582px) begonnen. Gleichzeitig ist die DIV aber "links-floatig",
was eigentlich bedeutet, dass sie sich am linken Rand von "slice_1_div"
anschmiegen sollte. Offenbar wiegt hier aber die "left"-Eigenschaft schwerer.
Weglassen kann man die "float"-Eigenschaft aber dennoch nicht, denn sonst
kommt bei einigen Browsern nur Chaos heraus. Warum auch immer. Man beachte
übrigens, dass auch hier keinerlei Angaben zur Höhe der DIV gemacht werden.
So richtig konfus wird es, wenn man sich nun die Definition der linken
Rumpf-Spalte "slice_1_l_div" anschaut, die später z.B. die Navigation
enthalten könnte:
00001
00002
00003
00004
00005
00006
#slice_1_l_div
{
position:absolute;
left:0px;top:0px;width:243px;height:100%;
background-color:#FF8040;
}
Diese DIV wird augenscheinlich "absolute" positioniert. I.d.R.
bedeutet eine absolute Positionierung einer DIV, dass sie die
umschliessende DIV quasi ignorieren und frei auf der Webseite
bewegt werden kann.
Das ist nun aber etwas, was wir hier nicht anstreben. Eher im
Gegenteil. Denn vielmehr sollte diese DIV am linken Rand der
Zeilen-DIV "slice_1_div" kleben. Und dabei möglichst genauso hoch
sein, wie die mit Inhalt gefüllte DIV "slice_1_m_div".
Als logisch denkender Programmierer sieht das Szenario momentan so aus:
Okay, die dreckige DIV "slice_1_l_div" ist absolut positioniert,
ergo verlässt sie die umschliessende DIV "slice_1_div", ergo bedeutet
die Höhenangabe von "100%", dass sie sich auf Screen-Höhe streckt,
wobei die zusätzlichen Angaben "left:0px; top:0px;" zur Folge hat,
dass das Teil in der linken oberen Ecke des Browsers klebt.
So ist es aber offenbar nicht. Nein, die DIV bleibt tatsächlich innerhalb
von "slice_1_div", sie klebt an deren linken, oberen Ecke, und sie nimmt
deren Höhe zu 100% ein. Das verstehe, wer will, ich tue es nicht. Gut
also, dass ich kein Webbrowser bin.
Verwirrende Styles: So würde die Navigations-Division platziert sein,
wenn der olle Schwamm ein Webbrowser wäre - absolut positioniert auf dem
Ursprung 0/0 und mit 100% Screen-Height.
In den letzten beiden Kapiteln haben wir den HTML- und CSS-Code analysiert,
der uns ein höhendynamisches, dreispaltiges Webdesign auswirft und dabei
ohne TABLEs operiert. Die Breite dieser "Pseudo-Tabelle" ist dabei mit
900 Pixel fix vorgegeben worden.
Leider genügt es nicht, die Breitenangabe in DIV "slice_div" abzuändern,
z.B. in "width: 600px", um eine schmalere Variante der Seite zu erhalten.
Damit wieder alles passt, müssten nämlich die Breiten-Werte der inneren
Spalten-DIVs neu berechnet werden. Es ist ja kein Zufall, dass die Summe
der drei "width"-Angaben, etwa der Kopf-Spalten-DIVs, "243px", "582px"
und "75px" exakt "900px" ergeben.
Hier wäre es hilfreich, wenn man in CSS Variablen vergeben könnte.
Zum Beispiel die Tabellenbreite per ...
... auf 600 Pixel setzt. Und dann anschliessend die "width"-Eigenschaft der
DIV "slice_0_l_div" über ...
00001
width:(tab_width*20/100)px;
... auf 20% von "tab_width" kalkuliert (also auf 120 Pixel). Das ist in CSS-Files
aber nicht möglich; die kennen weder Variablen noch Mathematik.
CSS lassen einen bei der Breiten-Dynamik im Stich. Noch kläglicher fällt
die CSS-Hilfe aus, wenn man auf die gleiche kranke Idee wie mein Chef kommt,
den verschiedenen "Slices" - das sind die Zellen der Pseudo-DIV-Tabelle -
genau zugeschnittene Hintergrund-Grafiken mit auf den Weg zu geben. Bei deren
Berechnung und Positionierung muss dann nämlich alles "von Hand" erledigt
werden.
Mein Chef ist auch Programmierer. Zumindest behauptet er das
gerne von sich. Und seine eigene Homepage hat er auch gar nicht
einmal so übel hinbekommen:
http://www.uwe-melzer.de:
Gar nicht einmal so üble Homepage von Uwe Melzer mit dynamisch an die Höhe des Inhalts
angepasster Hintergrundgrafik.
Technisch interessant ist hier, dass die komplette Seite von einer
Grafik umrandet wird, die sich an die inneren Gegebenheiten, sprich,
dem Content, anpasst. Realisiert hat er das mit schnöden TABLEs,
denen er Hintergrund-Grafiken verpasst hat, die er zuvor mit Photoshop
aus einer Basis-Grafik geschnitten ("gesliced") hat.
Das funktioniert zwar, ist aber mühselig. Und wehe, man kommt hinterher
auf die Idee, das Design umzuwerfen und die Page z.B. auf 1024 Pixel statt
800 Pixel Breite zu optimieren. Dann stimmt nichts mehr und Photoshop
muss erneut angeworfen werden.
Da wir gerade gelernt haben, wie man höhendynamische DIV-Pages aufbaut,
können wir nun doch einen Schritt weitergehen, und auch gleich die
schräge Slices-Geschichte automatisieren. Klar, das braucht eigentlich
kein Mensch. Aber halt mein Boss. Und da ich scharf auf Weihnachtsgeld
bin, basteln wir uns halt einen ... mh ... "CSS-DIV-Slicer" zusammen,
der den Job für uns übernimmt.
Okay, im vorherigen Kapitel wurde erwähnt, dass mein Chef Webseiten mit
grössenadaptiven Hintergrundgrafiken mag. Höhen-adaptive DIV-Tabellen
haben wir schon kennengelernt. Die halbe Miete haben wir also.
Nein, halt, eigentlich doch nur ein Viertel. Denn die passenden
Hintergrund-Grafiken müssen ja auch irgendwie erstellt werden.
Wie aber bereits erwähnt, können CSS nicht rechnen. Dynamische Breite
ist damit kaum zu realisieren. Und Grafiken zurechtschnippeln geht damit
schon einmal gar nicht.
PHP dagegen könnte beides. Es liesse sich durchaus ein PHP-Script
schreiben, welches seinerseits ein CSS-File generiert, welches in der
Webseite die Grafiken an die richtige Stelle setzt und dabei die vorgegebene
Gesamtbreite einhält.
So ein PHP-Script war mir aber zu mühsam, zumal mir die PHP-eigenen
Grafikfähigkeiten (noch) nicht so vertraut sind.
Und was macht ein versierter Delphi-Programmierer in solch einem Fall?
Der folgende, mit Delphi 7 zusammengeschusterte "CSS-DIV-Slicer" vermag:
- Einteilung einer beliebigen Grafik in neun "Slices"
- Ausstattung der "Slices" mit Hintergrundfarben
- Modifizierung der Hintergrund-Grafik eines "Slice" über die CSS-Repeat-Eigenschaft
- Generierung von an die Content-Höhe angepassten HTML- und CSS-Code
CSS-DIV-Slicer Profile: Im Register "Profil" können alle neun "Slices"
und die Attribute der zugehörigen "Slice-Cuts" eingestellt werden. Die Grafik
wird möglichst so wiedergegeben, wie sie später auch im Webbrowser erscheint.
CSS-DIV-Slicer Webpage: Das Register "Webpage" zeigt die generierte
Webseite, deren Content (zumindest die Zeilenanzahl) ebenso on-the-fly geändert
werden kann, wie die komplette Breite der Page.
Anders als bei meinen vorherigen Tutorials will ich den Source diesmal
nicht Zeile für Zeile durchkauen. Wir beschränken uns auf die interessanten
bzw. kniffligen Teile.
Über einen TOpenPictureDialog ein Bild auswählen und in eine TImage-Komponente
einzuladen ist trivial. Zur weiteren Verarbeitung das Bild in eine TBitmap zu
"assignen" ebenfalls. Diese Bitmap jedoch in eine weitere Bitmap zu wandeln,
die in eine vorgegebene TPaintBox passt, das ist schon etwas diffiziler. Die
folgenden Prozeduren erledigen das für uns:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
function tmain_f.img_read(fn:string):bool;
begin
[...]
//Bild einladen
img.picture.loadfromfile(fn);
//basis-bitmap fuellen
img_bmp.assign(img.Picture.graphic);
[...]
//img_bmp an PaintBox pb anpassen
pb_pResize(nil);
[...]
end;
//----------------------------------------------------------------
procedure Tmain_f.pb_pResize(Sender: TObject);
var
w,h:integer;
begin
//set pb_bmp dimension
pb_bmp.width:=pb.width;
pb_bmp.height:=pb.height;
[...]
//calculate site of original image to PaintBox
if(img_bmp.width/pb.width)>(img_bmp.Height/pb.height) then
begin
w:=pb.width;
h:=trunc(w*img_bmp.Height/img_bmp.width);
end
else
begin
h:=pb.height;
w:=trunc(h*img_bmp.width/img_bmp.height);
end;
[...]
//paint original image stretched to pb_img_bmp
pb_img_bmp.width:=w;
pb_img_bmp.height:=h;
SetStretchBltMode(pb_img_bmp.canvas.Handle,coloroncolor);
StretchBlt(
pb_img_bmp.canvas.Handle,0,0,w,h,
img_bmp.Canvas.Handle,0,0,img_bmp.width,img_bmp.Height,
srccopy
);
//remember ratio of paint-box-image to original-image
aspect_ration:=w/img_bmp.Width;
//calculate left and top for centering pb_img_bmp on PaintBox
pb_l:=(pb.Width-w)div 2;
pb_t:=(pb.height-h)div 2;
end;
Im TImage "img" steht das Original-Bild, z.B. ein JPG mit mehr als 2000x1000 Pixel.
Im TBitmap "img_bmp" wird die entsprechende Bitmap-Version gesichert. Die TPaintBox
"pb" hat dagegen beispielsweise nur eine Grösse von z.B. 800x400 Pixel. Die
TPaintBox-Grafik selbst wird im TBitmap "pb_bmp" gesichert. Um nun "img_bmp" in
"pb_img" einzubetten, muss wegen der Grössendifferenz gerechnet werden.
Bei jeder Grössenänderung von TPaintBox "pb" wird nun durch ein "OnResize"-Ereignis
die Prozedur "pb_pResize" aufgerufen. Hier wird zuerst ermittelt, ob "img_bmp"
in voller Breite oder aber in voller Höhe in "pb_bmp" verkleinert bzw. vergrössert
werden muss. Ist die PaintBox z.B. quadratisch, "img_bmp" aber rechteckig, kann
die bezüglich der Grösse modifizierte Version davon, also "pb_img_bmp", nur die
volle Breite von "pb_bmp" einnehmen, nicht aber dessen volle Höhe.
Volle Breite ausgenutzt von PaintBox: Im Verhältnis zu Breite und Höhe der
PaintBox ist das Original-Bild eher breit als hoch. Daher wird es in der
"StretchBlt"-Prozedur derart umgerechnet, dass es horizontal die komplette
PaintBox ausnutzt und vertikal zentriert wird.
Volle Höhe ausgenutzt von PaintBox: Hier ist das Original-Bild im Verhältnis
zu Breite und Höhe der PaintBox eher hoch als breit. Diesmal wird es derart
umgerechnet, dass es vertikal die komplette PaintBox ausnutzt und horizontal
zentriert wird.
Das Umrechnungsverhältnis zwischen Original-Bild und dem PaintBox-Bild wird in
der globalen Variable "aspect_ratio" festgehalten. Wir brauchen diesen Double-Wert
später noch, wenn wir PaintBox-Koordinaten auf das Original-Bild umrechnen müssen.
Die globalen Variablen "pb_l" und "pb_t" geben die x- und y-Koordinaten an,
die "pb_img_bmp" innerhalb von "pb_bmp" einnimmt, um vertikal und horizontal
zentriert zu werden.
Das folgende Schaubild möge die Zusammenhänge grafisch wiedergeben:
Vom 'img' zu 'pb_img_bmp': Das Original-Bild in "img" wird zur Bitmap
"img_bmp", die dann verkleinert/vergrössert wird zu "pb_img_bmp", sodass sie
zentriert in der PaintBox-Bitmap "pb_bmp" positioniert werden kann.
Um die gewünschten neun "Slices", das sind die Zellen der DIV-Tabelle,
zu positionieren, verwenden wir insgesamt vier TScrollBar-Instanzen: Zwei für die
vertikalen Schnitte und zwei für die horizontalen Schnitte.
Minima und Maxima der ScrollBars werden in der Prozedur "img_read" berechnet:
00001
00002
00003
00004
00005
00006
00007
00008
00009
slice_horizontal1_sb.Min:=1*_slice_min;
slice_horizontal1_sb.Max:=img_bmp.width-2*_slice_min;
slice_horizontal2_sb.Min:=2*_slice_min;
slice_horizontal2_sb.Max:=img_bmp.width-1*_slice_min;
slice_vertical1_sb.min:=1*_slice_min;
slice_vertical1_sb.Max:=img_bmp.height-2*_slice_min;
slice_vertical2_sb.min:=2*_slice_min;
slice_vertical2_sb.Max:=img_bmp.height-1*_slice_min;
Die Konstante "_slice_min" (hier: 5 Pixel) gibt an, wie klein
ein "Slice" dimensioniert werden darf. Durch diese Vorgabe wird
verhindert, dass der "CSS-DIV-Slicer" Exceptions produziert, weil
mit zu kleinen Werten keine vernünftigen Berechnungen mehr möglich
sind.
Die "Slice"-Linien werden in der zentralen Prozedur "pbPaint" auf
die PaintBox-Bitmap gezeichnet, und zwar durch Aufruf der internen
Procedure "paint_slices_lines":
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
procedure paint_slices_lines;
var
i:integer;
begin
[...]
//yep:paint horizontal slices-lines to pb_bmp
i:=slice_vertical1_sb.position;
pb_bmp.Canvas.moveTo(0,pb_t+h_pos_aspect_ration(i));
pb_bmp.Canvas.LineTo(pb_bmp.width,pb_t+h_pos_aspect_ration(i));
[...]
//paint vertical slices-lines to pb_bmp
i:=slice_horizontal1_sb.position;
pb_bmp.Canvas.moveTo(w_pos_aspect_ration(i),pb_t);
pb_bmp.Canvas.LineTo(pb_l+w_pos_aspect_ration(i),pb_bmp.height);
[...]
end;
Über die TCanvas-Prozedur "moveto" wird der Grafik-Pinsel positioniert,
ausgehend von dort dann mittels "lineto" eine Linie bis zu einem bestimmten
Punkt gemalt. Die horizontalen Linien beginnen links aussen und reichen bis
rechts aussen, und zwar waagrecht auf der Höhe der vertikalen TScrollBar-Instanzen,
deren Position zuvor über die Funktion "h_pos_aspect_ration" auf die
PaintBox-Dimension umgerechnet wird. Die vertikalen Linien werden entsprechend
von oben nach unten ganz ähnlich realisiert.
Einzeichnen der Slice-Linien: Vier Slice-Linien, die über ScrollBars
eingestellt werden, unterteilen das PaintBox-Bild in insgesamt neun "Slices".
Ein "Slice" (und der jeweils zugehörige "Slice-Cut") hat verschiedene Eigenschaften,
die später das Aussehen der Webseite bestimmen. Verwaltet werden die neun Slices
über das Array "slice_a" von Typ TSlice_cut:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
type
tcut_type=(ct_none,ct_all,ct_width,ct_height,ct_free);
tcut_repeat=(cr_repeat_no,cr_repeat,cr_repeat_x,cr_repeat_y);
tslice_cut=record
l,t,w,h:integer;
cut_type:tcut_type;
background_color:tcolor;
background_repeat:tcut_repeat;
end;
Tmain_f = class(TForm)
[...]
slice_cut_a:array[0..8]of tslice_cut;
[...]
end;
Um einen "Slice" auszuwählen, kann es einmal über die TComboBox "slice_cb"
selektiert oder aber mit der Maus auf der PaintBox angeklickt werden. Wird
nämlich auf "pb" geklickt, dann berechnen wir anhand der x/y-Koordinaten,
welches Slice dabei getroffen wurde:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
procedure Tmain_f.pbMouseUp
(
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
var
r:integer;
begin
[...]
x:=trunc((x-pb_l)/aspect_ration);
y:=trunc((y-pb_t)/aspect_ration);
if y<slice_vertical1_sb.Position then
begin
if x<slice_horizontal1_sb.Position then r:=0
else if x<slice_horizontal2_sb.Position then r:=1
else r:=2;
end
else if y<slice_vertical2_sb.Position then
begin
if x<slice_horizontal1_sb.Position then r:=3
else if x<slice_horizontal2_sb.Position then r:=4
else r:=5;
end
else
begin
if x<slice_horizontal1_sb.Position then r:=6
else if x<slice_horizontal2_sb.Position then r:=7
else r:=8;
end;
slice_cb.ItemIndex:=r;
slice_cbChange(Sender);
[...]
end;
Zunächst rechnen wird die Koordinaten-Werte "x" und "y" mittels
"aspect_ratio" auf die Koordinaten-Werte des Original-Bildes hoch.
Danach schauen wir, ob sich "y" z.B. unterhalb von "slice_vertical1_sb.Position"
befindet. In diesem Fall hätten wir die "Kopf-Zeile" der Tabelle getroffen.
Anschliessend prüfen wir, innerhalb welcher der drei möglichen Spalte sich
der "x"-Wert befindet. Gesetzt wird letztlich die Integer-Variable "r",
die dem ItemIndex der Slice-ComboBox "slice_cb" entspricht.
Das ausgewählte Slice wieder über die Prozedur "pbpaint" auf die PaintBox
gezeichnet, und zwar diesmal mithilfe der internen Prozedur "paint_slice_selection":
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
procedure paint_slice_selection;
var
slice_cut_rec:trect;
begin
[...]
//yep: get rectangle of active slice
slice_cut_rec:=slice_cut_rec_get(slice_cb.itemindex);
[...]
//calculate to paint-box-size
slice_cut_rec.Left :=pb_l+trunc(slice_cut_rec.Left*aspect_ration);
slice_cut_rec.top :=pb_t+trunc(slice_cut_rec.top*aspect_ration);
slice_cut_rec.right :=pb_l+w_pos_aspect_ration(slice_cut_rec.right);
slice_cut_rec.bottom:=pb_t+h_pos_aspect_ration(slice_cut_rec.bottom);
//paint brush to pb_bmp
pb_bmp.Canvas.Rectangle(slice_cut_rec);
end;
Selektion eines Slices: Der Benutzer hat auf das untere Drittel der
PaintBox geklickt. Die x/y-Koordinaten befanden sich innerhalb von Slice "7".
Daher wurde dieses Slice selektiert und in der Prozedur "pbpaint" mit
schwarz-weiss alternierenden Linien markiert.
Der "CSS-DIV-Slicer" ermöglicht es, innerhalb eines "Slices" mit der
Maus einen neuen Bereich zu selektieren - ich nenne die Dinger
"Slice-Cut" - und nur diesen dann in der Webseite als Background-Image
der zugehörigen DIV-Definition einzusetzen.
Zusätzlich kann die "Background-Repeat"-Eigenschaft gesetzt werden,
wodurch der "Slice-Cut" entweder nur einmal gezeichnet wird ("no-repeat"),
oder über die komplette DIV verläuft ("repeat"), bzw. dies nur über die
komplette Breite bzw. Höhe macht ("repeat-x" oder "repeat-y").
Was aber soll das bringen? Schauen wir uns dazu folgendes "Torbogens-Image" an:
Generator: Slices ohne Cuts: Wir sehen hier unsere neun "Slices" ohne
zusätzliche "Slice-Cuts". Die Mauerränder werden mit "Background-Repeat:repeat"
über die komplette DIV verteilt.
Lassen wir uns aus dieser Vorlage vom "CSS-DIV-Slicer" eine Webseite generieren,
dann schaut das Ergebnis (bei ausreichender Höhe des Inhalts) etwas unschön aus:
Webseite A: Slices ohne Cuts: Durch die Anzahl der
Inhaltszeilen wird die Tabelle gestreckt. Gemäss der "repeat"-Eigenschaft werden
die Mauerränder mehrfach gezeichnet. Bei der rechten Mauer sieht das auch ganz
passabel aus, aber bei der linken sind deutlich Unregelmässigkeiten zu erkennen.
Die linke Mauer erscheint "unnatürlich" abgehakt, weil der untere Teil des Slices
nicht harmoniert mit dem oberen. Durch geschickte Wahl eines "Slice-Cuts" kann
man das Ergebnis aber unter Umständen verbessern.
Generator: Slices mit Cuts: Innerhalb des linken, mittleren "Slices" (#3)
wurde ein "Slice-Cut" markiert, der über die komplette Breite des "Slice"
verläuft, nicht aber über dessen komplette Höhe.
Webseite B: Slices mit Cuts: Die linke Torbogen-Mauer ist relativ gleichmässig,
weil oberer und unterer Rand des "Slice-Cuts" besser harmonieren als dies beim
vollständigem "Slice" der Fall ist ("Webseite A: Slices ohne Cuts").
Um einen "Slice-Cut" zu erstellen, wählt man zunächst einen "Slice" per
ComboBox oder Maus aus. Anschliessend kann man auf der linken Seite
den gewünschten "Slice-Cut"-Typ einstellen. Man hat die Wahl zwischen:
- Kein Slice: Nur Hintergrundfarbe oder Transparenz
- Voller Slice: "Slice-Cut" identisch mit "Slice"
- Voller Breiten-Cut: Die volle Breite des "Slice" wird genommen, nicht
aber unbedingt auch die komplette Höhe
- Voller Höhen-Cut: Volle Höhe des "Slice" wird genommen, nicht
aber auch unbedingt die komplette Breite
- Freistil-Cut: Ein beliebigen Bildausschnitt wird als "Slice-Cut"
verwendet. Der Ausschnitt kann übrigens auch aus einem anderem "Slice"
als dem aktiven ausgewählt werden!
Um den "Slice-Cut"-Bereich zu markieren, kann der Anwender entweder die
zugehörigen SpinEdit-Instanzen verwenden, oder ihn über STEUERUNG+MAUS
direkt "aufmalen". Letzteres erfolgt über die folgenden Prozeduren:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
procedure Tmain_f.pbMouseDown
(
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
begin
[...]
if not(ssctrl in shift) then exit;
mousedown_ok:=true;
mouse_rec.left:=x;
mouse_rec.top:=y;
mouse_rec.right:=x+1;
mouse_rec.bottom:=y+1;
end;
procedure Tmain_f.pbMouseMove
(
Sender: TObject; Shift: TShiftState;
X,Y: Integer
);
var
slice_rec:trect;
begin
[...]
if not(ssctrl in shift) then exit;
if not mousedown_ok then exit;
mouse_rec.right:=x;
mouse_rec.bottom:=y;
slice_rec:=slice_rec_get(slice_cb.itemindex);
[...]
if ct_width_rb.checked then
begin
ct_width_t_se.Value:=
trunc((mouse_rec.top-pb_t)/aspect_ration)-
slice_rec.top;
ct_width_h_se.Value:=
trunc((mouse_rec.bottom-mouse_rec.top)/aspect_ration);
end
else if ct_height_rb.checked then
begin
[...]
end
else if ct_free_rb.Checked then
begin
[...]
end;
[...]
end;
procedure Tmain_f.pbMouseUp
(
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
begin
[...]
mousedown_ok:=false;
end;
Wird auf die PaintBox geklickt, löst dies die Prozedur "pbMouseDown" aus.
Hier prüfen wir, ob auch die STRG-Taste gedrückt wurde. Ist dem so,
dann merken wir uns den Klick-Punkt X/Y im globalen TRect "mouse_rect" als
"Left"- und "Top"-Werte. Ausserdem wird das Flag "mouse_down_ok" gesetzt.
Bewegt der Anwender nun die Maus über dem "Slice", wird die Prozedur
"pbMouseMove" ausgelöst. Befinden wir uns im "Slice-Cut"-Modus, dann merken
wir uns erneut die x- und y-Werte im "mouse_rec", diesmal als als "Width"-
und "Height"-Werte. Anschliessend rechnen wir alle "mouse_rec"-Attribute
auf die realen Image-Werte hoch, und übergeben sie je nach "Slice-Cut"-Typ
an die zugehörigen SpinEdit-Instanzen.
Der "Slice-Cut"-Modus endet, wenn der Benutzer den linken Mausknopf wieder
loslässt. Dadurch wird die Prozedur "pbMouseUp" ausgelöst und das Flag
"mouse_down_ok" auf "false" zurückgesetzt.
Freestyle-Slice-Cut: Mit STRG+Maus wurde aus "Slice 5" ein Bildbereich
ausgewählt, der aber im ausgewählten "Slice 2" zur Anwendung kommt. Der Bildbereich
lässt sich beim "Slice-Cut"-Typ "Freistil" zudem beliebig dimensionieren.
Um dem Benutzer direkt zu zeigen, welche Auswirkung die Änderung
von "Slices-Cuts" oder der "Repeat"-Eigenschaften auf das Aussehen der
Ziel-Webseite hat, simulieren wir das Verhalten eines Webbrowsers. Dies
geschieht einmal mehr über die Prozedur "pbpaint", diesmal über die
interne Prozedur "paint_slices_cuts":
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
procedure paint_slices_cuts;
var
r,
slice_w,slice_h,
slice_cut_w,slice_cut_h,
repeat_x,repeat_y,
x,y,l,t:integer;
slice_rec,slice_cut_rec:trect;
begin
[...]
for r:=0 to 8 do begin
//calculate slice-rectangle on PaintBox
slice_rec:=slice_rec_get(r);
slice_rec.Left :=trunc(slice_rec.Left*aspect_ration);
slice_rec.top :=trunc(slice_rec.top*aspect_ration);
slice_rec.right :=w_pos_aspect_ration(slice_rec.right);
slice_rec.bottom:=h_pos_aspect_ration(slice_rec.bottom);
//calculate slice-cut-rectangle on PaintBox
//dimension is identical with slice_rec if there is no cut set
slice_cut_rec:=slice_cut_rec_get(r);
slice_cut_rec.Left :=trunc(slice_cut_rec.Left*aspect_ration);
slice_cut_rec.top :=trunc(slice_cut_rec.top*aspect_ration);
slice_cut_rec.right :=w_pos_aspect_ration(slice_cut_rec.right);
slice_cut_rec.bottom:=h_pos_aspect_ration(slice_cut_rec.bottom);
//fill slice width background-color
pb_bmp.Canvas.pen.color:=slice_cut_a[r].background_color;
pb_bmp.Canvas.pen.width:=1;
pb_bmp.Canvas.brush.color:=slice_cut_a[r].background_color;
if slice_cut_a[r].background_color=_color_transparent then
pb_bmp.Canvas.brush.Style:=bsdiagcross
else
pb_bmp.Canvas.brush.Style:=bssolid;
pb_bmp.canvas.rectangle(
pb_l+slice_rec.left,pb_t+slice_rec.top,
pb_l+slice_rec.right,pb_t+slice_rec.bottom
);
//no slice or cut wanted?
if slice_cut_a[r].cut_type=ct_none then continue;
//set dimension of slice_cut-bitmap
slice_cut_w:=slice_cut_rec.Right-slice_cut_rec.Left;
slice_cut_h:=slice_cut_rec.bottom-slice_cut_rec.top;
slice_cut_bmp.Width:=slice_cut_w;
slice_cut_bmp.height:=slice_cut_h;
//copy slice-cut-rect from pb_img_bmp to slice_cut_bmp
bitblt(
slice_cut_bmp.canvas.handle,0,0,slice_cut_w,slice_cut_h,
pb_img_bmp.Canvas.Handle,slice_cut_rec.left,slice_cut_rec.Top,
srccopy
);
//set dimension of slice-bitmap
slice_w:=slice_rec.Right-slice_rec.Left;
slice_h:=slice_rec.bottom-slice_rec.top;
slice_bmp.width:=slice_w;
slice_bmp.height:=slice_h;
//set number of repeats in y- and x-direction
repeat_y:=(slice_h div slice_cut_h);
repeat_x:=(slice_w div slice_cut_w);
if slice_cut_a[r].background_repeat=cr_repeat_no then
begin
repeat_x:=0;
repeat_y:=0;
slice_w:=slice_cut_w;
slice_h:=slice_cut_h;
end
else if slice_cut_a[r].background_repeat=cr_repeat_x then
begin
repeat_y:=0;
slice_h:=slice_cut_h;
end
else if slice_cut_a[r].background_repeat=cr_repeat_y then
begin
repeat_x:=0;
slice_w:=slice_cut_w;
end;
//paint slice-cuts to slice-bitmap in y-direction
for y:=0 to repeat_y do begin
//paint slice-cuts to slice-bitmap in x-direction
for x:=0 to repeat_x do begin
//calculate actual slice_cut_position
l:=x*slice_cut_w;
t:=y*slice_cut_h;
//paint slice_cut_bmp to slice_bmp
bitblt(
slice_bmp.canvas.handle,l,t,slice_cut_w,slice_cut_h,
slice_cut_bmp.Canvas.Handle,0,0,
srccopy
);
end;
end;
//now copy the slice_cut-filled slice_bmp to pb_bmp
bitblt(
pb_bmp.canvas.handle,
pb_l+slice_rec.left,
pb_t+slice_rec.Top,
slice_w,slice_h,
slice_bmp.Canvas.Handle,0,0,
srccopy
);
end;
end;
Wir durchlaufen das Array "slice_cut_a", welches die neun "Slice-Cuts"
enthält, wie sie der Benutzer mittels ScrollBars und Maus bestimmt hat.
Die Funktionen "slice_rec_get" und "slice_cut_rec_get" liefert uns
die Koordinaten-Rechtecke des aktuell betrachteten "Slices" bzw.
"Slice-Cuts". Ist kein "Slice-Cut" gesetzt, sind beide TRect-Instanzen identisch.
Die Attribute "left", "top", "right" und "bottom" werden anschliessend
auf die TPaintBox-Dimensionen umgerechnet.
Das komplette "Slice"-Rechteck wird anschliessend mit der gewählten
Hintergrundfarbe ausgefüllt. Um die transparente Farbe darzustellen,
wird statt einem "bsSolid"- ein "bsDiagCross"-Brush-Style verwendet,
wodurch ein zusätzliches Kreuzmuster auf die Grafik gelegt wird.
Transparente "Slices" ohne Slice: Transparente Slices ohne Hintergrundgrafik
erscheinen später auf der Webseite nicht. Um sie für den Benutzer zu kennzeichnen,
werden sie im "Show Slices and Cuts"-Modus mit einer Kreuzschraffur wiedergegeben.
Die Hintergrundfarbe des aktuellen "Slices" ist soeben gemalt worden. Nun wird
die Bitmap "slice_cut_bmp" passend dimensioniert und mit dem gesetzten "Slice-Cut"
gefüllt, also dem gewählten Bildausschnitt aus dem Original-Bild. Falls kein
"Slice-Cut" vorhanden ist, wird der komplette "Slice" verwendet.
Jetzt wird die "Background-repeat"-Eigenschaft behandelt: Wir prüfen, wie oft
unser "Slice-Cut" in den aktuellen "Slice" passt, und zwar sowohl vertikal
("repeat_y") als auch horizontal ("repeat_x"). Entsprechend oft wird dann die
Bitmap "slice_cut_bmp" mittels API-Funktion "BitBlt" auf die Bitmap "slice_bmp"
kopiert.
Zum Schluss wird die (mit "Slice-Cuts" gefüllte) "slice_bmp" noch an berechneter
Stelle auf die Bitmap der PaintBox verfrachtet. Die "pb_bmp" wird dadurch in
neun Durchgängen mit allen "Slices" und "Slice-Cuts" komplett bemalt.
Slices und Cuts gemixt: Hier sehen wir verschiedene Arten von "Slices"
und "Slice-Cuts". Megan Fox Gesicht ganz oben wurde als "Freestyle-Cut" konzipiert.
Das Navigationsfeld kommt ohne "Slice" aus, dafür aber mit gelber Hintergrundfarbe.
Und der untere Rand ist ein "Volle Breite-Cut", der etwa ein Drittel der
ursprünglichen Höhe des "Slices" einnimmt, durch die "Background-repeat"-Eigenschaft
aber insgesamt dreimal untereinander gezeichnet wird.
Wie wir gesehen haben, können mit dem "CSS-DIV-Slicer" beliebige Bilder eingeladen,
in "Slices" unterteilt, und mit zusätzlichen Eigenschaften wie Hintergrundfarben
und "Slice-Cuts" versehen werden. Um diese Arbeit zu sichern, kann man sie in
Profilen speichern.
"CSS-DIV-Slicer"-Profile sind gewöhnliche INI-Dateien, erhalten jedoch die
Endung ".cds". Zum Neuanlegen eines Profils verwenden wir die folgende
Prozedur:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
procedure Tmain_f.profile_new;
var
r:integer;
begin
[...]
img.Picture.graphic:=nil;
img_bmp.Width:=10;
img_bmp.height:=10;
img_fn_e.text:='';
img_dim_e.Text:='';
//slices werden auf standard gesetzt
for r:=0 to 8 do begin
slice_cut_a[r].l:=10;
slice_cut_a[r].t:=10;
slice_cut_a[r].w:=50;
slice_cut_a[r].h:=50;
slice_cut_a[r].cut_type:=ct_all;
slice_cut_a[r].background_color:=_color_transparent;
slice_cut_a[r].background_repeat:=cr_repeat;
end;
profile_fn:=ansilowercase(homedir+_profile_empty);
caption:=_caption+' - '+profile_fn;
//save profile state
profile_str:=profile_str_get;
[...]
end;
Das Bild des letzten Profiles wird gelöscht (sofern vorhanden).
Alle neun Slices erhalten für ihre Attribute Standardwerte. Die
Caption der Form wird gesetzt, der Profilnamen auf "_profile_empty"
gesetzt. Zuletzt merken wir uns in "profil_str" den aktuellen Status
des frischen Profils. Dazu wird die Funktion "profile_str_get"
eingesetzt:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
function tmain_f.profile_str_get:string;
function i(v:integer):string;
begin
result:=inttostr(v);
end;
function b(v:bool):string;
begin
if v then result:='1' else result:='0';
end;
var
r:integer;
begin
result:=
img_fn_e.text+
i(slice_horizontal1_sb.Position)+
[...]
b(show_slice_rb.Checked)+
[...]
i(web_color_sh.brush.color);
for r:=0 to 8 do
begin
result:=
result+
i(integer(slice_cut_a[r].cut_type))+
i(slice_cut_a[r].l)+
[...]
i(slice_cut_a[r].background_color)+
i(integer(slice_cut_a[r].background_repeat));
end;
end;
//----------------------------------------------------------------
function tmain_f.profile_write_ask:byte;
begin
//some changes in profile?
result:=id_no;if profile_str=profile_str_get then exit;
result:=msg_question_ync
(
'Profile '+profile_fn+' has changed.'+_cr+
'Save actual settings to file?'
);
if result=id_yes then profile_write(profile_fn);
end;
Der "profile_str"-String enthält alle Parameter eines Profiles,
konvertiert in einzelne Zeichen und hintereinander angefügt zu einem
einzigen langen String. So lässt sich später jede Änderung am Profil
schnell feststellen, indem wir das aktuelle Resultat von "profile_str_get"
einfach mit dem zuvor gesicherten "profile_str" vergleichen.
Einladen und Speichern geschieht in ähnlicher Weise, nur dass die
Parameter nicht aus einem String gelesen bzw. in einen String
geschrieben, sondern aus einer INI-Datei gelesen bzw. in eine
INI-Datei geschrieben werden.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
procedure tmain_f.profile_read(fn:string);
var
r:integer;
begin
[...]
with TIniFile.Create(fn) do
begin
img_fn_e.text:=readstring('param','profile_img_fn','');
slice_horizontal1_sb.max:=10000;
slice_horizontal1_sb.Position:=readinteger('param','w1_sb',10);
[...]
show_slice_rb.Checked:=readbool('show','show_slice_rb',true);
[...]
sh_color_set
(
web_color_sh,
readinteger('web','web_color_sh',_color_transparent)
);
for r:=0 to 8 do
begin
slice_cut_a[r].cut_type:=tcut_type
(
readinteger
(
'slice_cut','cut_type_'+inttostr(r),
integer(ct_all)
)
);
slice_cut_a[r].l:=readinteger('slice_cut','l_'+inttostr(r),10);
[...]
slice_cut_a[r].background_color:=readinteger
(
'slice_cut','background_color_'+inttostr(r),
_color_transparent
);
slice_cut_a[r].background_repeat:=tcut_repeat(readinteger
(
'slice_cut','backround_repeat_'+inttostr(r),
integer(cr_repeat))
);
end;
free;
end;
profile_fn:=fn;
img_read(img_fn_e.text);
caption:=_caption+' - '+profile_fn;
//save profile state
profile_str:=profile_str_get;
[...]
end;
//----------------------------------------------------------------
procedure tmain_f.profile_write(fn:string);
var
r:integer;
begin
[...]
with tinifile.Create(fn) do
begin
writestring('param','profile_img_fn',img_fn_e.text);
writeinteger('param','w1_sb',slice_horizontal1_sb.Position);
[...]
writebool('show','show_slice_rb',show_slice_rb.Checked);
[...]
writeinteger('web','web_color_sh',web_color_sh.brush.color);
for r:=0 to 8 do
begin
writeinteger
(
'slice_cut',
'cut_type_'+inttostr(r),
integer(slice_cut_a[r].cut_type)
);
writeinteger('slice_cut','l_'+inttostr(r),slice_cut_a[r].l);
[...]
writeinteger
(
'slice_cut',
'background_color_'+inttostr(r),
slice_cut_a[r].background_color
);
writeinteger
(
'slice_cut',
'backround_repeat_'+inttostr(r),
integer(slice_cut_a[r].background_repeat)
);
end;
free;
end;
profile_fn:=fn;
caption:=_caption+' - '+profile_fn;
//save profile state
profile_str:=profile_str_get;
[...]
end;
Hat der Benutzer ein Profil erstellt und gesichert, kann er sich
schlussendlich eine fertige Webseite generieren lassen, die das Profil
mehr oder weniger 1:1 widerspiegelt. Die Webseite wird automatisch
erzeugt, wenn auf das TabSheet "Webpage" gewechselt wird.
Betrachten wir zunächst die Erstellung der CSS-Datei.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
procedure Tmain_f.web_css_html_make;
var
r,s_w,s_l_w,s_r_w,s_0_h,s_2_h,
s_m_w,s_r_l,s_0_r_t:integer;
s,content_txt:string;
web_width_aspect_ration:double;
function color2html(Color:TColor):string;
begin
if color=_color_transparent then
begin
result:='transparent';
exit;
end;
Result:=
'#'+
IntToHex(Byte(Color),2)+
IntToHex(Byte(Color shr 8),2)+
IntToHex(Byte(Color shr 16),2);
end;
function cut_type2html(ct:tcut_type;fn:string):string;
begin
if ct=ct_none then
begin
result:='none';
deletefile(fn);
exit;
end;
Result:='url('''+fn+''');';
end;
function background_set(s,von:string;r:integer):string;
var
nach:string;
sc:tslice_cut;
begin
sc:=slice_cut_a[r];
nach:=
'background-color:'+
color2html(sc.background_color)+';'+
_cr+
' background-image:'+
cut_type2html(sc.cut_type,'slice_'+von+'.jpg')+';'+
_cr+
' background-repeat:'+
slice_repeat_cb.items[integer(sc.background_repeat)]+';';
von:='$s_'+von+'_background_txt';
result:=stringreplace(s,von,nach,[rfreplaceall]);
end;
function scaled(i:integer):integer;
begin
result:=round(i*web_width_aspect_ration);
end;
function val_set(s:string;nm:string;v:integer):string;
begin
result:=stringreplace(s,nm,inttostr(v)+'px',[rfreplaceall]);
end;
begin
[...]
//scaling: adapt img-width to web-width
web_width_aspect_ration:=web_width_se.value/img_bmp.Width;
//css------------------------------------------------
s_w:=web_width_se.value;
s_l_w:=scaled(slice_horizontal1_sb.position);
s_r_w:=scaled(img_bmp.width-slice_horizontal2_sb.position);
s_0_h:=scaled(slice_vertical1_sb.position);
s_2_h:=scaled(img_bmp.Height-slice_vertical2_sb.position);
s_m_w:=s_w-s_l_w-s_r_w;
s_r_l:=s_w-s_r_w;
s_0_r_t:=2*s_0_h;
s:=css_org_m.Text;
//comments
s:=stringreplace(s,'$generator_txt',_caption,[rfreplaceall]);
s:=stringreplace(s,'$profile_txt',profile_fn,[rfreplaceall]);
s:=stringreplace(s,'$date_txt',datetimetostr(now),[rfreplaceall]);
//body
s:=stringreplace
(
s,
'$web_color_txt',
color2html(web_color_sh.Brush.color),
[rfreplaceall]
);
s:=val_set(s,'$web_margin_t_txt',web_margin_t_se.value);
[...]
//slice dimensions
s:=val_set(s,'$s_w_px',s_w);
s:=val_set(s,'$_0_h_px',s_0_h);
[...]
//slice backgrounds
s:=background_set(s,'0_l',0);
s:=background_set(s,'0_m',1);
s:=background_set(s,'0_r',2);
[...]
//save CSS
css_m.Text:=s;
css_m.lines.savetofile(homedir+_page_dir+_css_fn);
[...]
end;
Im TMemo "css_org_m" steht ein Muster der CSS-Datei. Alle veränderlichen
Teile darin haben einen eine Kennung mit "$" erhalten, die dann je nach
eingestellter Seitenbreite und "Slice-Cuts" durch einen berechneten
oder anderweitig ermittelten String gezielt per "stringreplace"-Funktion
ersetzt werden. Am Schluss wird der derart veränderte String an das TMemo
"css_m" übergeben und als "css_div_sliver.css"-Datei gespeichert.
Die Muster-CSS-Datei orientiert sich dabei stark am in Kapitel x vorgestellten
CSS-File. Es schaut auszugsweise folgendermassen aus:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
/*
CSS-CODE
--------
GENERATOR: $generator_txt
(Daniel Schwamm, http://www.daniel-schwamm.de)
PROFILE : $profile_txt
DATE : $date_txt
*/
body
{
background-color: $web_color_txt;
margin: $web_margin_t_txt
$web_margin_r_txt
$web_margin_b_txt
$web_margin_l_txt;
}
#slice_div
{
position:relative;
left:0px;
top:0px;
width:$s_w_px;
margin: 0px auto;
}
#slice_0_div
{
position:relative;
left:0px;
top:0px;
width:100%;
height:$s_0_h_px;
margin: 0px auto;
}
#slice_0_l_div
{
position:relative;
left:0px;
top:0px;
width:$s_l_w_px;
height:100%;
$s_0_l_background_txt
}
#slice_0_m_div
{
position:relative;
left:$s_l_w_px;
top:-$s_0_h_px;
width:$s_m_w_px;
height:100%;
$s_0_m_background_txt
}
#slice_0_r_div
{
position:relative;
left:$s_r_l_px;
top:-$s_0_r_t_px;
width:$s_r_w_px;
height:100%;
$s_0_r_background_txt
}
[...]
Die HTML-Seite wird in ganz ähnlicher Weise wie die CSS-Seite
generiert. Die Änderungen sind hier viel geringer, denn alle
Berechnungen oder Design-Arbeiten finden im CSS-File statt.
Die HTML-Datei enthält also im Wesentlichen nur die "nackten"
HTML-Tags mit passender CSS-ID.
Wir haben hier einmal die String-Replace-Funktionalität:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
procedure Tmain_f.web_css_html_make;
var
r,s_w,s_l_w,s_r_w,s_0_h,s_2_h,
s_m_w,s_r_l,s_0_r_t:integer;
s,content_txt:string;
web_width_aspect_ration:double;
function text_set(s:string;von:string):string;
var
nach:string;
begin
nach:='';
if web_text_chb.Checked then
nach:=copy(von,2,length(von)-5);
result:=stringreplace(s,von,nach,[rfreplaceall]);
end;
begin
[...]
content_txt:='';
for r:=0 to web_content_se.value-1 do
begin
content_txt:=
content_txt+
'Content_'+inttostr(r+1)+
'<br>'+_cr;
end;
s:=html_org_m.Text;
s:=text_set(s,'$0_l_txt');
s:=text_set(s,'$0_m_txt');
[...]
s:=stringreplace(s,'$generator_txt',_caption,[rfreplaceall]);
[...]
//save HTML
html_m.Text:=s;
html_m.lines.savetofile(homedir+_page_dir+_html_fn);
[...]
end;
Und zum anderen die Muster-HTML-Datei:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'
'http://www.w3.org/TR/html4/loose.dtd'>
<html>
<!--
HTML-CODE
---------
GENERATOR: $generator_txt
(Daniel Schwamm, http://www.daniel-schwamm.de)
PROFILE : $profile_txt
DATE : $date_txt
-->
<head>
<title>$generator_txt - $profile_txt - $date_txt</title>
<link rel=stylesheet type=text/css href='css_div_slicer.css'>
</head>
<body>
<div id='slice_div'>
<div id='slice_0_div'>
<div id='slice_0_l_div'>$0_l_txt</div>
<div id='slice_0_m_div'>$0_m_txt</div>
<div id='slice_0_r_div'>$0_r_txt</div>
</div>
<div id='slice_1_div'>
<div id='slice_1_l_div'>$1_l_txt</div>
<div id='slice_1_m_div'>
$content_txt
</div>
<div id='slice_1_r_div'>$1_r_txt</div>
</div>
<div id='slice_2_div'>
<div id='slice_2_l_div'>$2_l_txt</div>
<div id='slice_2_m_div'>$2_m_txt</div>
<div id='slice_2_r_div'>$2_r_txt</div>
</div>
</div>
</body>
</html>
Die CSS- und HTML-Seite haben wir uns im vorherigen Kapitel bereits
generieren lassen. Das Einzige, was jetzt noch zur kompletten Webpage
fehlt, sind die "Slices" bzw. die "Slice-Cuts".
Hier hilft uns die Prozedur "web_slices_cuts_make" weiter:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
procedure Tmain_f.web_slices_cuts_make;
var
tmp_bmp:tbitmap;
h,w,w1,w2,h1,h2:integer;
jpg:tjpegimage;
web_width_aspect_ration:double;
function scaled(i:integer):integer;
begin
result:=round(i*web_width_aspect_ration);
end;
procedure slice(r:integer;fn:string;l,t,w,h:integer);
var
slice_cut_rec:trect;
begin
//no background?
if slice_cut_a[r].cut_type=ct_none then
begin
deletefile(homedir+_page_dir+'slice_'+fn+'.jpg');
exit;
end;
//full slice?
if slice_cut_a[r].cut_type=ct_all then
begin
tmp_bmp.Width:=w;
tmp_bmp.Height:=h;
end
else
begin
//cut-slice
slice_cut_rec:=slice_cut_rec_get(r);
//cut-bitmap dimension
w:=slice_cut_rec.Right-slice_cut_rec.Left;
h:=slice_cut_rec.bottom-slice_cut_rec.top;
l:=slice_cut_rec.left;
t:=slice_cut_rec.Top;
end;
//scaling
tmp_bmp.Width:=scaled(w);
tmp_bmp.Height:=scaled(h);
SetStretchBltMode(tmp_bmp.canvas.Handle,coloroncolor);
StretchBlt(
tmp_bmp.canvas.handle,0,0,tmp_bmp.Width,tmp_bmp.height,
img_bmp.Canvas.Handle,l,t,w,h,
srccopy
);
jpg.Assign(tmp_bmp);
jpg.SaveToFile(homedir+_page_dir+'slice_'+fn+'.jpg');
end;
begin
[...]
jpg:=tjpegimage.Create;
jpg.CompressionQuality:=70;
tmp_bmp:=tbitmap.Create;
try
//scaling: adapt img-width to web-width
web_width_aspect_ration:=web_width_se.value/img_bmp.Width;
w:=img_bmp.width;
w1:=slice_horizontal1_sb.Position;
w2:=slice_horizontal2_sb.Position;
h:=img_bmp.Height;
h1:=slice_vertical1_sb.Position;
h2:=slice_vertical2_sb.Position;
slice(0,'0_l', 0,0, w1,h1);
slice(1,'0_m',w1,0,w2-w1,h1);
slice(2,'0_r',w2,0, w-w2,h1);
slice(3,'1_l', 0,h1, w1,h2-h1);
slice(4,'1_m',w1,h1,w2-w1,h2-h1);
slice(5,'1_r',w2,h1, w-w2,h2-h1);
slice(6,'2_l', 0,h2, w1,h-h2);
slice(7,'2_m',w1,h2,w2-w1,h-h2);
slice(8,'2_r',w2,h2, w-w2,h-h2);
finally
tmp_bmp.Free;
jpg.Free;
end;
[...]
end;
Wir erzeugen am Anfang eine temporäre TBitmap "bmp_tmp" und ein
temporäres TJPegImage "jpg". Dann berechnen wir den Double-Wert
"web_width_aspect_ration", der vorgibt, wie die originalen
Bilder-"Slices" auf die gewünschte Webseiten-Breite umgerechnet
werden müssen.
Die interne Prozedur "slice" schneidet uns dann den passenden
Bildausschnitt aus dem Original-Bild, verkleinert bzw. vergrössert ihn
gemäss "web_width_aspect_ration" per API-StretchBlt, konvertiert
die so gefüllte "bmp_tmp" in "jpg", und speichert dieses zuletzt
ab.
Zuletzt noch ein paar Beispiel-Pages, die mit dem "CSS-DIV-Slicer" erstellt
wurden. Einmal mit wenig Zeilen Inhalt und einmal mit etwas mehr Zeilen Inhalt.
Der "CSS-DIV-Slicer" ist .... nun ja, sicherlich kein häufig gewünschtes
Tool. Doch wer Webseiten auf Basis von DIVs erstellen will, die höhendynamisch
und mit flexibler Breite ausgestattet sind, und dazu noch Background-Images
in allen DIVs einsetzt, für den mag das ein durchaus nützliches Werkzeug sein,
da es einem einen Haufen Zeit und Ärger ersparen kann.
Für mich fällt es v.a. in die Kategorie: Ich hab's gemacht, weil ich's kann.
Mängel gibt es diverse. So kann man Background-Images per CSS auch noch exakt
positionieren innerhalb einer DIV. Das auch noch mit hineinzubauen hatte ich
aber keine Lust mehr. Zumal mir der Platz auf dem Linken Komponenten-Panel
allmählich ausging.
So etwas verdirbt mir jedenfalls nicht den Spass an meinem Proggy.
Ist halt nichts Hundertprozentiges. But who cares?
Wer mag, kann sich den "CSS-DIV-Slicer" bei mir downloaden. Enthalten ist
der komplette Source für Delphi 7, die EXE und ein paar Profile für fertige
Webpages.
CSS-DIV-Slicer.zip
Have fun!