Apple Enterprise - NeXTanswers Support Archive
Enterprise
[Index]
[Download]


Search NeXTanswers for:

NEXTSTEP Interface Objects Q&A



Creation Date: Jul 22, 1998
Keywords:
NeXTSTEP

Disclaimer

This document pertains to the NeXTSTEP operating system, which is no longer a supported product of Apple Computer. This information is provided only as a convenience to our customers who have not yet upgraded their systems, and may not apply to OPENSTEP, WebObjects, or any other product of Apple Enterprise Software. Some questions in this Q&A document may not apply to version 3.3 or to any given specific version of NeXTSTEP.

Overview

This document contains development questions relating to windows, views, and other interface objects under NeXTSTEP 3.3 and/or previous versions.

Questions

Q:
I would like to convert a point returned in an NXEvent from its base coordinates to a view coordinate system. There are several routines to convert coordinates between views and between the screen and base coordinate systems, but I have not found one to convert a base coordinate to a view's coordinate system. How do I do this?

A: Calling the following method with aView == nil converts the point from the base coordinate system of the window to the view's coordinate system:

- convertPoint:(NXPoint *)aPoint fromView:aView;

Q: I have a button which, when clicked, removes a View from a Window. However the button that does this is a subview of the View that is removed. This works except I get the following message on the console:

Assertion failed: You removed a View from the View hierarchy that had been lockFocus'ed

Is this a problem? Can I ignore the message?

A: Typically the action method for a control is called while in the mouseDown for that control. At this point the AppKit has performed a lockFocus on that control (while it is highlighted) and it will not remove the lockFocus until the action method has completed (and the highlight is turned off). For this reason it is not a good idea to be modifying the view hierarchy of any superviews from within the action method of a control. If it is not possible to restructure the code so that the hierarchy is modified outside of the action method, then the next best approach is to perform the action with a slight delay. The code might look like:

[self perform:@selector(removeView:) with:sender afterDelay:1 cancelPrevious:YES];


Q:
How can I make connections in InterfaceBuilder between objects in different nib files, or in the same one loaded multiple times?

A: You can't. You need to make the association in your code. The object that is the nib file's owner should take care of communicating with any other objects (including the owners of other nib files). For example, the file's owner can have methods that return instance variables referring to any objects from the nib file that you want other objects to be able to talk to. (You must have created outlets in Interface Builder for these objects, in order for the objects to be assigned to instance variables.) The file's owner can also set the delegate of an object it owns to be some external object.

It's conceptually impossible to make the connections in Interface Builder, because there's no way to know which instance of an object from one nib file is connected to an instance of some object from another nib file. Each nib file is loaded at a different time during the program execution, and any nib file can get loaded multiple times. Only your code "knows" when the various objects get created--namely, when you call loadNibSection:owner:. Remember that nib files are just templates; the objects are unarchived when you load the nib file. When you make connections in InterfaceBuilder, you're really just making the template of these connections. Only when the nib file is loaded do the objects and their interconnections get instantiated, so to speak. If the other nib file hasn't been loaded yet, you can't connect to its objects, since they don't exist in your program yet. That's why connections are limited to nib files. It's also one reason every nib file must have an owner: so that objects from that nib file can communicate with other objects in your program.

The same logic applies to loading the same nib file multiple times. A typical use of loading the same nib file multiple times is in applications that have the notion of a document. The document is described by a nib file that gets loaded each time you want a new document (usually when the user clicks on the "New" menu). There should generally be a new instance of the owner of the nib file for each time you want to load the same nib file. The only way to communicate between the objects in different documents is through their owners.

One example of multiply loaded nib files is /NextDeveloper/Examples/SoundAndMusic/SoundKit/SoundEditor. In this application, SoundController is the main object that loads in the application's main nib file. It also is responsible for creating SoundDocuments, each of which loads in its own nib data. In other words, a new instance of the SoundDocument class is the file's owner for each "instance" of SoundDocument.nib--or more precisely, for the objects collectively unarchived each time

[NXApp loadNibSection:SoundDocument.nib owner:self];

is called in SoundDocument's init method. Although it wasn't necessary, there could have been methods in SoundDocument to return the objects "inside" each SoundDocument's nib file. For example, SoundDocument could have implemented a method called -soundView that returned the variable mySoundView, which is an InterfaceBuilder outlet. This way any other objects could have talked to the SoundView, by messaging SoundController to get the current document (with the document method), and then messaging the current document to get its SoundView (with the soundView method).


Q: How do I programmatically create a window 80 characters wide for any given font?

A: The following code snippet works for a fixed width font such as Ohlfs or Courier. A non-fixed width font can only be approximated by choosing either an "average-width" character, or the widest character in the font.

#import <math.h>
#import <appkit/Window.h>
#import <appkit/Font.h>

float maxcharwidth, screenwidth;
id screenFont, myWindow;
NXCoord leftMargin, rightMargin, topMargin, bottomMargin;
NXRect aRect;

