如何通过编程更改 NSTextView 的 textValue 来实现撤消/重做?

发布于 2024-12-17 22:08:34 字数 1515 浏览 3 评论 0原文

我创建了一个带有 NSTextView 和按钮的简单演示应用程序,为 textView 提供了一个 NSTextViewDelegate 并添加了一个操作:

- (IBAction)actionButtonClicked:(id)sender {
    NSString *oldText = [[[self.textView textStorage] string] copy];
    NSString *newText = @"And... ACTION!";

    [[self.textView undoManager] registerUndoWithTarget:self.textView
                                               selector:@selector(setString:)
                                                 object:oldText];
    [[self.textView undoManager] setActionName:@"ACTION"];

    [self.textView setString:newText];
}

如果我手动更改文本,撤消/重做工作不会出现问题。但是,如果我使用操作方法更改文本,撤消将按预期工作,但重做不再起作用(什么也没有发生),并且撤消管理器似乎被打乱...

好吧 - 为了避免 NSTextView 出现问题,我创建了一个模型类,将 NSTextView 绑定到它并将撤消/重做移动到模型,但这显示了与以前相同的行为 - 我做错了什么 - 这应该很容易,不是吗?

#import "GFTextStore.h"

@implementation GFTextStore

@synthesize textVal = textVal_;

-(void)changeText{
    if (!undoMan_) {
        undoMan_ = [[[NSApplication sharedApplication] mainWindow] undoManager];   
    }

    NSAttributedString *oldText = [self.textVal copy];
    NSString *tempStr = [[oldText string] stringByAppendingFormat:@"\n%@",[[NSCalendarDate date]description]];
    NSAttributedString *newText = [[NSAttributedString alloc] initWithString:tempStr];

    [self setTextVal:newText];


    [undoMan_ registerUndoWithTarget:self
                            selector:@selector(setTextVal:)
                              object:oldText];

    [undoMan_ setActionName:@"ACTION"];
}
@end

I created a simple demo app with a NSTextView and a button, the provided a NSTextViewDelegate to the textView and added an action:

- (IBAction)actionButtonClicked:(id)sender {
    NSString *oldText = [[[self.textView textStorage] string] copy];
    NSString *newText = @"And... ACTION!";

    [[self.textView undoManager] registerUndoWithTarget:self.textView
                                               selector:@selector(setString:)
                                                 object:oldText];
    [[self.textView undoManager] setActionName:@"ACTION"];

    [self.textView setString:newText];
}

Undo/redo works without problems, if I change text by hand. But if I change the text with the action method, undo works as expected, but redo does not work anymore (nothing happens) and the undo manager seems to be scrambled...

OK - to avoid problems with NSTextView I created a model class, bound the NSTextView to it and moved the undo/redo to the model, but this shows the same behavior as before - what the I'm doing wrong - this should be easy, shouldn't it?

#import "GFTextStore.h"

@implementation GFTextStore

@synthesize textVal = textVal_;

-(void)changeText{
    if (!undoMan_) {
        undoMan_ = [[[NSApplication sharedApplication] mainWindow] undoManager];   
    }

    NSAttributedString *oldText = [self.textVal copy];
    NSString *tempStr = [[oldText string] stringByAppendingFormat:@"\n%@",[[NSCalendarDate date]description]];
    NSAttributedString *newText = [[NSAttributedString alloc] initWithString:tempStr];

    [self setTextVal:newText];


    [undoMan_ registerUndoWithTarget:self
                            selector:@selector(setTextVal:)
                              object:oldText];

    [undoMan_ setActionName:@"ACTION"];
}
@end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

征﹌骨岁月お 2024-12-24 22:08:34

此处无需添加 NSUndoManager,只需让 NSTextView 完成该工作即可。

您只需要确保仅调用以 insert... 开头的 NSTextView 的更高级别的方法,而不是直接设置 textView 或 textStorage 的文本/字符串:

    [self.textView insertText:newString];

如果您绝对需要使用 setString 或其他较低级别的方法,然后您只需要添加处理 textDidChange 委托所需的方法: -shouldChangeTextInRange:replacementString-didChangeText (这是通过 insert... 方法完成的):

if( [self.textView shouldChangeTextInRange:editedRange replacementString:editedString]) {
    // do some fancy stuff here…
    [self.textView.textStorage replaceCharactersInRange:editedRange
                          withAttributedString:myFancyNewString];
    // … and finish the editing with
    [self.textView didChangeText];
}

这会自动让 NSTextView 的 undoManager 启动。我认为 undoManager 正在 shouldChangeTextInRange: 中准备撤消分组并调用 didChangeText 中的撤消:.

No need to add the NSUndoManager here, just let the NSTextView do the job.

You just need to make sure you are calling the higher level methods only of NSTextView beginning with insert… and not setting the text/string of the textView or the textStorage directly:

    [self.textView insertText:newString];

If you absolutely need to use setString or other lower level methods, then you just need to add the required methods handling the textDidChange delegation: -shouldChangeTextInRange:replacementString and -didChangeText (which is done by the insert... methods btw):

if( [self.textView shouldChangeTextInRange:editedRange replacementString:editedString]) {
    // do some fancy stuff here…
    [self.textView.textStorage replaceCharactersInRange:editedRange
                          withAttributedString:myFancyNewString];
    // … and finish the editing with
    [self.textView didChangeText];
}

This automatically lets the undoManager of NSTextView kick in. I think the undoManager is preparing an undoGrouping in shouldChangeTextInRange: and invoking the undo in didChangeText:.

梦幻的味道 2024-12-24 22:08:34

-setString: 是从 NSText 继承的方法。要仅使用 NSTextView 方法来处理此问题以便处理撤消,只需执行以下操作:

[self.textView setSelectedRange:NSMakeRange(0, [[self.textView textStorage] length])];
[self.textView insertText:@"And… ACTION!"];

以这种方式更改文本可以完全避免与撤消管理器混淆。

-setString: is an inherited method from NSText. To handle this using NSTextView methods only so that undo is handled, just do this:

[self.textView setSelectedRange:NSMakeRange(0, [[self.textView textStorage] length])];
[self.textView insertText:@"And… ACTION!"];

Making the text change this way avoids mucking about with the undo manager at all.

指尖上的星空 2024-12-24 22:08:34

假设您希望 NSTextView 在用户按下 Enter 键时创建一个新的撤消组(Apple Pages 行为)。然后你可以在 NSTextView 子类中键入以下代码:

override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
    super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
    guard replacementString != nil else { return true }

    let newLineSet = NSCharacterSet.newlineCharacterSet()
    if let newLineRange = replacementString!.rangeOfCharacterFromSet(newLineSet) {

        // check whether it's a single character (user hit Return key)
        let singleCharRange = (replacementString!.startIndex)! ..< (replacementString!.startIndex.successor())!
        if newLineRange == singleCharRange {
            self.breakUndoCoalescing()
        }
    }

    return true
}

Let's say you want your NSTextView to create a new undo group when user hits the Enter key (Apple Pages behavior). Then you might type this code in your NSTextView subclass:

override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
    super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
    guard replacementString != nil else { return true }

    let newLineSet = NSCharacterSet.newlineCharacterSet()
    if let newLineRange = replacementString!.rangeOfCharacterFromSet(newLineSet) {

        // check whether it's a single character (user hit Return key)
        let singleCharRange = (replacementString!.startIndex)! ..< (replacementString!.startIndex.successor())!
        if newLineRange == singleCharRange {
            self.breakUndoCoalescing()
        }
    }

    return true
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文