【问题标题】:Undo/Redo Menu Items Never Enabled撤消/重做菜单项从不启用
【发布时间】:2020-01-20 18:58:18
【问题描述】:

我有一个核心数据应用程序,其 NSTableView 绑定到 NSArrayController。我使用数组控制器管理添加和删除对象。我正在尝试添加撤消/重做支持,因此当一个人使用菜单项从表视图中删除对象时,他们可以撤消删除。

我的删除方法是:

- (IBAction)removeHost:(id)sender
{
    NSInteger row = [bookmarkList selectedRow];

    // Get the object so we can get to the attributes of the host
    NSArray *a = [bookmarksController arrangedObjects];
    NSManagedObject *object = [a objectAtIndex:row];

    if (!object) return;
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    NSUndoManager *undoManager = [managedObjectContext undoManager];

    if (managedObjectContext.undoManager == nil)
    {
        NSLog(@"No undo manager in app controller!");
    } else {
        NSLog(@"We've got an undo manager in app controller!");
    }

    [undoManager registerUndoWithTarget:self selector:@selector(addBookmarkObject:) object:object];
    [bookmarksController removeObject:object];
    [undoManager setActionName:@"Bookmark Delete"];
}

删除对象可以正常工作,但撤消不能。 Command-Z 菜单项永远不会启用。我设置了一个临时菜单项和操作来测试 undoManager,

- (IBAction)stupidUndoRemoveHost:(id)sender
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    NSUndoManager *undoer = [managedObjectContext undoManager];

    NSLog(@"canUndo? %hhd", [undoer canUndo]);
    NSLog(@"canRedo? %hhd", [undoer canRedo]);
    NSLog(@"isUndoRegistrationEnabled? %hhd", [undoer isUndoRegistrationEnabled]);
    NSLog(@"undoMenuItemTitle = %@", [undoer undoMenuItemTitle]);
    NSLog(@"redoMenuItemTitle = %@", [undoer redoMenuItemTitle]);

    [undoer undo];
}

使用这个 IBAction 我可以撤消(嗯,有点,它添加了两次对象,很明显这里还有更多错误),但我只能做一次。如果我删除另一个对象 canUndo 返回 0,而愚蠢的UndoRemoveHost 什么也不做。

我知道我不明白这里的某些内容。我在这里阅读的帖子数不胜数,还有几篇博客文章和Apple documentation。我以前也这样做过,但就像十年前一样,所以我的技能有点生疏。非常感谢任何正确方向的帮助或指示。

更新:这里是 addBookmarkObject 方法:

- (void)addBookmarkObject: (NSManagedObject *)object
{
    [bookmarksController addObject:object];
}

这是来自 AppDelegate 的windowWillReturnUndoManager

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
    NSUndoManager *undoManager = [[NSUndoManager alloc] init];
    self.persistentContainer.viewContext.undoManager = undoManager;

    if (self.persistentContainer.viewContext.undoManager == nil)
    {
        NSLog(@"No undo manager!");
    } else {
        NSLog(@"We've got an undo manager!");
    }

    return self.persistentContainer.viewContext.undoManager;
}

【问题讨论】:

  • 显示 addBookmarkObject 的代码。我没有看到 removeHost 中的代码有任何问题。
  • 谢谢,我已经用更多代码更新了这个问题。
  • 托管对象上下文的撤消管理器应该处理撤消,您不必自己实现它。为什么要替换windowWillReturnUndoManager: 中的撤消管理器?这是一个基于文档的应用程序吗?
  • 它不是基于文档的,我在之前的故障排除步骤中添加了它。我将删除自定义撤消管理器并重试。
  • @Willeke 如果我没有在 App Delegate 的 windowWillReturnUndoManager 中初始化 undoManager,则没有撤消管理器。登录我的 App Controller 的 stupidUndoRemoveHost 显示没有撤消管理器,在委托中也是如此。

标签: objective-c cocoa core-data nsundomanager


【解决方案1】:

windowWillReturnUndoManager: 会在每次 Appkit 想要注册撤销操作以及想要启用/禁用 Undo 菜单项时调用。如果windowWillReturnUndoManager: 返回一个新的撤消管理器,则撤消堆栈为空并且撤消菜单项被禁用。

当一个对象被移除时,Core Data 会注册一个撤销操作,removeHost: 不应该注册一个额外的撤销操作。

- (IBAction)removeHost:(id)sender
{
    [bookmarksController remove:sender];
    [undoManager setActionName:@"Bookmark Delete"];
}

Xcode macOS Cocoa App with Core Data 模板存在一些缺陷。

NSWindowDelegate 方法 windowWillReturnUndoManager: 未被调用,因为在 xib 中,窗口的委托未连接到应用委托。修复:将窗口的委托连接到委托。

self.persistentContainer.viewContext.undoManagernil。修复:创建持久化容器时创建一次撤消管理器。

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"TestCDUndo"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    …
                    abort();
                }
                self->_persistentContainer.viewContext.undoManager = [[NSUndoManager alloc] init];
            }];
        }
    }

    return _persistentContainer;
}

【讨论】:

  • 关于 undomanager 是 nil。这个答案解决了这个问题。然而,关于 OSX 上下文的文档状态提供了它自己的 undomanager,这令人困惑。 developer.apple.com/documentation/coredata/…
  • 文档指出“在 macOS 中,上下文默认提供撤消管理器;在 iOS 中,撤消管理器默认为 nil。”但显然NSPersistentContainer.viewContext 默认不提供撤消管理器。
  • 试过了(上下文的新实例),它仍然为零。
  • @MarekH 请使用minimal reproducible example 发布一个新问题。
  • 有点可笑。你有一个班轮 - >stackoverflow.com/questions/64539712/…
猜你喜欢
  • 1970-01-01
  • 2012-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-14
相关资源
最近更新 更多