我几个月来一直在修补Cocoa,我正在尝试添加撤消/重做支持到我正在编写的严格用于学习目的的Cocoa应用程序,可让您调整iTunes轨道元数据。感谢NSUndoManager的prepareWithInvocationTarget:
方法,我已经掌握了基础知识 - 您可以撤消/重做对所选曲目的播放计数和上次播放日期的更改。 (我正在使用appscript-objc获取/设置iTunes曲目数据。)
然而,由于更新大量iTunes曲目可能需要一些时间,我想让用户在正在进行时取消撤消/重做操作,但我没有看到明显的机制使用NSUndoManager完成此任务。我该怎么做呢?
修改
为了澄清,考虑到这一点,我想我真正想要的是一种操纵撤销/重做堆栈的方法,这样我就可以避免Rob Napier在他的回答中提到的“不一致状态”。
因此,为了响应撤消操作而未进行任何更改(例如,在调用撤消之前,用户已打开iTunes的“首选项”窗口,阻止Apple事件),撤消操作可以保留在顶部undo stack,重做堆栈保持不变。如果操作在中途被取消或失败,那么我想在重做堆栈上推送一个操作,该操作可以反转经历的更改(如果有的话),并且在撤消堆栈的顶部有一个应用所做更改的操作。成功。我认为有效地拆除撤销和重做堆栈之间的操作可能会引起用户混淆,但它似乎是解决问题最宽容的方式。
我怀疑这个问题的答案可能是“编写你自己该死的撤销管理器”,“你的撤销操作不应该失败”或“你不必要地过度复杂化”,但我很好奇。 :)
答案 0 :(得分:1)
这是您的代码的问题,而不是NSUndoManager
。无论您请求NSUndoManager
呼叫的方法是什么,都需要具有中断功能。这与在最初执行时取消操作没有什么不同(事实上,只要可能,它应该是相同的代码)。无论是否有线程,有两种常见的方法可以实现这一点。
在线程情况下,您在后台线程上运行撤消操作,并在每个循环中检查BOOL,例如self.shouldContinue。如果它被设置为假(通常由其他一些线程),那么你就停止了。
在没有线程的情况下实现此目的的类似方法是这样的:
- (void)doOperation
{
if ([self.thingsToDo count] == 0 || ! self.shouldContinue)
{
return;
}
id thing = [self.thingsToDo lastObject];
[self.thingsToDo removeLastObject];
// Do something with thing
[self performSelector:@selector(doOperation) withObject: nil afterDelay:0];
}
这里的一个主要问题是,此取消将使您的撤消堆栈处于不确定状态。由你来处理。同样,这与创建撤消项目时的初始情况没有什么不同。但是,如果处理中断,那么在撤消期间应该如何处理中断。它们通常不是不同的行为。
答案 1 :(得分:1)
我有两个答案。第一种是处理这个问题的常用方法:
当您点击取消按钮时调用撤消,让NSUndoManager回滚所有更改。
请参阅: http://www.cimgf.com/2008/04/30/cocoa-tutorial-wiring-undo-management-into-core-data/ http://www.mac-developer-network.com/columns/coredata/coredatafeb09/ 例如这种方法。他们正在讨论表格,但它应适用于任何可取消的内容。
此方法的问题在于它会留下不一致的重做堆栈。你可以通过调用removeAllActionsWithTarget从NSUndoManager驱逐目标来解决这个问题:
第二种解决方案要复杂得多,但我实际上使用它并且它有效。我在Java中使用移植版本,所以如果示例不在objective-c中,请原谅我,但我会尝试将这个概念贯穿其中。
我严格为操作创建一个新的撤消管理器,并将其设置为“活动”(我认为这意味着在Cocoa中将其设置在控制器上)。我保留了对原始参考的引用,以便在完成后我可以将事情恢复正常。
如果他们点击取消按钮,您可以在主动撤消管理器上调用撤消,释放它并将原始撤消管理器设置回原来的位置。除了最初的那些之外,原始文件不会有任何撤销或重做操作。
如果操作成功,则有点棘手。此时你需要registerUndoWithTarget:selector:object:。这是一个需要发生什么的伪代码:
invoke(boolean undo) {
oldUndoManager = currentUndoManager
setCurrentUndoManager(temporaryUndoManager)
if (undo)
temporaryUndoManager.undo()
oldUndoManager.registerUndo(temporaryUndoManager,
"invoke", false)
else
temporaryUndoManager.redo()
oldUndoManager.registerUndo(temporaryUndoManager,
"invoke", true)
setCurrentUndoManager(oldUndoManager)
}
这将允许您的原始(旧)撤消管理器基本上在新(临时)上调用撤消/重做,并在响应中设置相应的撤消/重做。这比第一个复杂得多,但确实帮助我做了我想做的事。
我的理念是,只有完成的操作应该转到撤消管理器。取消的操作是一种从不存在的所有实际目的的操作。你不会在我知道的任何Apple文档中找到它,只是我的意见。