[myText getMarginLeft: &leftMargin
right: &rightMargin
top: &topMargin
bottom: &bottomMargin];
screenFont = [myFont screenFont];

maxcharwidth = MAX([myFont getWidthOf: "0"],
[screenFont getWidthOf: "0"]);
screenwidth = ceil(maxcharwidth * 80.0) + leftMargin + rightMargin +1;

/* Now use that width when creating the window */
NXSetRect(&aRect, 0.0, 0.0, screenwidth, height);
#ifdef 1.0
myWindow = [Window newContent: &aRect
style: NX_TITLEDSTYLE
backing: NX_BUFFERED
buttonMask: NX_MINIATURIZEBUTTONMASK
defer: NO];
#else /* 2.0 or 3.0 */
myWindow = [[Window alloc] initContent: &aRect
style: NX_TITLEDSTYLE
backing: NX_BUFFERED
buttonMask: NX_MINIATURIZEBUTTONMASK
defer: NO];
#endif

Note that the ifdef for 1.0 is for illustration purposes only and is NOT defined in any NEXTSTEP include file.

The key is to get the width of any character (since all have the same width) of both the screen font and the printer font. Take the maximum width and do a ceil of the calculation, since the printer width may be fractional.

Note that a fudge factor of +1 is added to the calculation. This is a known bug.

Note also that this window does not contain a vertical scrollbar. If it did, the width of the scrollbar would need to be accounted for.


Q: How can my program tell whether it is running on a system with a color screen?

A: The PostScript graphics model allows you to do all your rendering in 24 bit color, and have the WindowServer display it in the best way possible (dithering down to 2 bits, if necessary). If there are situations in which you need to use the "inquire and adapt" approach, the AppKit provides you with a method in the Application class called colorScreen that returns the most colorful screen available to your application. By examining this data structure, you can determine how deep your color screen is. Below is a code fragment that does this:

#import <appkit/Application.h>
#import <appkit/screens.h>
...
{
const NXScreen *deepestScreen;

deepestScreen=[NXApp colorScreen];

switch ( deepestScreen->depth ) {

case NX_TwelveBitRGBDepth :
/* Twelve Bit Color */
break;

case NX_TwentyFourBitRGBDepth :
/* True Color */
break;

case NX_TwoBitGrayDepth :
default :
/* Greyscale */
break;

}
}


Q: How can I tell whether a View can display color or not?

A: The previous question does not answer which kinds of rendering commands you should be sending for a given View. There are two headed systems, and other circumstances where the window you are drawing in is not based on the value of colorScreen. The most common thing you want to do is different rendering based on whether this View can display color or not. One way to do this is to use the shouldDrawColor method of View. Do the following in your drawSelf:: method for the View:

- drawSelf:(NXRect *)rects :(int)count
{
if ([self shouldDrawColor]) {
... color-specific drawing ...
} else {
...
}
...
}

If you need to know the actual depth of a given window, use the depthLimit method of Window. Here's some code for that:

NXWindowDepthLimit depth;
if ((depth = [myWindow depthLimit]) == NX_DefaultDepth) {
depth = [Window defaultDepthLimit];
}

Q: How do I display a new window I created programmatically?

A: If you allocate a window and initialize it with the init method, that window, by default, has display disabled. When display is disabled, the window appears on-screen without a title and its contentView is white. You must send it a display message to get things going (i.e., so that it draws itself properly). Simply sending it orderFront: is not sufficient.

If your window is allocated and initialized with the initContent:style:backing:buttonMask:defer: method, display is disabled only if the defer flag is set to NO. If it's YES, the window looks fine when it gets the orderFront: message.

Q: How do I make a window resize from a corner other than the upper right?

Q: I have a view that I want to resize. However, what I really want is for the window to be resized such that my view becomes the given size using the autosizing attributes that I have set in my window. How do I do that?

A: The following window method, sizeWindow:byCorner:, resizes a window to the given size by moving the given corner. The view method below it, sizeTo::byWindowCorner:, resizes a view by growing its window such that the view becomes the given size with respect to its autosizing settings. The two methods are implemented as categories to Window and View respectively.

// Format is 80 columns with 4 space tabs.
@interface Window(Sizing)
- sizeWindow:(NXCoord)width :(NXCoord)height byCorner:(int)corner
@end

@implementation Window(Sizing)

#define CORNER_UPPER_LEFT 0
#define CORNER_LOWER_LEFT 1
#define CORNER_UPPER_RIGHT 2
#define CORNER_LOWER_RIGHT 3

// Keep 'a' between x and y
#define CLAMP(a,x,y) (MAX((x), MIN((y), (a))))

