Invoke NSControl's "valueChanged" when changing values programmatically - objective-c

I am about to implement a simple audio fader.
I subclassed NSSlider and NSSliderCell to be able to draw the graphics accordingly.
I want the slider to jump to a defined position whenever the handle is clicked with the Cmd key pressed. This is pretty common in most audio applications.
Setting the value to the slider is working flawlessly, but it does not invoke the action method to inform my target that the value changed.
This must be happening somewhere in the mouseDown event handling of "super", probably in NSControl. This is what I got so far :
- (void) mouseDown:(NSEvent *)theEvent
{
if ( ([theEvent modifierFlags] & NSCommandKeyMask) > 0)
[self setFloatValue:100.0f];
else
[super mouseDown:theEvent];
}
I found a method in the NSControl doc that looks promising:
-(BOOL)sendAction:(SEL)theAction to:(id)theTarget Parameters :
theAction : The selector to invoke on the target. If the selector is
NULL, no message is sent.
theTarget : The target object to receive the message. If the object is
nil, the application searches the responder chain for an object
capable of handling the message. For more information on dispatching
actions, see the class description for NSActionCell.
But I don't have a clue what the selector would be named like.
The target would probably be super?
Any help is much appreciated!!
Best,
Flo

The action could be the name of the action method that you already are using for your slider, and the target should be the class instance where that method is implemented. So, for example, if you had an IBAction in your app delegate that was connected to your slider called sliderReport: then do this:
[self sendAction:#selector(sliderReport:) to:[NSApp delegate]];
Additional Note: I assumed from your question that you wanted to invoke the same IBAction that you have connected to your slider, but you could send a different selector if you want -- if you need to distinguish between how the slider value was changed, having a different method would be the way to go. You can name it anything you want (with a colon on the end, so it can send itself as the sender argument if you need that).

I found an answer myself.
Although the answer of #rdelmar might also work, it won't anymore if you change the name of the action method.
I found a universal way of triggering the action method manually:
- (void) mouseDown:(NSEvent *)theEvent
{
if ( ([theEvent modifierFlags] & NSCommandKeyMask) != 0)
{
[self setFloatValue:100.0f];
[self.target performSelector:self.action withObject:self];
}
else
[super mouseDown:theEvent];
}

Related

How to handle Print event (via shortkey CMD + P) in Cocoa with Objective-C?

I tried this in AppDelegate. But after shortkey CMD + P the method never being called.
- (void)handleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
NSLog(#"111111111");
}
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self andSelector:#selector(handleEvent:withReplyEvent:)
forEventClass:kCoreEventClass andEventID:kAEPrintDocuments];
You don't need to handle the AppleEvent directly, take a look at the NSApplicationDelegate method application:printFiles:withSettings:showPrintPanels:. HTH
⌘P is tied to the menu item titled "Print…". Triggering this menu item calls a particular responder chain message, which by default is -print: (see MainMenu.xib connections).
One simply needs to implement -print: somewhere along the responder chain to receive that message. By default, macOS applications have an implementation of -print: in NSWindow and so you'd have to either override this implementation to further it along the responder chain or implement it in a view/view controller subclass. As an example, this could be the implementation you have in an NSView subclass that is first responder or if you subclass NSWindow:
- (void)print:(id)sender {
// viewToPrint is whatever NSView we want to use in the print operation
[[NSPrintOperation printOperationWithView:viewToPrint] runOperation];
}
Also be sure to enable printing for your app's capabilities or else your app will complain that printing isn't permitted.

NSWindow, press key ENTER: how to limit the key listening to the focused NSControl?

