3

私はここ数か月 Cocoa をいじり回しており、iTunes トラックのメタデータを調整できる、厳密に学習目的で書いている Cocoa アプリに元に戻す/やり直しのサポートを追加しようとしています。NSUndoManager のメソッドのおかげでprepareWithInvocationTarget:、基本が整っています。たとえば、選択したトラックの再生回数と最終再生日の変更を元に戻したりやり直したりできます。(私は appscript-objc を使用して iTunes トラック データを取得/設定しています。)

ただし、多数の iTunes トラックの更新には少し時間がかかる可能性があるため、ユーザーが進行中に元に戻す/やり直し操作をキャンセルできるようにしたいと考えていますが、これを達成するための明確なメカニズムは見当たりません。 NSUndoManager で。どうすればこれを行うことができますか?

編集:
明確にするために、これについてもう少し考えてみると、私が本当に求めているのは、元に戻す/やり直しスタックを操作して、Rob Napier が回答で言及している「一貫性のない状態」を回避する方法だと思います。

そのため、変更を加えずに失敗した元に戻す操作 (たとえば、元に戻すを呼び出す前に、ユーザーが Apple Events をブロックする iTunes の [設定] ウィンドウを開いた場合) に応答して、元に戻す操作が元に戻すスタックの一番上に残る可能性があります。 REDO スタックは変更されません。操作が途中でキャンセルまたは失敗した場合は、実行された変更 (ある場合) を元に戻す操作をやり直しスタックにプッシュし、実行されなかった変更を適用する操作を元に戻すスタックの上に置きたいと思います。成功しません。元に戻すスタックとやり直しスタックの間で操作を効果的に分割すると、ユーザーの混乱を招く可能性があると思いますが、問題を処理する最も寛容な方法のようです。

これに対する答えは、「あなた自身のUndo Managerを書いてください」、「あなたの元に戻す操作は失敗するべきではありません」、または「これを不必要に複雑にしすぎている」のいずれかであると思いますが、私は興味があります. :)

4

2 に答える 2

1

2つの答えがあります。1 つ目は、これを処理する一般的な方法です。

キャンセル ボタンを押したときに undo を呼び出し、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/を参照してください。彼らはシートについて話し合っていますが、キャンセル可能なものすべてに適用する必要があります。

この方法の問題点は、一貫性のない REDO スタックが残ることです。removeAllActionsWithTarget を呼び出して NSUndoManager からターゲットを削除することで、これを解決できる場合があります。

2番目の解決策はもっと複雑ですが、実際に使用して動作します。私はJavaで移植されたバージョンを使用しているので、例がobjective-cにない場合は許してください。とにかく概念を理解しようとします.

操作専用の新しい元に戻すマネージャーを作成し、それを「アクティブ」に設定します (これは、Cocoa のコントローラーに設定することを意味すると思います)。元のものへの参照を保持しているので、完了したら通常の状態に戻すことができます。

キャンセル ボタンが押された場合は、アクティブな取り消しマネージャーで取り消しを呼び出し、それを解放して、元の取り消しマネージャーを以前の場所に戻すことができます。オリジナルには、元にあったアクション以外の保留中の取り消しまたはやり直しアクションはありません。

操作が成功した場合、少し注意が必要です。この時点で、UndoWithTarget: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 のドキュメントにはそれがありませんが、私の意見です。

于 2009-12-18T03:20:28.640 に答える
1

これはコードの問題であり、NSUndoManager. 呼び出しを要求したメソッドNSUndoManagerには、割り込み機能が必要です。これは、操作が最初に実行されたときに操作をキャンセルすることと同じです (実際、可能な限り同じコードにする必要があります)。これを実現するには、スレッドを使用する方法と使用しない方法の 2 つの一般的な方法があります。

スレッド化されたケースでは、バックグラウンド スレッドで元に戻す操作を実行し、すべてのループで self.shouldContinue などの BOOL をチェックします。false に設定された場合 (通常は他のスレッドによって)、停止します。

スレッドなしでこれを達成する同様の方法は次のとおりです。

- (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];
}

ここでの大きな問題は、このキャンセルによって元に戻すスタックが不確定な状態のままになることです。それに対処するのはあなた次第です。繰り返しますが、これは元に戻す項目を作成した最初のケ​​ースと変わりません。ただし、割り込みを処理する場合は、元に戻すときに割り込みを処理する方法です。それらは一般的に異なる種類のアクションではありません。

于 2009-11-02T14:27:32.043 に答える