/******************************************************************************
This Method resizes the receiving window as if it was dragged with the given corner. This method is useful when you want the window to resize by a corner other that the default upper right.
******************************************************************************/
- sizeWindow:(NXCoord)width :(NXCoord)height byCorner:(int)corner
{
NXRect newFrame;
NXSize minSize, maxSize;

// Clamp width and height to their respective minimum and maximum values
[self getMinSize:&minSize]; [self getMaxSize:&maxSize];
width = CLAMP(width, minSize.width, maxSize.width);
height = CLAMP(height, minSize.height, maxSize.height);

// Set newFrame from the old frame and the new sizes
NXSetRect(&newFrame, NX_X(&frame), NX_Y(&frame), width, height);

// Move the respective corner by the amount of growth and set newFrame
switch(corner) {
case CORNER_UPPER_LEFT:
NX_X(&newFrame) -= width - NX_WIDTH(&frame);
break;
case CORNER_LOWER_LEFT:
NX_X(&newFrame) -= width - NX_WIDTH(&frame);
NX_Y(&newFrame) -= height - NX_HEIGHT(&frame);
break;
case CORNER_UPPER_RIGHT:
break;
case CORNER_LOWER_RIGHT:
NX_Y(&newFrame) -= height - NX_HEIGHT(&frame);
break;
}
[self placeWindowAndDisplay:&newFrame];
return self;
}
@end


@interface View(Sizing)
- sizeTo:(NXCoord)width :(NXCoord)height byWindowCorner:(int)corner;
@end

@implementation View(Sizing)

/******************************************************************************
This Method resizes the receiving view to the given width and height by resizing the window by the appropriate amount with respect to autosizing. This method is useful for those occasions when you know what size a view should be, but don't know how big to make the window to hold it. If you ask for a new width, it assumes the view is width sizable. The same goes for height. If the hierarchy contains a ClipView (ie, in a ScrollView) it assumes that you want the ClipView's subview to be fully exposed. The window size will not exceed the set maximum.
******************************************************************************/
- sizeTo:(NXCoord)width :(NXCoord)height byWindowCorner:(int)corner
{
int autosizing = [self autosizing];
float widthGrowth = width - NX_WIDTH(&bounds);
float heightGrowth = height - NX_HEIGHT(&bounds);
float stretchingWidth = NX_WIDTH(&bounds);
float stretchingHeight = NX_HEIGHT(&bounds);
float newSuperWidth, newSuperHeight;
NXRect superFrame;

// If we are a contentView we simply need to grow window by our growth
if(self==[window contentView]) {
[window getFrame:&superFrame];
[window sizeWindow:NX_WIDTH(&superFrame) + widthGrowth
:NX_HEIGHT(&superFrame) + heightGrowth byCorner:corner];
}
else {
[superview getFrame:&superFrame];

// Add margins to stretching lengths if they have been turned on in IB
if(autosizing & NX_MINXMARGINSIZABLE) stretchingWidth += NX_X(&frame);
if(autosizing & NX_MAXXMARGINSIZABLE)
stretchingWidth += NX_WIDTH(&superFrame) - NX_MAXX(&frame);
if(autosizing & NX_MINYMARGINSIZABLE)
stretchingHeight += NX_Y(&frame);
if(autosizing & NX_MAXYMARGINSIZABLE)
stretchingHeight += NX_HEIGHT(&superFrame) - NX_MAXY(&frame);

// Add growth times a ratio of stetching length::view length to Super
newSuperWidth = NX_WIDTH(&superFrame) +
widthGrowth*stretchingWidth/NX_WIDTH(&bounds);
newSuperHeight = NX_HEIGHT(&superFrame) +
heightGrowth*stretchingHeight/NX_HEIGHT(&bounds);

// Resize the Superview
[superview sizeTo:newSuperWidth :newSuperHeight
byWindowCorner:corner];

// If we are a ClipView, bring the docview up to size
if([self isKindOf:[ClipView class]])
[[(ClipView *)self docView] sizeTo:width :height];
}
return self;
}


Q:
How can I make a non-rectangular Window?

A: You cannot at present make a non-rectangular Window. We hear the request. Tell us how important is this ability compared with your other needs. The WhatADrag example on the 2.0 release (/NextDeveloper/Examples/WhatADrag) shows you how to create windows with transparency for dragging arbitrary shapes on the screen between windows. This does not produce true transparent windows, but it accomplishes most of what developers need transparent windows for.

Q: How can I make a non-rectangular View?

A: You can clip a View to a non-rectangular shape by creating a subclass of View, and overriding the following View method to establish your own clipping path.

- clipToFrame:(const NXRect *) frameRect
/*
* TYPE: Focusing and displaying; Clips to frame during focusing
*
* This method is provided to allow for your View to do arbitrary clipping
* during focusing. This method is called from within the focusing
* machinery if clipping is required. The default implementation just
* calls PSrectclip with the values in frameRect. You must use frameRect
* rather than just looking at your View's frame instance variable,
* because due to focusing, the origins may not be the same.
*/
{
PSrectclip(NX_X(frameRect), NX_Y(frameRect),
NX_WIDTH(frameRect), NX_HEIGHT(frameRect));
return self;
}


In addition, the 3.0 documentation on clipToFrame: contains an example of clipping the View to a circular region.

