selectText of NSTextField on focus

后端 未结 5 1686
既然无缘
既然无缘 2021-02-04 10:17

Could anybody suggest a method to select all the text of an NSTextField when the user clicks it?

I did find suggestions to subclass NSTextField

相关标签:
5条回答
  • 2021-02-04 10:49

    I like Konstantin's solution, but that one will select on every mouse down. Here's a variation I'm using to select in mouseDown method, but only if it just became the first responder:

    class SelectingTextField: NSTextField {
    
        var wantsSelectAll = false
    
        override func becomeFirstResponder() -> Bool {
            wantsSelectAll = true
    
            return super.becomeFirstResponder()
        }
    
        override func mouseDown(with event: NSEvent) {
            super.mouseDown(with: event)
    
            if wantsSelectAll {
                selectText(self)
                wantsSelectAll = false
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-04 10:54

    The answer from @Rob presumably worked at one point, but as @Daniel noted, it does not work any more. It looks like Cocoa wants to track the mouse and drag out a selection in response to a click, and trying to select the text in response to becomeFirstResponder does not play well with that.

    The mouse event needs to be intercepted, then, to prevent that tracking. More or less by trial and error, I've found a solution which seems to work on OS X 10.10:

    @interface MyAutoselectTextField : NSTextField
    @end
    
    @implementation MyAutoselectTextField
    - (void)mouseDown:(NSEvent *)theEvent
    {
        [[self currentEditor] selectAll:nil];
    }
    @end
    

    As far as I can tell, by the time mouseDown: gets called the field editor has already been set up, probably as a side effect of becomeFirstResponder. Calling selectAll: then selects the contents of the field editor. Calling selectText: on self instead does not work well, presumably because the field editor is set up. Note that the override of mouseDown: here does not call super; super would run a tracking loop that would drag out a selection, and we don't want that behavior. Note that this mouseDown: override doesn't affect selection once the textfield has become first responder, because at that point it is the field editor's mouseDown: that is getting called.

    I have no idea what range of OS X versions this works across; if you care, you'll need to test it. Unfortunately, working with NSTextField is always a little fragile because the way field editors work is so strange and so dependent upon the implementation details in super.

    0 讨论(0)
  • 2021-02-04 10:56

    I know this is an old question, but here's an update for those who might stumble over this.

    Override NSTextField and hook into becomeFirstResponder(). You won't have to worry about managing delegates or clicks. The tricky part is finding the field editor for the focused text field first before asking it to select all the text.

    Objective-C

    // in AutoselectOnFocusTextField.h
    @interface AutoselectOnFocusTextField : NSTextField
    @end
    
    // in AutoselectOnFocusTextField.m
    @implementation AutoselectOnFocusTextField
    - (BOOL)becomeFirstResponder {
        if (![super becomeFirstResponder]) {
            return NO;
        }
        NSText* fieldEditor = [self currentEditor];
        if (fieldEditor != nil) {
            [fieldEditor performSelector:@selector(selectAll:) withObject:self afterDelay:0.0];
        }
        return YES;
    }
    @end
    

    Swift

    class AutoselectOnFocusTextField: NSTextField {
        override func becomeFirstResponder() -> Bool {
            guard super.becomeFirstResponder() else {
                return false
            }
            if let editor = self.currentEditor() {
                editor.perform(#selector(selectAll(_:)), with: self, afterDelay: 0)
            }
            return true
        }
    }
    
    0 讨论(0)
  • 2021-02-04 11:03

    There isn't an easier solution, you need to subclass NSTextField to do what you want. You will need to learn how to handle subclassing if you are to do anything useful in Cocoa.

    Text fields can be relatively complex to subclass because an NSTextField uses a separate NSTextView object called the Field Editor to handle the actual editing. This text view is returned by the NSWindow object for the NSTextField and it is re-used for all text fields on the page.

    Like any NSResponder subclass, NSTextField responds to the methods -acceptsFirstResponder and -becomeFirstResponder. These are called when the window wants to give focus to a particular control or view. If you return YES from both these methods then your control/view will have first responder status, which means it's the active control. However, as mentioned, an NSTextField actually gives the field editor first responder status when clicked, so you need to do something like this in your NSTextField subclass:

    @implementation MCTextField
    - (BOOL)becomeFirstResponder
    {
        BOOL result = [super becomeFirstResponder];
        if(result)
            [self performSelector:@selector(selectText:) withObject:self afterDelay:0];
        return result;
    }
    @end
    

    This first calls the superclass' implementation of -becomeFirstResponder which will do the hard work of managing the field editor. It then calls -selectText: which selects all the text in the field, but it does so after a delay of 0 seconds, which will delay until the next run through the event loop. This means that the selection will occur after the field editor has been fully configured.

    0 讨论(0)
  • 2021-02-04 11:03

    Some updates with Swift:

    import Cocoa
    
    class TextFieldSubclass: NSTextField {
        override func mouseDown(theEvent: NSEvent) {
            super.mouseDown(theEvent)
            if let textEditor = currentEditor() {
                textEditor.selectAll(self)
            }
        }
    }
    

    Or for precise selection:

    import Cocoa
    
    class TextFieldSubclass: NSTextField {
        override func mouseDown(theEvent: NSEvent) {
            super.mouseDown(theEvent)
            if let textEditor = currentEditor() {
                textEditor.selectedRange = NSMakeRange(location, length)
            }
        }
    }
    

    Swift version, works for me:

    import Cocoa
    
    class TextFieldSubclass: NSTextField {
        override func becomeFirstResponder() -> Bool {
            let source = CGEventSourceCreate(CGEventSourceStateID.HIDSystemState)
            let tapLocation = CGEventTapLocation.CGHIDEventTap
            let cmdA = CGEventCreateKeyboardEvent(source, 0x00, true)
            CGEventSetFlags(cmdA, CGEventFlags.MaskCommand)
            CGEventPost(tapLocation, cmdA)
            return true
        }
    }
    
    0 讨论(0)
提交回复
热议问题