メインコンテンツまでスキップ

チュートリアルValueTreeでUndoManagerを使う

📚 Source Page

アプリケーションに取り消し/やり直しアクションを実装します。で以前の中間状態を簡単に復元できます。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()関数をそれぞれ使用する。

イベントをトランザクションとして扱う

のもう一つの便利な機能である。UndoManagerは、複数のアクションを1つのアンドゥ/リドゥ・トランザクションとしてグループ化できる機能である。この機能はbeginNewTransaction()関数を使用する。undoManagerインスタンスへの呼び出しはすべてperform()の機能である。UndoManagerは、次のbeginNewTransaction()コール。

例としてTimerを呼び出す。beginNewTransaction()関数を定期的に実行し、アクションのグループをトランザクションとしてまとめて保存する。トランザクションはMainContentComponentを継承する。Timerタイマー・コールバックを受け取るクラス[1]:

class MainContentComponent   : public juce::Component,
public juce::DragAndDropContainer,
private juce::Timer // [1]
{
public:

対応するヘッダーファイルでコールバック関数を宣言する。[2]:

    void timerCallback() override               // [2]
{
undoManager.beginNewTransaction(); // [4]
}

コンストラクタで、トランザクション呼び出しの間隔をミリ秒単位で指定してタイマーを開始する。[3]:

        startTimer (500);       // [3]
}

最後にbeginNewTransaction()関数を使用する。UndoManagerタイマーのコールバックで[4]:

    void timerCallback() override               // [2]
{
undoManager.beginNewTransaction(); // [4]
}
エクササイズ

アクションのグループを区切るためにタイマーを使用する代わりに、取り消し/やり直しアクション5回ごとにトランザクションを実装する。

注記

この修正版のソースコードはUndoManagerTutorial_02.hファイルにある。

概要

このチュートリアルを完了することで、アプリケーションの以前の状態を復元する方法を学びました。特に

  • 保存UndoableActionオブジェクトをUndoManagerオブジェクトがある。
  • 合格UndoManagerインスタンスをValueTreeアクセス機能。
  • 取り消し/やり直しアクションのグループをトランザクションとして処理。

参照