Q:
I am using the Windows submenu. For each new window document that I create in my application, a new menu cell is added to the submenu with the window's title and a small X at the left-most edge of the menu cell. How do I programmatically control this X to make it broken when the document associated with the menu cell has been changed?

A: The window method setDocEdited: (BOOL)flag is responsible for displaying the broken X when it is set to YES. This method controls both the X in the menu cell, and the X on the upper right corner of the window. It has to be reset to NO before saving the document, so that the X reappears as non-broken. See the code snippet below:


/* In the case of a text object for example,this text delegate method is responsible for notifying that the document has changed. Please note that ruler and font changes as well as changes caused by spell checking the Text are not reflected by this text delegate method.
*/

- textDidChange: sender
{
[currentDocWindow setDocEdited:YES];
return self;
}




/* Remember to reset setDocEdited to NO before saving your file, so that the X shows as a regular X when reading the file back in.
*/

- saveFilename:(const char *)filename
{
[currentDocWindow setDocEdited:NO];

/* To generate a textDidChange message */
[currentDocWindow makeFirstResponder:currentDocWindow];
........
return self;
}


/* After setting setDocEdited to YES, the state of isDocEdited is YES. You can query this state and, if appropriate, display an alert panel before the window closes. Note the use of NXStringTable here for ease of localization. Please refer to our user interface guidelines and release notes for more details.

*/

#define SAVE NX_ALERTDEFAULT
#define CLOSE NX_ALERTALTERNATE
#define CANCEL NX_ALERTOTHER

- windowWillClose:sender
{
int result;

if ([currentDocWindow isDocEdited]) {
if (!stringTable)
stringTable = [[NXApp delegate] stringTable];
result = NXRunAlertPanel([NXApp appName],
[stringTable valueForStringKey:
"The document %s has unsaved changes.\nClose anyway?"],
[stringTable valueForStringKey:"Save"],
[stringTable valueForStringKey:"Close"],
[stringTable valueForStringKey:"Cancel"],
[currentDocWindow title]);
switch(result) {
case SAVE:
...
break;
case CLOSE:
break;
case CANCEL:
return nil;
}
}

......

return self;
}

Q: How do I disable/enable menu cells?

A: The method setUpdateAction:(SEL)aSelector forMenu:aMenu is responsible for the menu cell update, where aSelector is your own update method, and aMenu the menu you want to update. This method has to be called inside appDidInit: because NXApp is responsible for the update. Doing so makes your program more efficient, since it needs to be done only once.

Q: Should I use the application method setAutoupdate:(BOOL)flag or the menu update method for updating the menu items? What are the trade-offs?

A: The application method setAutoupdate:YES causes the menus to stay up to date all the time (i.e., it only updates them when they are on-screen). Calling update directly every time something changes might be a lot of extra work for nothing. However, if things rarely change in the menu, it might be worth not incurring the window list traversal setAutoupdate: causes.


Example (adapted from /NextDeveloper/Examples/Draw)

