CSS-DIV-Slicer

CSS-DIV-Slicer-Tutorial von Daniel Schwamm (13.03.2010 - 20.03.2010)

Inhalt

1. Mode-Erscheinungen beim Web-Design

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.

1.1. TABLEs sind klasse ...

Tatsache ist, TABLEs sind logisch, einfach und sehr individuell gestaltbar. Eine halbe Stunde intensiv gelernt, dann hat man die Dinger drauf.

1.2. ... aber DIVs sind hip

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.

CSS-DIV-Slicer - Der CSS-DIV-Slicer

1.3. Höhen-dynamische Page mit DIVs

1.3.1. Klassisches Drei-Spalten-Design

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!

CSS-DIV-Slicer - Klassischer Web-Aufbau

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.

1.3.2. "Totale" Dynamik ist nicht drin

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.

1.3.3. HTML-Part

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 ...

1.3.4. CSS-Part

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;
}
1.3.4.1. Die Breite ist fixiert ...

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.

1.3.4.2. ... aber die Höhe passt sich an

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:

CSS-DIV-Slicer - Höhen-dynamische Webseite mit DIVs

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.

1.3.4.3. Hä? Warum passt sich die Höhe an den Inhalt an?

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.

CSS-DIV-Slicer - Wenn Daniel ein Webbrowser wäre

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.

1.4. CSS sind primitiv - sie kennen weder Variablen noch Mathematik

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 ...

00001
var tab_with=600;

... 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.

2. Der CSS-DIV-Slicer

2.1. Was der Boss will, soll der Boss bekommen

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:

CSS-DIV-Slicer - www.uwe-melzer.de - die Homepage vom Boss

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.

2.2. Der CSS-DIV-Slicer - yet an other PHP-Webpage-Generator?

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.

2.2.1. PHP kann alles ...

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.

2.2.2. ... der Schwamm aber nicht

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?

2.3. Der CSS-DIV-Slicer - yet an other Delphi-Webpage-Generator

2.3.1. Übersicht

Der folgende, mit Delphi 7 zusammengeschusterte "CSS-DIV-Slicer" vermag:

  1. Einteilung einer beliebigen Grafik in neun "Slices"
  2. Ausstattung der "Slices" mit Hintergrundfarben
  3. Modifizierung der Hintergrund-Grafik eines "Slice" über die CSS-Repeat-Eigenschaft
  4. Generierung von an die Content-Höhe angepassten HTML- und CSS-Code

CSS-DIV-Slicer - CSS-DIV-Slicer Profile

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 - CSS-DIV-Slicer Webpage

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.

2.3.2. Beschränkung auf das Wesentliche

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.

2.3.3. Interessant & knifflig: Bild einladen und an PaintBox anpassen

Ü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.

CSS-DIV-Slicer - Volle Breite ausgenutzt bei PaintBox

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.

CSS-DIV-Slicer - Volle Höhe ausgenutzt bei PaintBox

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:

CSS-DIV-Slicer - Vom img zu pb_img_bmp

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.

2.3.4. Schnittmarken setzen - 9 x Slicen

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.

2.3.4.1. Minima und Maxima der ScrollBars

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.

2.3.4.2. Slices malen

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.

CSS-DIV-Slicer - Einzeichnen der Slice-Linien

Einzeichnen der Slice-Linien: Vier Slice-Linien, die über ScrollBars eingestellt werden, unterteilen das PaintBox-Bild in insgesamt neun "Slices".

2.3.4.3. Einen "Slice" selektieren

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;

CSS-DIV-Slicer - Selektion eines Slices

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.

2.3.5. "Slice-Cuts" und die "Background-Repeat"-Eigenschaft

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:

CSS-DIV-Slicer - Slices ohne Cuts bei einem Torbogen

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:

CSS-DIV-Slicer - Webseite mit Slices ohne Cuts bei einem Torbogen

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.

CSS-DIV-Slicer - Slices mit Cuts bei einem Torbogen

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.

CSS-DIV-Slicer - Webseite mit Slices mit Cuts bei einem Torbogen

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").

2.3.5.1. "Slice-Cuts" erstellen

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.

CSS-DIV-Slicer - Freestyle-Slice-Cut

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.

2.3.5.2. "Slice-Cuts" auf die PaintBox malen

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.

CSS-DIV-Slicer - Transparente Slices ohne Slice

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.

CSS-DIV-Slicer - Slices und Cuts gemixt

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.

2.3.6. Profil-Verwaltung

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.

2.3.6.1. Neues Profil anlegen

"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;
2.3.6.2. Profil-Modifikationen feststellen

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.

2.3.6.3. Profil einlesen & speichern

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;

2.3.7. CSS-Seite erstellen

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.

2.3.7.1. CSS-Generator

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;
2.3.7.2. CSS-Muster

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
}


[...]

2.3.8. HTML-Seite erstellen

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.

2.3.8.1. HTML-Generator

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;
2.3.8.2. HTML-Muster

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>

2.3.9. Page generieren mit "Slicing" bzw. "Slice-Cutting"

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.

3. Beispiele

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.

CSS-DIV-Slicer - CSS-DIV-Slicer Demo #1

CSS-DIV-Slicer - CSS-DIV-Slicer Demo #2

CSS-DIV-Slicer - CSS-DIV-Slicer Demo #3

4. Fazit

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?

5. Download

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!