• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
validateUserInterfaceItem: and chaining actions
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

validateUserInterfaceItem: and chaining actions


  • Subject: validateUserInterfaceItem: and chaining actions
  • From: Jakob Olesen <email@hidden>
  • Date: Sun, 30 Jul 2006 13:57:38 +0200

I was reading Daniel Jalkut's blog (http://www.red-sweater.com/blog/ 161/the-case-of-the-missing-check) and stumbled on a quote from the "NSUserInterfaceValidations Protocol Reference":

http://developer.apple.com/documentation/Cocoa/Reference/ ApplicationKit/Protocols/NSUserInterfaceValidations_Protocol/ Reference/Reference.html

To validate a control, the application calls validateUserInterfaceItem: for each item in the responder chain, starting with the first responder. If no responder returns YES, the item is disabled.

This would suggest, as Daniel points out, that you should return NO for unknown actions in validateUserInterfaceItem. Unfortunately the quote is WRONG. If you follow the link to the companion guide, you find example code that implements the following algorithm: (rewritten for simplicity)


- (BOOL)validate
{
id validator = [NSApp targetForAction:[self action] to:[self target] from:self];
if ((validator == nil) || ![validator respondsToSelector:[self action]])
return NO;
if ([validator respondsToSelector:@selector (validateUserInterfaceItem:)])
return [validator validateUserInterfaceItem:self];
return YES;
}


That is:
1. Find the target for my action
2. Validate using that target and nobody else.

-[NSApp targetForAction:to:from:] walks the responder chain, but only calls -[respondsToSelector:]. It doesn't do validation.

If you read the documentation for NSMenu and NSToolbar, you will see that they do something similar. There are some complications with different key and main windows, and with application and window delegates, but the basic pattern is: Find the target, validate once.

This is the right approach. You should validate with the target that is going to receive the action, not some other random responder up the chain that happens to implement the same action, but might never see it.

It follows from the validator code that -[validateUserInterfaceItem:] is only called for implemented actions, and that a transparent - [validateUserInterfaceItem:] simply returns YES, i.e., implementing

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
{
    return YES;
}

has no effect at all. So for actions you don't recognize, simply return YES and pretend you're not there:

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
{
    if ([item action]==@selector(foo:))
        return [self canFoo];
    return YES;
}

But the quote above actually expresses an interesting idea: If I can't perform an action, maybe somebody up the responder chain can? This is similar to the way -[keyDown:] can be passed up the responder chain until somebody cares.

Here is a snippet from WebKit's WebFrameView:

- (void)scrollPageDown:(id)sender
{
if (![self _pageVertically:NO]) {
// If we were already at the bottom, tell the next responder to scroll if it can.
[[self nextResponder] tryToPerform:@selector (scrollPageDown:) with:sender];
}
}


That is, when the frame receiving the -[scrollPageDown:] action is already at the bottom, it passes the action up the responder chain, allowing an outer frame to scroll instead. Very useful. You can see the effect when scrolling embedded frames in Safari.

Now, WebKit doesn't actually validate -[scrollPageDown:], but what if you wanted to? If the validation algorithm was as described in the quote above, it would just work. But it doesn't work that way, and it shouldn't work that way. Not all actions are chained, and the validation algorithm can't know if it is.

Here is one way of doing it, using a category on NSResponder:

@implementation NSResponder(ChainedValidation)
- (BOOL)tryToValidateUserInterfaceItem: (id<NSValidatedUserInterfaceItem>)item
{
if (![self respondsToSelector:[item action]])
return [[self nextResponder] tryToValidateUserInterfaceItem:item];
if ([self respondsToSelector:@selector (validateUserInterfaceItem:)])
return [self validateUserInterfaceItem:item];
return YES;
}
@end


Then, for your chained actions:

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
{
if ([item action]==@selector(foo:))
return [self canFoo] || [[self nextResponder] tryToValidateUserInterfaceItem:item];
return YES;
}


You would have to create categories on NSWindow and NSApplication as well since they include their delegate in the responder chain. You may want to check if -[self nextResponder] is nil, if you're into that sort of thing. (It works as long as you trust that sending a message to nil returns nil, and that nil becomes NO when interpreted as a BOOL).

The tricky part is making this work with -[validateMenuItem:] and - [validateToolbarItem:]. These protocols fall back to - [validateUserInterfaceItem:], so the validation algorithm looks something like this:

- (BOOL)validate
{
id validator = [NSApp targetForAction:[self action] to:[self target] from:self];
if ((validator == nil) || ![validator respondsToSelector:[self action]])
return NO;
if ([validator respondsToSelector:@selector(validateMenuItem:)])
return [validator validateMenuItem:self];
if ([validator respondsToSelector:@selector (validateUserInterfaceItem:)])
return [validator validateUserInterfaceItem:self];
return YES;
}


Naïvely, we could extend the category like this:

@implementation NSResponder(ChainedValidation)
- (BOOL)tryToValidateMenuItem:(id<NSMenuItem>)item
{
if (![self respondsToSelector:[item action]])
return [[self nextResponder] tryToValidateMenuItem:item];
if ([self respondsToSelector:@selector(validateMenuItem:)])
return [self validateMenuItem:item];
if ([self respondsToSelector:@selector (validateUserInterfaceItem:)])
return [self validateUserInterfaceItem:item];
return YES;
}
@end


- (BOOL)validateMenuItem:(id<NSMenuItem>)item
{
if ([item action]==@selector(foo:))
return [self canFoo] || [[self nextResponder] tryToValidateMenuItem:item];
return YES;
}


This doesn't work perfectly, though. If a responder implements the action and -[validateUserInterfaceItem:] as above, but not - [validateMenuItem:], the chaining happens through - [tryToValidateUserInterfaceItem:], and -[validateMenuItem:] won't be called for the following responders.

Here is another approach:

@implementation NSResponder(ChainedValidation)
- (BOOL)tryToValidateUserInterfaceItem: (id<NSValidatedUserInterfaceItem>)item
{
if (![self respondsToSelector:[item action]])
return [[self nextResponder] tryToValidateUserInterfaceItem:item];
if ([item conformsToProtocol:@protocol(NSMenuItem)] && [self respondsToSelector:@selector(validateMenuItem:)])
return [self validateMenuItem:(id<NSMenuItem>)item];
if ([item isKindOfClass:[NSToolbarItem class]] && [self respondsToSelector:@selector(validateToolbarItem:)])
return [self validateToolbarItem:(NSToolbarItem*)item];
if ([self respondsToSelector:@selector (validateUserInterfaceItem:)])
return [self validateUserInterfaceItem:item];
return YES;
}
@end


This is dubious since calling -[validateUserInterfaceItem:] could cause -[validateMenuItem:] to be called up the responder chain, but it would probably do the right thing in most cases. The very odd case of an NSToolbarItem subclass adopting the NSMenuItem protocol is left as an exercise for the reader.


_______________________________________________ Do not post admin requests to the list. They will be ignored. Cocoa-dev mailing list (email@hidden) Help/Unsubscribe/Update your Subscription: This email sent to email@hidden
  • Prev by Date: Re: A rant and a question...
  • Next by Date: AppleScript from Cocoa applications
  • Previous by thread: Re: How to use a DataSource for NSLevelindicatorCell?
  • Next by thread: AppleScript from Cocoa applications
  • Index(es):
    • Date
    • Thread