static void initMenu(id menu)
/* A private C function:
* Sets the updateAction for every menu item which sends to the
* First Responder (i.e. their target is nil).
* Returns the active menu if is found in this menu.
*/
{
int count;
id matrix, cell;
id matrixTarget, cellTarget;

matrix = [menu itemList];
matrixTarget = [matrix target];

count = [matrix cellCount];
while (count--) {
cell = [matrix cellAt:count :0];
cellTarget = [cell target];
if (!matrixTarget && !cellTarget) {
[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
} else if ([cell hasSubmenu]) {
initMenu(cellTarget);
}
}
}


/* Initializes the updateAction method for the menu cells.
setAutoupdate:YES means that updateWindows will be called
after every event is processed (to keep the menu items up to date).
*/
- appDidInit:sender
{
........
initMenu([NXApp mainMenu]);
[NXApp setAutoupdate:YES];
return self;
}

#define TOGGLE 4 /* Some integer cell tag value */

- (BOOL)menuItemUpdate:menuCell
/* Here goes your own code for menu cell update. This is only a simple example
which toggles the state of the selected cell. The return value YES means
that the menu will be updated.
*/
{

BOOL cellState;


switch ([menuCell tag])
{
case TOGGLE: /* toggle menu cell state */
cellState = [menuCell isEnabled];
[menuCell setEnabled:!cellState];
return YES;
default:
break;
}

return NO;
}

Q: How do I find the id of a menu?

A: To obtain the menu id of the main menu use this:

[NXApp mainMenu]

There is no direct way to find the id of other menus unless you know the menu cell which corresponds to the menu in question. Then you can use the ActionCell method target which returns the target of the cell (i.e., its menu id). This method can be used to find any menu id. It is especially useful for finding the id of a submenu.

Example:

myMenuId = [myMenuCell target];

Q: How can I remove a menu cell from a menu? I can disable, enable, add, sizeToFit, update, and free, but I cannot get the actual cell to disappear from the submenu.

A: You need to remove the cell from the matrix maintained by the menu, and then have the menu resize to fit its new cells. Here is a code snippet:

- removeMenuCell:(int)cellNum from:theMenu
{
id matrix;

[theMenu disableFlushWindow];
matrix = [theMenu itemList];
[matrix removeRowAt:cellNum andFree:YES]; // remove cell
[theMenu sizeToFit]; // adjust menu to new size
[[theMenu reenableFlushWindow] flushWindow];
return self;
}

You can disable window flushing before removing the cell to avoid screen flicker, but be sure to reenable it once you're done. The above method is used if you know the number of the cell you want to remove. If you only have the menu cell's title, you can do this:

- removeMenuCellName:(const char*)cellTitle from:theMenu
{
int i, count;
id matrix, cells;
id cell;
const char* title;

[theMenu disableFlushWindow];
matrix = [theMenu itemList];
cells = [matrix cellList]; // get the List of cells
count = [cells count];
for(i = 0; i < count; i++)
{
cell = [cells objectAt:i];
title = [cell title];
if(title && !strcmp(title, cellTitle))
{ // compare to find correct title
[matrix removeRowAt:i andFree:YES];
break;
}
}
[theMenu sizeToFit]; // resize menu
[[theMenu reenableFlushWindow] flushWindow];
return self;
}

Q:
Why does my View lose its TIFF image after I make a call to setFlipped? Here is what I did, and the results I got.

Description:

1. Create a viewScroller
2. Create an imageView
3. [viewScroller setDocView: imageView];
4. [imageView setFlipped:YES];
5. load tiff file into imageView
6. [imageView display];

Results:

1. Without the setFlipped: call, my TIFF image shows up fine, with the scrollers sized respective to the imageView.
2. With the setFlipped: call, my TIFF image DOES NOT show up, BUT the scrollers are sized respective to the imageView AND the y-axis scrollbar shows at the top (like it should be).

A: If you composite your TIFF image at the point (0,0), after flipping the view, you won't see anything because the image is in fact now off-screen, i.e. showing at the top left corner (imagine the image height going beyond the y axis--growing upward). However, if you composite at the point (0, height of view rectangle), the image is shown at the lower left corner. Now, if you composite at the point (0, height of the image), the image is shown as hanging from the top left corner.

Q: I created a pop-up list by dragging it from the InterfaceBuilder palette into a window, but it doesn't seem to be freed when the window is freed.

A: This is a bug. Freeing a window does free all of its views, but the pop-up list is not a member of the view hierarchy! It's attached as the target of a Button. There are two situations in which this bug can bite an unwary programmer:

1) An application creates multiple document windows by loading a nib section multiple times. In the nib section, there's a window containing a pop-up list. The application leaks a pop-up list instance and a DPS window each time a document is closed. Furthermore, MallocDebug's "Leaks" button won't find the missing nodes because a pop-up list is a window, and there are pointers to the missing pop-up lists in [NXApp windowList].

2) Same situation as in (1), except that each time, the nib section is loaded into a unique zone, and the zones are destroyed when the documents are closed. Destroying the zone gets rid of all the garbage, but now there are bad pointers in [NXApp windowList]. Some time later, the application crashes in code that you didn't write, referencing objects that you don't know about, leaving you very confused.

The solution is to free the pop-up list when you free the window. When setting up the nib file, make an outlet to the Button. In your code, use the outlet to ask the Button for its target before you free the window. The target is the pop-up list, which you then free explicitly.

Q: If I have a window delegate, which method should I use to close the window, the close: method or the performClose: method?

A: The performClose: method should be used instead of close: because it sends a windowWillClose: to its delegate, while the close: method doesn't. See the documentation in the reference manual about the window class for more information about these two methods.

Q: How do I set the current selection on a Popup?