I have an NSWindow with a main "OK" button. This button has as "key equivalent" property in interface builder, the key ENTER i.e ↵.
It works good, but now I have a new NSComboBox, which is supposed to invoke a method when the user selects a list item, or he preses Enter / ↵.
However, when I press Enter, the main Button receive the notification and the window close. How to prevent this?
This is the normal behavior what you are getting, but you can hack a bit, by removing and adding the key-equivalent.
Add following delegates of NSComboBox:
- (void)comboBoxWillPopUp:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#""];
}
- (void)comboBoxWillDismiss:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#"\r"];
}
One way you can workaround for prevent enter notification is like that below:-
//Connect this action method to your combobbox and inside that set one BOOL flag to yes
- (IBAction)comBoxItm:(id)sender
{
self.isEnterCalled=YES;
}
//Now check this flag to your some method where close window is called
-(void)someMethod
{
//Check the flag value if it is yes then just ignore it
if (!self.isEnterCalled)
{
//Close window logic
}
self.isEnterCalled=NO;
}
Ran into the same problem. Had "hot key" which I'd like to switch off while editing some text fields. I found solution for myself. There's no need in override lots of NSTextField base methods.
Firstly, I removed all the "key equivalents". I used to detect Enter key down with the + (void)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent *(^)(NSEvent *))block class method of NSEvent. You pass block as a parameter, where you can check for some conditions. The first parameter is the event mask. For your task it would be NSKeyDownMask, look for other masks at the NSEvent Reference Page
The parameter block will perform each time the user pushes the button. You should check if it is right button pushed, and - generally - if the current window first responder isn't some editable control. For that purposes we need NSWindow category class just not to implement this code each time we deal with NSKeyDownMasked local monitors.
NSWindow+Responders class listing:
#interface NSWindow (Responders)
- (BOOL)isEditableFirstResponder;
#end
#implementation NSWindow (Responders)
- (BOOL)isEditableFirstResponder
{
if (!self.firstResponder)
return NO; // no first responder at all
if ([self.firstResponder isKindOfClass:[NSTextField class]]) // NSComboBox is NSTextField subclass
{
NSTextField *field=(NSTextField *)self.firstResponder;
return field.isEditable;
}
if ([self.firstResponder isKindOfClass:[NSButton class]]) // yep, buttons may be responders
return YES;
return NO; // the first responder is not NSTextField or NSButton subclass - not editable
}
#end
Don't know if there's another way to check if we are now editing some text field or combo box. So, there's at least the part you add the local monitor somewhere in your class (NSWindow, NSView, some controller etc.).
- (void)someMethod
{
id monitor=[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:(NSEvent *)^(NSEvent *theEvent){
if (theEvent.keyCode==/*Enter key code*/ && ![self.window.isEditableFirstResponder]) // you should check the key modifiers too
{
// your code here
}
return theEvent; // you may return the event to pass the key to the receiver
}];
}
Local monitors is safe remedy about the Apple rules. It works only inside your application. For global key down events you may use addGlobalMonitor but Apple may reject your app from the AppStore.
And don't forget to remove the monitor when there's no need in it.
- (void)viewControllerShutdownMethod
{
[NSEvent removeMonitor:monitor];
}
Good luck.

How to overwrite textDidChange: method correctly?

