>binaervarianz >projekte >CocoaRay
Kapitel 2: Lästiges Betriebssystemgewäsch

Was auch immer man programmiert, am Anfang steht immer der lästige Kampf mit den Betriebssystembibliotheken, bis man endlich seine Ausgabe in einem hübschen Fenster sieht. Das gilt insbesondere, wenn man kein Kommandozeilentool, sondern ein Grafikprogramm schreibt.

Da haben wir ja noch mal Glück, dass es in unserem Falle wenigstens Mac OS X mit dem Cocoa-Framework ist.

Als erstes starten wir also Xcode mit einem neuen ObjC-Cocoa-Projekt. Die MainMenu.nib öffnen wir mit dem InterfaceBuilder.
Dort erstellen wir uns eine paar neue Subklassen. Eine NSView Subklasse (bei mir BIRenderView) wird zum Anzeigen unserer Ergebnisse dienen. Von NSObject leiten wir uns eine BIRenderer und eine BIControls ab. Erstere wird unseren Renderalgorithmus beinhalten, zweitere dient als Controller und nimmt alle Anfragen der GUI entgegen.

InterfaceBuilder

Wir ziehen uns nun unser Fenster zurecht und platzieren einen CustomView aus der Palette darauf. Diesen stellen wir auf die feste Größe von 360x240 Pixeln ein. (Wie ich auf die Größe komme? Nun ich hatte 320x240 im Sinn und hab mich vertippt. Aber das Ergebnis passte so schön zum Breitbildschirm, dass ich es so gelassen habe.) Anschließend wählen wir in den Eigenschaften des NSView unter Custom Class unsere eigene Subklasse aus. (Weitere Elemente habe ich mir erst einmal gespart. Wer schon ein bisschen vorarbeiten will, kann ja mal in die weiteren Kapitel schielen).

Anschließend kann für alle erstellten Klassen der Quelltext erzeugt werden. Damit wäre die Arbeit im InterfaceBuilder für's Erste getan.

In Xcode sollten nun 6 neue Dateien für 3 neue Klassen erschienen sein, die wir in den Ordner Classes verschieben. BIControls können wir ignorieren, da hier noch nichts passiert. Wenden wir uns erst mal dem BIRenderView zu. Dieser erbt von NSView unter anderem zwei Methoden, die wir überschreiben müssen:

- (id)initWithFrame:(NSRect)frameRect
initialisiert das Objekt nach der Erzeugung durch InterfaceBuilder. Hier müssen alle Variablen initialisiert werden.
- (void)drawRect:(NSRect)rect
wird regelmässig vom System aufgerufen, um den View neu zu zeichnen. Hier muss also der Aufruf an unseren Renderalgorithmus eingefügt werden. Leider reicht es nicht, das zu zeichnende bereitzustellen oder zu übergeben. Wir müssen es aktiv selber zeichnen.

Was das bedeutet und wie ich das anstelle, hat mich die erste Woche Entwicklungszeit gekostet. Es gibt viele Funktionen, die wirklich zeichnen, wir wollen jedoch eigentlich nur ein fertig gerendertes Bild darstellen. Dazu müssen wir es in ein Cocoa Objekt verpacken, welches wir dann anweisen können, sich selbst zu zeichnen. Das Objekt der Wahl ist vom Typ NSImage, welches wir in der Headerdatei deklarieren:

@interface BIRenderView : NSView
{
	BIRenderer* renderer;	
	NSImage* image;	
}

Wie man sieht, deklarieren wir bei der Gelegenheit gleich noch eine Instanz unseres Renderers. Beide müssen wir natürlich initialisieren, und zwar in der erwähnten Funktion in der Hauptdatei (überschriebene Funktionen müssen im Header nicht erwähnt werden):


- (id)initWithFrame:(NSRect)frameRect{
	if ((self = [super initWithFrame:frameRect]) != nil) {
		renderer = [[[BIRenderer alloc] init] initWithRect:frameRect];
		image= [[NSImage alloc] init];		
	}
	return self;
}

Die alloc und init sind Standardmethoden, die jedes Objekt versteht, initWithRect müssen wir in unserem Renderer nachher selbst definieren. Es gibt das auszufüllende Rechteck weiter, das auch der View zur Initialisierung erhält.

Die Zeichenfunktion gestaltet sich hier noch recht einfach. Zunächst sagen wir dem Renderer, dass er ein neues Bild erzeugen soll. Anschliessend müssen wir dieses in das NSImage packen und dieses zeichnen lassen. Die letzten beiden Aufgaben geschehen in nur einem Befehl, leider können wir in das Image nicht direkt unsere Rohdaten packen. Ein weiteres Cocoa-Objekt muss hier zwischengeschaltet werden, nämlich ein NSBitmapImageRef. Dieses wird auf Anfrage von unserem Renderer erzeugt und direkt dem Image zum Zeichnen übergeben:


- (void)drawRect:(NSRect)rect{	
	[renderer drawImage];	
	[image drawRepresentation:[renderer imgRep] inRect:rect];
}

Das wär's schon im BIRenderView. Welche Funktionen des Renderers haben wir bis jetzt schon benutzt? Natürlich -(void) drawImage, zum Rendern des Bildes. Das müssen wir zwar deklarieren, aber können es zunächst leer lassen. Dann die -(NSBitmapImageRep*) imgRep Methode, um die gerenderten Daten zu erhalten und die -(id) initWithFrame:(NSRect)frameRect Initialisierungsmethode. Alle drei schreiben wir in den Header, zusammen mit einem NSBitmapImageRep.


@interface BIRenderer : NSObject
{
	NSBitmapImageRep* image;
}

- (id)initWithRect:(NSRect)rect;
- (NSBitmapImageRep*)imgRep;

Wie implementieren wir diese Methoden? Nun, imgRep gibt einfach das eben deklarierte image Objekt zurück. Damit dieser Zeiger nicht ins Leere zeigt, muss er initialisiert werden. Das Bild ist natürlich leer, aber das Programm sollte so kompilieren und ausführbar sein.


-(id)initWithRect:(NSRect)rect{
	if(self=[self init]){		
		image = [NSBitmapImageRep alloc];
	}
	return self;
}

- (NSBitmapImageRep*) imgRep {				
	return image;
}

So, geschafft! Das war der langweiligste Teil der ganzen Arbeit. Ab jetzt gibt's optisches Feedback und wir können Prototypen kompilieren. Bravo!

>binaervarianz >projekte >CocoaRay