A: In Release 2 and later, InterfaceBuilder has an object on the palette that presents a popup menu of choices. This is actually a button (when you ask InterfaceBuilder for the class of this object, sure enough, it's a button). It's a special button whose target is a PopUpList and action causes the popup menu to be displayed. There is no special message for setting the current selection of this popup, all you do is set the title to be the choice you want to set your popup to, as you would for an ordinary Button. So, if you have an outlet for a popup called "popUpButton" you can do this:

[popUpButton setTitle:"Item2"];

To get the number of the item currently selected, you have to send the indexOfItem: message to the PopUpList (which is the target of the popup Button). So, with the example above, you would use:

num=[[popUpButton target] indexOfItem:[popUpButton title]]

Q: In my application when my window becomes the key window or the main window, sometimes it doesn't display itself. What's going on?

A: There is a bug where display sometimes is disabled in a Window when the window becomes key and/or main. The result is that a window (or its delegate) tries to do some drawing in its becomeKeyWindow or becomeMainWindow (or windowDidBecomeKey: or windowDidBecomeMain:) method, and the drawing doesn't happen because some drawer observes that display is disabled.

Here's a workaround. You can do this little dance in either your delegate method or your override of Window's becomeKeyWindow or windowDidBecomeMain.

- windowDidBecomeMain:win
{
BOOL displayWasDisabledForActivation = [NXApp _isInvalid] &&
![win isDisplayEnabled];

if (displayWasDisabledForActivation)
[win reenableDisplay];

/* whatever you do now... */

if (displayWasDisabledForActivation)
[win disableDisplay];
return self;
}

The _isInvalid method is a private method, and as such is undocumented. Calling this ensures that you are in the case where this bug bites. We thought this might be prudent in case there was a different time when you really would like display to be disabled.

Valid for 2.0, 3.0


Q: I have a button which, when clicked, removes a View from a Window. However the button that does this is a subview of the View that is removed. This works except I get the following message on the console:

Assertion failed: You removed a View from the View hierarchy that had been lockFocus'ed

Is this a problem? Can I ignore the message?

A: Typically the action method for a control is called while in the mouseDown for that control. At this point the AppKit has performed a lockFocus on that control (while it is highlighted) and it will not remove the lockFocus until the action method has completed (and the highlight is turned off). For this reason it is not a good idea to be modifying the view hierarchy of any superviews from within the action method of a control. If it is not possible to restructure the code so that the hierarchy is modified outside of the action method, then the next best approach is to perform the action with a slight delay. The code might look like:

[self perform:@selector(removeView:) with:sender afterDelay:1 cancelPrevious:YES];

There is also an example of this method in the DrawApp class of the Draw example.

Valid for 1.0, 2.0, 3.0

Q: I am trying to draw something to my view and am getting the message:

Assertion failed: No REAL window during lockFocus on a view

I've checked and the window instance variable of the view is non-nil. Why am I getting this message?

A: A REAL window is a PostScript window. Your check for a non-nil window assures that you have a AppKit-side window, but it doesn't check to make sure there is an associated PostScript window. When you grab a window in InterfaceBuilder, it is deferred by default. When a window is deferred the associated PostScript window won't be created until you actually bring that window on screen. You cannot draw to a window which doesn't yet have a PostScript window. You can change the deferred status using the ``Deferred'' switch button on the InterfaceBuilder Window inspector or with the proper argument to the Window method (initContent:style:backing:buttonMask:defer:). If you're ever in doubt about a view's status, you can use the canDraw method which returns a boolean which indicates whether you are able to draw to that view.

Valid for 1.0, 2.0, 3.0


Q. The CompositeLab example in /NextDeveloper/Examples/AppKit sometimes hangs when I drag in non-standard image types for which filters need to be run. Typically it wakes up after a minute or so without having accepted the image; however, if I drag the same image in again, it accepts it without a problem.

A. During a drag session, an application should not make calls to Workspace or call methods and functions that would require contacting Workspace. Launching filter services, applications, obtaining icons for files, sliding images, and methods in the Workspace protocol fall in this category. Making such a request will cause the application to hang until either the request or the drag service times out; the results can be unpredictable and often both the request and the drag operation will fail.

In the case of CompositeLab, the application asks NXImage to open an file from the performDragOperation: method. If the image requires a filter and the filter needs to be launched, this request hangs CompositeLab. However, the same request succeeds the second time as the filter will have been launched by the Workspace after the first attempt times out.

The easiest workaround is to open the image in the concludeDragOperation: instead of the performDragOperation:. This latter method is invoked while the drag session is still in process, so requests to Workspace hang. The concludeDragOperation: method, on the other hand, is called after the drag session is terminated, so the application may contact Workspace during that time.

The following changes will make CompositeLab work correctly. Replace the existing performDragOperation: method with the following two methods. Note that although this new performDragOperation: doesn't perform the operation, it still validates the dragged data. This is important--the return value of this method determines whether Workspace causes the dragged file(s) to fly back to the source, indicating success or failure to the user.

- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
{
return ([self draggingUpdated:sender] == NX_DragOperationNone) ? NO : YES;
}

- concludeDragOperation:(id <NXDraggingInfo>)sender
{
Pasteboard *pboard = [sender draggingPasteboard];

if (includesType([pboard types], NXColorPboardType)) {
[self doColorDrag:sender];
[sourceColorWell setColor:sourceColor];
[destColorWell setColor:destColor];
[backColorWell setColor:backgroundColor];
} else {
(void)[self changeCustomImageTo:[[NXImage allocFromZone:[self zone]] initFromPasteboard:pboard]];
}
return self;
}

If an application wishes to actually open and display images during the drag session, it should ask NXImage if the image can be handled directly or requires a filter service, and open the image only if it falls into the former category. Of course, actually opening and displaying the image during the drag session isn't advisable in the first place as this operation might take a while.



Q: I'm trying to add a cell to a matrix or column of a browser that has already been loaded. I think I'm performing the right steps. I'm even explicitly calling a redisplay but the matrix never shows the new cell. What's going on?

A: When you add cells, the matrix needs to grow to a new size to accommodate them. Most likely the cells are being added, but without resizing the matrix, the new cells are being clipped from view. After adding cells, be sure to call sizeToCells (or sizeToFit depending on which you want) so the matrix will resize and you can see the new cells.

Here is code fragment that shows adding a cell to an already loaded column of a browser:


id matrix,cell;
int row;

matrix = [browser matrixInColumn:0];
[matrix getNumRows:&row numCols:NULL];
[matrix addRow];
cell = [matrix cellAt:row :0];
[cell setStringValue:"New Cell"];
[cell setLoaded:YES];
[cell setLeaf:YES];
[matrix sizeToCells];
<<--- Don't forget this!!


Valid for 1.0, 2.0, 3.0, 3.1


Q: How do I programmatically make a multiple selection in a matrix? I want it to behave exactly as though the user had shift-clicked to select several cells.

A: You must call setState: and highlight:inView:lit: for each cell that you want to select. Here is a code fragment which will select all the even-numbered cells and deselect all the odd-numbered cells in a matrix.

int rows;

[matrix getNumRows:&rows numCols:NULL];
[matrix lockFocus];
while (rows--) {
NXRect rect;
BOOL even = (((rows % 2) == 0) ? 1 : 0);
id cell = [matrix cellAt:rows:0];
[matrix getCellFrame:&rect at:rows :0];
[cell setState:even];
[cell highlight:&rect inView:matrix lit:even];
}
[matrix unlockFocus];
[[matrix window] flushWindow];


Valid for 1.0, 2.0, 3.0


Q: What is the difference between a Form and a FormCell? How do I make the correct connections in InterfaceBuilder?

A: A Form is a matrix of FormCells. Even a matrix with only one FormCell is a Form. If you have created a single FormCell with InterfaceBuilder and want to connect to the Form, carefully control-drag to the edge of the cell. When it is outlined in gray, you are connecting to the Form. Control-drag further into cell, and it is outlined in black; now you are connecting to the FormCell. Look at the "Comments" field in the InterfaceBuilder inspector while you are making your connection--it tells you what class the outlet is, for example, "Destination is of the Form Class." The same is true when you are connecting to a Form with more than one FormCell. Drag to the outside edge or in between cells; the form is outlined in gray and you are connecting to the form. Drag to a particular FormCell; it is outlined in black and you are connecting to that FormCell.

Note: In NEXTSTEP Release 3, the "Comments" field in the InterfaceBuilder Inspector has been replaced by a "Connections" field. The information displayed is self-explanatory: The left side shows the outlet name, while the right side shows the class name.

Valid for 1.0, 2.0, 3.0


Q: I am using the function NXGetNamedObject(const char * aname, id owner) to find the id of my NXBrowser object. My main program containing the loadNibSection:owner: was created by InterfaceBuilder and had NXApp in the owner: argument. However, passing NXApp as the owner always returns a nil id. What am I doing wrong?

A: By default, InterfaceBuilder assigns the Application object (NXApp) as the owner of a Window, and a View's Window as the owner of that View. Since NXBrowser is a subclass of View, you must use the NXBrowser's Window id instead of NXApp as owner in the function NXGetNamedObject().

Below is a code snippet for illustration:

id myWindow, myBrowser;

/* Get the id for the browser's window */
myWindow = NXGetNamedObject("MyWindow", NXApp);
/* Get the id of the browser itself */
myBrowser = NXGetNamedObject("MyBrowser", myWindow);


Note that named objects are a big performance loser because of the memory it takes to keep track of all the names of the objects. For this reason, we would recommend that you use loadNibSection:owner:withNames:NO when loading in nib files. Try to use outlets instead of named objects whenever possible.

Valid for 2.0, 3.0


Q: I have subclassed Button and ButtonCell in my application. I am setting the cell class for my button in the -init: method for my Button subclass like this:

@implementation MyButton : Button

-init
{
[super init];
[MyButton setCellClass: [MyButtonCell class]];
return self;
}
@end

However my cell class is not being created and initialized properly. What is going on?

A: setCellClass: is a factory method and must occur prior to any instance creation--init: is too late. The correct place to do this is in the +initialize factory method for your Button subclass (which is called before any instances are created). This problem can occur with all Cell subclasses--such as TextField and NXBrowser. (+initialize is an Object factory method; see the Object class specification sheet for more information.)

@implementation MyButton : Button

+initialize
{
[super initialize];
[MyButton setCellClass: [MyButtonCell class]];
return self;
}
@end

Valid for 2.0, 3.0


Q: I have a matrix of cells, each 10 pixels high. The matrix lives within a ScrollView, and ten cells are visible at any time. I set my lineScrolling to 10.0, and my pageScrolling to 100.0 (10.0 pixels * 10 cells); line scrolling works fine, but page scrolling (alt-clicking on a scroll arrow) doesn't do what I expect. Instead of scrolling the matrix up or down by ten cells, it only scrolls it by a few pixels (in fact, less than the lineScroll amount). What's happening?

A: The pageScroll value, to quote the documentation, is the ``amount in common before and after the page scroll.'' For example, if I set the pageScroll value to 40.0 in the above example, a page scroll will scroll away six of the ten cells currently visible. The 40.0 tells the ScrollView that you want 40.0 pixels of the previously visible contents to remain visible after the page scroll, which translates to 4 cells. With the pageScrolling set to 100.0 in the above example, the ScrollView thinks you want 100.0 pixels of the previously visible contents to appear after page scrolling. Since that's almost all the visible pixels, the matrix scrolls very little. Setting the pageScroll value to 0.0 will cause all ten cells to scroll out of view on a page scroll.

Valid for 1.0, 2.0, 3.0


Q: I would like to have an object in my custom palette do something different when it is unarchived in test mode in Interface Builder. How can I determine if I am in test mode?

A: You can use the isTestingInterface method which is defined in the IB (Interface Builder) protocol. See /NextLibrary/Documentation/NextDev/GeneralRef/08_InterfaceBuilder/Protocols/IB.rtf:

Protocol Description


Interface Builder's subclass of the Application class conforms to this protocol. Thus, objects in your custom palette can interact with Interface Builder's main module by sending messages (corresponding to the methods in this protocol) to NXApp.

isTestingInterface
- (BOOL)isTestingInterface

Returns YES if Interface Builder is in Test mode.

However, there is a bug with the isTestingInterface method in Release 3.1 and after. Even in test mode, [NXApp isTestingInterface] always yields NO during unarchiving (read:, awake and finishUnarchiving). One possible workaround is to delay the isTestingInterface message by calling perform:with:afterDelay:cancelPrevious:. Another workaround you can consider is illustrated in Jeff Martin's StringList MiniExample. You can take a look at the StringList MiniExample available via NeXTAnswers, document #1254. The workaround is implemented in the awake method for FilenameList. It is only valid in awake:

// test to see if mainMenu is visible, which it isn't while test
// interface mode is being set up.

if([NXApp conformsTo:@protocol(IB)])
if([[NXApp mainMenu] isVisible]) {
// do something special for test mode
return self;
}

Valid for 3.0, 3.1


Q: It seems like the scrollView ignores the frame.origin of its docView. Also, does a ScrollView clip to its update rect(s)?

A: A ScrollView translates the coordinate system of its contentView in response to user movement of the scrollers. Since the frame of the docView is defined in the coordinate system of the contentView (i.e. it's a subview of the contentView), translating the coordinate system of the contentView has the effect of moving the frame, and hence the visible portion of the docView.

When you do a setDocView: the system translates the coordinate system of the contentView so that the contentView's bounds.origin is the same as the frame.origin of the docView. For example: if the frame.origin of your docView is {100.0,100.0}, the bounds.origin of the contentView will be {100.0,100.0} as well.

So the ScrollView does pay attention to what the frame.origin of the docView is, but it doesn't really matter what it is, at least initially.

User code really shouldn't make changes to the frame of the docView as it is being managed by its superview (a ClipView). To achieve scrolling use the - rawScroll: method.

No, the drawing is not clipped to the update rects; however, you can restrict the area being redrawn yourself by using PSrectclip() or NXRectClip() in your drawSelf:: method.

Valid for 1.0, 2.0, 3.0


Q: In my application I have subclassed the Text object and called it SubText. I have a ScrollView which I installed using InterfaceBuilder which has a standard Text object as its docView. I wish to replace that Text object with my own but I wish to maintain all the same behaviors--with a vertical scrollbar and text which resizes properly. How do I do that?

A: The following code snippet creates an instance of your new subclass of Text and inserts it into the docView of the ScrollView. The old Text object is then freed. In the code sample ``theSV'' is an outlet connected to the ScrollView. Because an existing ScrollView is used, certain setups are not required. See Chapter 9, page 35 of the Concepts volume of the 1.0 Technical Documentation for more information on setting up a brand-new ScrollView.

The key here that many people miss is to set the min size and max size on the Text object.

id stdDoc;
NXRect r;

/* Measure the old doc view. */
stdDoc = [theSV docView];
[stdDoc getFrame:&r];

[theSV setVertScrollerRequired:YES];
[theSV setHorizScrollerRequired:NO];
[theSV setDynamicScrolling:YES];

/* Instantiate the new Text object */
#ifdef 1.0
newDoc = [SubText newFrame:&r];
#else /* 2.0 or 3.0 */
newDoc = [[SubText alloc] initFrame: &r];
#endif
[newDoc moveTo:0.0:0.0];
[newDoc notifyAncestorWhenFrameChanged: YES];
[newDoc setVertResizable:YES];
[newDoc setSelectable:YES];
[newDoc setEditable: YES];
[newDoc setAutosizing:NX_WIDTHSIZABLE];
[newDoc setMinSize:&r.size];
r.size.height = 1.0e30;
[newDoc setMaxSize:&r.size];
[theSV setDocView:newDoc];

/* Free the old Text object */
[stdDoc free];


Note that the ifdef for 1.0 is for illustration purposes only and is NOT defined in any NeXTstep include file.

Valid for 1.0, 2.0, 3.0




OpenStep | Alliances | Training | Tech Support | Where to Buy