I subclassed NSTextField and overwrite the textDidChange: as:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
}
But when I drag a input text box into my .xib file and control drag another class to assign the delegate I found the delegate's controlTextDidChange: method was never called.
Now tryingto solve this problem, I tried two ways al below:
I. calling super:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
[super textDidChange:notification];
}
But I got an error in runtime: attempt to insert nil object from objects[0]
II. calling delegate's method
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
if ([self.delegate responseToSelector:#selector(controlTextDidChange:)])
{
[self.delegate ...]; // <--- Opps, something not happerned here.
}
}
What not happerned? I expected that the auto-complete should display the controlTextDidChange: method at the position of ... above. But it did not, actually. I typed the method directly, compilation fail because method not found.
How should I make my sub-class call the delegate normally? How should I overwrite the textDidChange: method correctly?
Further question for Vytautas:
I am sure I was using NSTextField. And I set a break point inside controlTextDidChange: method. As it was called, I should have known.
I did control-drag the text field to the delegate object, and I print delegate object in the textDidChange: method, it was sure that the delegate was set correctly.
The other delegate methods, such as controlTextDidBeginEditing: were called correctly. But controlTextDidChange: not called
I tried comment out the over-written in the subclassed NSTextField class, then controlTextDidChange: was called.
Therefore I was quite sure that I am not overwritting the textDidChange: right. But I do not known how to fix it.
What made me confused mostly was that why auto-completion did not show the controlTextDidChange: method when I attempted to call it.
About the auto-completion, here is how it showed:
No - controlTextDidChange: method.
2nd further reply for Vytautas:
I tried calling '[self controlTextDidChange]' but it did not work, and error occurred (as highlighted below):
I can say that - controlTextDidChange: is called for sure.
Maybe there is something wrong with you bindings in your *.xib.
Also it can be that in *.xib you are using NSTextView, not NSTextField.
In this case - controlTextDidChange: won't be called for sure.
If that is the case then you should take a look to NSTextView, NSTextViewDelegate and NSTextDelegate. NSTextView delegate has an alternative method for this - textDidChange:

keyDown:(NSEvent *)event is not invoked when the focus is on a text field

I've overridden - (void)keyDown:(NSEvent *)event in my NSPanel subclass.
However it is invoked only if the focus is not on a NSTextField inside my panel.
However I need to catch the event "Enter button pressed" regardless of if the focus is on the text field or the panel.
How to make sure it is always invoked?
Are you sure that you need to catch the key down event for that?
Apple state in the docs that fiddling with keyDown: for controls is kind of a last resort, to be used only if the normal Cocoa architecture around delegates does not do what you want.
If the purpose is to catch the enter button pressed, notice that this event in a text field triggers the textDidEndEditing delegate method (or notification, if you prefer that).
So if you implement controlTextDidEndEditing: in a delegate for your NSTextField you should be able to react to the event. This notification (and the relative delegate method) is sent when the field editor ends editing.
If you prefer to catch the event one step earlier (before the field editor ends editing), you can implement the delegate method control:textView:doCommandBySelector: which lets you intercept specific key events (such as the return key) and modify the behaviour of the editor.
An example could be the following:
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
BOOL retval = NO;
if (commandSelector == #selector(insertNewline:)) {
retval = YES; // Handled
// Do stuff that needs to be done when newLine is pressed
}
return retval;
}
There is a lot of documentation on Apple's site about it, for instance an introduction here.

Listen to a value change of my text field

I'm trying to understand how to catch a "text changed" event from a text field in my window. I'm used to Java's "action listeners", and can't find anything similar in Objective-C/Cocoa.
I searched for quite a while and found the "key value observing" protocol, but the observeValueForKeyPath: method (function?) only triggers when the value of my text field was changed in code (using [textfield setStringValue:...], e.g.), not by typing in it.
How can I "listen" to the value change when a user types in the text field?
You can set a delegate for your NSTextField instance and have the delegate implement the following method:
- (void)controlTextDidChange:(NSNotification *)notification {
// there was a text change in some control
}
Your delegate object can be the application delegate, a window controller, a view controller, or some other object in your application. The delegate can be programatically set via
[myTextField setDelegate:delegateObject];
or, in Interface Builder, via the delegate outlet available in the NSTextField control.
Note that if there are multiple controls hooked to the same delegate then -controlTextDidChange: will be sent for each control, i.e., the same method is called for different controls. If you want different behaviour according to the control where the text has changed, you can use -[NSNotification object] to identify the control that has sent the notification.
For instance, if you have two text fields with corresponding outlets nameField and addressField, and you’ve set the same delegate for both fields, then:
- (void)controlTextDidChange:(NSNotification *)notification {
// there was a text change in some control
// [notification object] points to the control that has sent
// the notification
if ([notification object] == nameField) {
// nameField has changed
}
else if ([notification object] == addressField) {
// addressField has changed
}
}
Alternatively, you could have one delegate for each text field. In this case, there’d be no need to test [notification object].
You can also just hook up to the "Editing Changed" from IB and create the Action to handle it
- (IBAction)txtField_Changed:(id)sender
{
// my textfield has been changed
}
This works for me
func textView(textView: NSTextView, shouldChangeTextInRange affectedCharRange: NSRange, replacementString: String?) -> Bool {
print("Changed!")
return true
}
You can use textFieldShouldBeginEditing: method of UITextFieldDelegate. In iOS listeners are called NSNotifications
EDIT
In objective-c a lot of UIObjects have a corresponding protocol class that's called "delegate" The delegate is responsible for reacting to events. So to be able to respond or to be notified about actions you need to implement the delegate and its methods.