ValueTreeでUndoManagerを使う
アプリケーションに取り消し/やり直しアクションを実装します。で以前の中間状態を簡単に復元できます。UndoableActionオブジェクトを作成し、取り消し可能なアクションをトランザクションにグループ化する方法を学ぶ。
レベル:中級
プラットフォーム:Windows, macOS, Linux
クラス: UndoManager,UndoableAction,ValueTree,TreeView,TreeViewItem
スタート
このチュートリアルではValueTreeで説明されているようにTutorial: The ValueTree class.もしまだなら、まずそのチュートリアルを読むべきだ。
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
デモ・プロジェクト
このデモ・プロジェクトはUndoManagerクラスはValueTree過去の歴史がいかに簡単に復元できるかを示すために。それはValueTreeデータをツリー構造として扱うにはTreeViewそしてTreeViewItemクラスを作成します。プロジェクトをビルドして実行すると、このように表示されるはずだ:

現時点では、ドラッグ&ドロップでValueTreeノードを作成し、データ構造の階層を変更できます。子ノードを展開したり折りたたんだりすることもできるが、変更を元に戻したりやり直したりすることはできない。この機能をUndoManagerクラスである。
ここで紹介するコードは、大まかに以下のものと似ている。バリューツリーデモJUCEデモより。
元に戻す/やり直しボタンの追加
まず、次の2つを加えてみよう。TextButtonオブジェクトをユーザー・インタフェースに追加し、アンドゥとリドゥの機能を使えるようにしました。このセクションではラムダ関数に慣れている必要があります。Tutorial: Listeners and Broadcastersチュートリアル
の中でMainContentComponentクラスを宣言します。TextButton各ボタンの変数[1]:
    juce::TreeView tree;
    juce::TextButton undoButton, redoButton;    // [1]
    std::unique_ptr rootItem;
コンストラクタのメンバー初期化リストで、以下のテキストを設定する。TextButtonオブジェクト[2]:
    MainContentComponent()
        : undoButton ("Undo"),
          redoButton ("Redo")   // [2]
    {
最後に、ボタンを表示させます。[3]に割り当てるラムダ関数を準備する。Button::onClickヘルパーオブジェクト[4]:
//...
addAndMakeVisible (undoButton);
addAndMakeVisible (redoButton);          // [3]
undoButton.onClick = [this] {  };
redoButton.onClick = [this] {  };        // [4]
 
setSize (600, 400);
次に、ボタンの境界をresized()メソッドを使用する:
    void resized() override
    {
        // This is called when the MainContentComponent is resized.
        // If you add any child components, this is where you should
        // update their positions.
 
        auto r = getLocalBounds();
 
        auto buttons = r.removeFromBottom (20);
        undoButton.setBounds (buttons.removeFromLeft (100));
        redoButton.setBounds (buttons.removeFromLeft (100));
 
        tree.setBounds (r);
    }
UndoManagerインスタンスを引数として渡す
以来ValueTreeクラスはアンドゥ/リドゥの動作を自動的に処理する。UndoManagerインスタンスをパラメータとして 登録します。UndoableActionオブジェクトのインスタンスを宣言する。これを実装するには、まずUndoManagerクラス[1]:
    juce::UndoManager undoManager;              // [1]
次に、対応するアンドゥ/リドゥの動作を処理するために、ボタンがクリックされたときに呼び出される関数を代入する。ラムダ関数では、それぞれ[UndoManager::undo()](https://docs.juce.com/master/classUndoManager.html#a39f45c284e8d0df1a0d378e676246931 "Tries to roll-back the last transaction.")そして[UndoManager::redo()](https://docs.juce.com/master/classUndoManager.html#aaea507a3b9eaea3360c0e393edf69ccb "Tries to redo the last transaction that was undone.")以下の通りである:
        addAndMakeVisible (undoButton);
        addAndMakeVisible (redoButton);                         // [3]
        undoButton.onClick = [this] { undoManager.undo(); };
        redoButton.onClick = [this] { undoManager.redo(); };    // [4]
 
        setSize (600, 400);
の中でValueTreeItemクラスへの参照も保持する。UndoManagerインスタンス[2]:
private:
    juce::ValueTree tree;
    juce::UndoManager& undoManager; // [2]
クラス・コンストラクタのメン バー初期化リストにUndoManager参照[3]:
    ValueTreeItem (const juce::ValueTree& v, juce::UndoManager& um)
        : tree (v), undoManager (um)    // [3]
    {
ValueTreeItemのサブ項目が再帰的に作成されるときは、必ずUndoManagerインスタンス[4]:
    void refreshSubItems()
    {
        clearSubItems();
 
        for (auto i = 0; i < tree.getNumChildren(); ++i)
            addSubItem (new ValueTreeItem (tree.getChild (i), undoManager));    // [4]
    }
これで、ルートValueTreeItemをインスタンス化できます。UndoManagerインスタンス[5]でのMainContentComponentクラスである:
        tree.setDefaultOpenness (true);
        tree.setMultiSelectEnabled (true);
        rootItem.reset (new ValueTreeItem (createRootValueTree(), undoManager));    // [5]
        tree.setRootItem (rootItem.get());
このように、3つの異なるメソッドがあり、それらを更新する必要がある。TreeView.
    void itemDropped (const juce::DragAndDropTarget::SourceDetails&, int insertIndex) override
    {
        juce::OwnedArray selectedTrees;
        getSelectedTreeViewItems (*getOwnerView(), selectedTrees);
 
        moveItems (*getOwnerView(), selectedTrees, tree, insertIndex, undoManager);     // [1]
    }
 
    static void moveItems (juce::TreeView& treeView, const juce::OwnedArray& items,
                           juce::ValueTree newParent, int insertIndex, juce::UndoManager& undoManager)
    {
                    v.getParent().removeChild (v, &undoManager);                        // [2]
                    newParent.addChild (v, insertIndex, &undoManager);                  // [3]
- [1]アイテムがドラッグ&ドロップされるたびに、移動を処理する静的関数にundoManagerを渡します。
- [2]このアクションをundoManagerに登録する必要がある。
- [3]次に、その子を新しい親に追加し、新しいアクションをundoManagerに登録します。
を通過させる。undoManagerについて言及する。ValueTree機能addChild()そしてremoveChild()とする。UndoManagerを実行する。UndoableActionを呼び出してください。perform()関数について説明する。私たちは以下をカバーする。UndoableActionオブジェクトは今後のチュートリアルで紹介する。
に保存されたアンドゥとリドゥの説明を表示します。LabelコンポーネントをそれぞれのTextButtonオブジェクトを使用する。getUndoDescription()そしてgetRedoDescription()関数をそれぞれ使用する。
イベントをトランザクションとして扱う
のもう1つの便利な機能である。UndoManagerは、複数のアクションを1つのアンドゥ/リドゥ・トランザクションとしてグループ化できる機能である。この機能はbeginNewTransaction()関数を使用する。undoManagerインスタンスへの呼び出しはすべてperform()の機能である。