撤消/重做菜单项从不启用

Posted

技术标签:

【中文标题】撤消/重做菜单项从不启用【英文标题】: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 显示没有撤消管理器,在委托中也是如此。 【参考方案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 发布一个新问题。 有点可笑。你有一个班轮 - >***.com/questions/64539712/…

以上是关于撤消/重做菜单项从不启用的主要内容,如果未能解决你的问题,请参考以下文章

在 WinForms 中为上下文菜单动态选择菜单项的正确方法是啥?

如何在不关闭菜单的情况下选择菜单项?

选择菜单项时无法显示组

在 react material-ui 菜单中测试嵌套菜单项

单击主菜单项的jQuery不会显示子子菜单

如何更改操作栏上菜单项的位置