【问题标题】:How to implement undo/redo with programatic change of the textValue of a NSTextView?如何通过编程更改 NSTextView 的文本值来实现撤消/重做?
【发布时间】:2012-01-05 22:55:32
【问题描述】:

我用一个 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

【问题讨论】:

    标签: cocoa nstextview undo-redo


    【解决方案1】:

    这里不需要添加 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: 中的 undoGrouping: 并在 didChangeText: 中调用 undo。

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      • 谢谢 Rob,但这并不能解决我的问题。为了避免 NSTextView 出现问题,我做了一些更改并将撤消/重做移至模型 - 请参阅编辑后的问题...
      【解决方案3】:

      假设您希望您的 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
      }
      

      【讨论】:

        猜你喜欢
        • 2012-06-12
        • 1970-01-01
        • 2012-07-02
        • 2015-08-10
        • 2020-05-12
        • 1970-01-01
        • 2016-06-18
        • 2011-04-03
        • 2016-02-18
        相关资源
        最近更新 更多