Developing Visualization Applications with Cocoa and VTK
Pages: 1, 2, 3
Introduction to the Cocoa in VTK
VTK is a cross-platform framework and a good thing too. But this means that things are not necessarily done in the way a Cocoa programmer might expect. So before writing code specific to our molecular viewer, we will need a few general classes to adapt VTK more to the Cocoa way of doing things.
In order to achieve portability, VTK is based on a system of abstract
factories. Usually a VTK programmer
would call the "New" method of the VTK class vtkRenderWindow,
in order to create a new window to render in. A concrete subclass of
vtkRenderWindow, in our case
vtkCocoaRenderWindow, is created by New and returned; but the
programmer doesn't need to know which subclass is used: she just interacts
with the window via the methods of the abstract
vtkRenderWindow class. In this way the code can be made
portable. If the program is compiled and run on a different architecture,
the New method will simply instantiate a different concrete subclass, and
since all interaction with the window occurs via the methods of the
abstract vtkRenderWindow class, the code will work without
modification. Elegant, isn't it?
This is excellent object-oriented design and highly admirable, but we
want to be a bit less admirable here. We want to be able to do things in
the Cocoa way, creating a custom view in Interface Builder, dragging it
into an NSWindow, connecting outlets and setting actions, the way we know
and love. To achieve this, we have to change the implementation of a class
called vtkCocoaWindow; thanks to the dynamism of Objective-C,
we can do this without subclassing, simply by using a category.
|
|
But first I probably should explain what vtkCocoaWindow
is. vtkCocoaWindow is an Objective-C subclass of
NSWindow. When the "New" method of the abstract
vtkRenderWindow class is called, an instance of
vtkCocoaRenderWindow gets created. The vtkCocoaRenderWindow,
which is a C++ object, needs to be able to draw with the Cocoa framework,
so it creates a Cocoa object for this purpose:
vtkCocoaWindow. The vtkCocoaWindow has a single
NSOpenGLView in it, of the subclass
vtkCocoaGLView, and this is where VTK renders using OpenGL.
Phew, that was a mouthful. If this is a bit overwhelming, check out the VTK Cocoa Classes figure, which tells the same thousand words a little more concisely. And if that doesn't help, have a quick lie down and then proceed with the rest of the article, completely ignoring what you just read. If you don't care how it works, just that it does, you don't need to understand this explanation.
vtkCocoaWindow and vtkCocoaGLView are
designed to work in close collaboration. If vtkCocoaWindow
changes size, it resizes its vtkCocoaGLView as well. This is
the way VTK expects things to be done. But what we would prefer is to have
a single NSView subclass, which we can drop in any window in Interface
Builder, and have everything work. So we basically want to keep
vtkCocoaGLView and get rid of
vtkCocoaWindow. Unfortunately, vtkCocoaWindow
does some important stuff, so we can't get rid of it completely. But we
can hide it in the background. We will change the methods of
vtkCocoaWindow that don't fit with our aim to use
vtkCocoaGLView as a standalone class by creating a category.
Categorizing vtkCocoaWindow
Due to the dynamism of Objective-C, it is possible to actually change the implementation of a class without subclassing, even without having access to the source code. We do have the source code in this case, but it would be nice not to have to change the official source of VTK, just extend it a bit. For this purpose, we will use a category.
Begin by selecting "New File..." from the File menu, and give it the
name "vtkCocoaWindowModifications". Move the header and implementation
files created to the Classes group. Now include the following code in
vtkCocoaWindowModifications.h
#import <AppKit/AppKit.h>
#import "vtkCocoaWindow.h"
@interface vtkCocoaWindow (CocoaWindowModifications)
- (void)setvtkCocoaGLView:(vtkCocoaGLView *)thevtkCocoaGLView;
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize;
- (BOOL)windowShouldZoom:(NSWindow *)sender toFrame:(NSRect)newFrame;
@end
As you can see, this is a category of the vtkCocoaWindow
class called CocoaWindowModifications. Now add the
implementation for these methods to the
vtkCocoaWindowModifications.m file:
@implementation vtkCocoaWindow (CocoaWindowModifications)
// This has been overloaded to prevent the window making the vtkCocoaGLView its contentView.
- (void)setvtkCocoaGLView:(vtkCocoaGLView *)thevtkCocoaGLView {
myvtkCocoaGLView = thevtkCocoaGLView;
}
// This method, and the next one, have been changed to prevent them trying to
// to resize the render window.
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize {
return proposedFrameSize;
}
- (BOOL)windowShouldZoom:(NSWindow *)sender toFrame:(NSRect)newFrame {
return YES;
}
@end
As indicated by the in-code comments, the implementation of the
setvtkCocoaGLView: has been modified so that it does not
attempt to set the vtkCocoaGLView as its content view. We don't want this
here because our vtkCocoaGLView should behave like any other
NSView. We want to be able to use the view in any NSWindow, or as the
subview of any other NSView, and not be restricted to using it only as the
content view of a vtkCocoaWindow.
The other two methods have been modified so that they no longer send
messages to resize and position the vtkCocoaRenderWindow with
which the vtkCocoaWindow is associated. Our objective is to
transform the vtkCocoaWindow from a view object, which
appears on the screen as a UI element, into a controller object, which is
invisible to the user and whose role is simply to mediate between the
vtkCocoaRenderWindow and vtkCocoaGLView
objects. Because it is no longer a view object, we don't care about its
size, and we don't want it to send geometry-related messages to the
vtkCocoaRenderWindow.


