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

チュートリアル:メインコンポーネント

📚 Source Page

このチュートリアルでは、メインコンテンツコンポーネントを作成してアプリケーションウィンドウにグラフィカルコンテンツを追加する方法を示します。これはウィンドウ内でユーザーにコンテンツを表示するために重要です。

レベル: 初級
プラットフォーム: Windows, macOS, Linux
クラス: DocumentWindow, Component, Graphics

はじめに

Projucer を起動し、MainComponentTutorialという名前で新しい GUI アプリケーションプロジェクトを作成します。Files to Auto-Generate:フィールドでCreate a Main.cpp file onlyを選択してください。

この手順でサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucer を始めようを参照してください。

以下のようにMainWindowクラスを含めるようにMainComponentTutorialApplicationクラスを変更します:

//==============================================================================
class MainComponentTutorialApplication : public juce::JUCEApplication
{
public:
//...

//==============================================================================
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow (juce::String name) : DocumentWindow (name,
juce::Colours::lightgrey,
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
centreWithSize (300, 200);
setVisible (true);
}

void closeButtonPressed() override
{
JUCEApplication::getInstance()->systemRequestedQuit();
}

private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};

private:
std::unique_ptr<MainWindow> mainWindow;
};

initialise()関数に以下の行を追加します:

void initialise (const juce::String& commandLine) override
{
mainWindow.reset (new MainWindow (getApplicationName()));
}

最後に、shutdown()関数に以下の行を追加します:

void shutdown() override
{
mainWindow = nullptr;
}

はじめに

前回のチュートリアル(チュートリアル:アプリケーションウィンドウ)では、アプリケーションのグラフィカルインターフェースが存在するフレームとなるメインウィンドウについて説明しました。このチュートリアルでは、アプリのインターフェースのコンテンツを表示するオブジェクトであるメインコンテンツコンポーネントを作成します。メインコンテンツコンポーネントはすべての JUCE アプリにとって必須のオブジェクトです。

Projucerで新しい GUI アプリケーションを作成すると、自動的にメインコンテンツコンポーネントが生成されます。しかし、この概念に慣れ、JUCE アプリがどのように構造化されているかを理解する良い方法は、そのようなメインコンテンツコンポーネントを自分で作成することです。これがこのチュートリアルで行うことです。

IDE でチュートリアルプロジェクトを開いてください。前回のチュートリアルで到達した同じ場所から始めます:空のアプリケーションウィンドウがある状態です。Main.cppファイルにはMainWindowクラスがあります。前回のチュートリアル(チュートリアル:アプリケーションウィンドウ)でその使い方をすでに学びました。今度は、このウィンドウにコンテンツを入れていきます!

しかし、その前に、まずコンポーネントの概念についてもう少し探ってみましょう。

Component クラス

すべての JUCE グラフィカルインターフェースの最も重要な基本クラスはComponentクラスです。JUCE では、ボタン、スライダー、テキストフィールドなど、GUI のほぼすべての可視要素がこのクラスから派生するコンポーネントです。JUCE でそのようなアプリを書く方法は、メインアプリケーションウィンドウが所有し、ウィンドウのコンテンツであるメインコンポーネントを作成することです。他のすべてのコンポーネントはこのメインコンポーネントのになります(チュートリアル:親コンポーネントと子コンポーネントを参照)。MainWindowが派生しているDocumentWindowクラスには、メインウィンドウがそのコンテンツ(メインコンポーネントとその子を含む)を正しく表示するために必要な機能が含まれています。

ヒント

覚えておいてください:JUCE のすべてのグラフィカル要素はComponentクラスから派生しています。GUI を構築するために、異なるコンポーネントがコンポーネントのネストされた階層に配置されます。最上位のコンポーネントはメインコンテンツコンポーネントと呼ばれます。詳細はチュートリアル:親コンポーネントと子コンポーネントを参照してください。

メインコンポーネントクラスの追加

新しいソースファイルの作成

では、メインコンポーネントクラスを作成しましょう。このためには、このクラスのソースコードが入る新しいファイルを作成する必要があります。Projucerに戻り、そこでチュートリアルプロジェクトを開きます。左側でFilesブラウザが開いていることを確認します。次に、Sourceグループ(新しい C++ソースファイルは常にここに入れるべきファイルグループ)を右クリックし、Add new Component class (split between CPP & header)..を選択します。Projucerは新しいComponentサブクラスの名前を尋ねます。ダイアログでMainComponentと入力し、Create Filesをクリックします。Projucerが 2 つの新しいファイルMainComponent.cppMainComponent.hを作成したことがわかります。プロジェクトを保存し、IDEで再度開きます。そこにも新しいファイルが表示されるはずです。Projucerは新しいコンポーネントクラス用のコードを自動的に作成しており、次のセクションで検討します。

Projucerで新しいコンポーネントクラスを追加
Projucerで新しいコンポーネントクラスを追加
ヒント

覚えておいてください:新しいクラスを作成する場合、それらは独自のファイルに入れ、ファイル名はクラス名と一致させるべきです。常にProjucerを使用して新しいファイルを作成してください。IDEからは絶対に作成しないでください(Projucerは次にプロジェクトを保存するときにそのような変更を上書きします)。

新しいコンポーネントクラス

ご覧のように、Projucerは自動的に新しいクラスをComponentクラスから派生させ、以下のクラス宣言を追加しました:

class MainComponent : public juce::Component
{
public:
MainComponent();
~MainComponent();

void paint (juce::Graphics&) override;
void resized() override;

private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Component基本クラスには、そこから派生するすべてのクラスでオーバーライドすべき 2 つの重要な仮想メンバー関数があります。Projucerはすでにこれら 2 つのオーバーライドを作成しています:

  • Component::paint():このメンバー関数はコンポーネントが画面上にどのように描画されるべきかを決定し、すべてのコンポーネントクラスで実装する必要があります。
  • Component::resized():このメンバー関数はコンポーネントがリサイズされたときに何が起こるべきかを決定し、すべてのコンポーネントクラスで実装する必要があります(アプリでこのコンポーネントが決してリサイズ可能にならないことが確実な場合を除く)。
ヒント

基本クラスの関数をオーバーライドするすべてのクラスの関数には、常にoverrideキーワードを追加してください。これにより、アプリでの予期しないエラーを防ぎ、JUCE コーディング標準の一部です。

paint 関数の実装

paint()関数はComponentオブジェクトが画面上にどのようにレンダリングされるかを決定します。ここでMainComponentクラスにカスタムの外観を追加します。

Projucerは自動的にいくつかのデモコードを追加しました。paint()関数に独自のコードを入れましょう。

void MainComponent::paint (juce::Graphics& g)
{
g.fillAll (juce::Colours::lightblue);

g.setColour (juce::Colours::darkblue);
g.setFont (20.0f);
g.drawText ("Hello, World!", getLocalBounds(), juce::Justification::centred, true);
}

このコードの詳細にはあまり立ち入る必要はありません。ここで使用されている関数(およびそれ以上)については、次のチュートリアルチュートリアル:Graphics クラスで詳しく学びます。今のところ、このデモコードがコンポーネントを水色の背景で塗りつぶし、次にコンポーネントの中央に青いフォントで**Hello, World!**というテキストをレンダリングすることはおそらく推測できるでしょう。ポイントは、MainComponentオブジェクトがどのように見えるべきかを決定するすべてのコードは、まさにここのpaint()関数内に入るということです。

メインコンポーネントを表示する

では、コードをコンパイルして実行してください。青い背景とテキストの代わりに、まだ空のアプリケーションウィンドウしか表示されていないことがわかります。なぜでしょうか?

実は、MainWindowオブジェクトにコンテンツを表示すべきだと伝えていなかったのです。まず、MainWindowクラスがMainComponentクラスについて知ることができるようにヘッダをインクルードする必要があります。Main.cppファイルの先頭、すでに存在するインクルードの下に以下のインクルードを追加します:

#include "MainComponent.h"

次のステップはMainComponentオブジェクトを作成し、メインウィンドウのコンテンツとして追加することです。DocumentWindow::setContentOwned()関数を呼び出すことでこれを行えます。

ヒント

「Owned」とは、MainWindowオブジェクトがMainComponentオブジェクトのライフタイムを担当し、自身のデストラクタが呼ばれたときに自動的に破棄することを意味します。

MainWindowクラスのコンストラクタに以下の行を追加します:

setContentOwned (new MainComponent(), true);

MainWindowコンストラクタが以下のようになるようにします:

MainWindow (juce::String name) : DocumentWindow (name,
juce::Colours::lightgrey,
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new MainComponent(), true);
centreWithSize (getWidth(), getHeight());
setVisible (true);
}

もう 1 つの詳細を変更したことに注意してください:Component::centreWithSize()関数への引数も変更されました。もうMainWindowオブジェクトのサイズを明示的に設定せず、そのコンテンツに基づいてサイズを判断するよう指示しています:

centreWithSize (getWidth(), getHeight());

しかし、これが機能するためには、centreWithSize()関数呼び出しが発生する前にMainComponentオブジェクトのサイズが設定されている必要があります。これが行われていない場合、メインウィンドウは適切なウィンドウサイズがわかりません(実行するとアサーション失敗が発生します)。次のセクションでこれを達成する方法を説明します。

コンポーネントのサイズの設定

原則として、Componentオブジェクトのサイズを設定する方法は 2 つあります。コンポーネント自身のコンストラクタでサイズを設定するか、親コンポーネントのコンストラクタでサイズを設定するかです。メインコンポーネントの場合、通常はコンポーネント自身でサイズを設定します。MainComponentクラスのコンストラクタに以下の行を追加します:

setSize (400, 300);

(もちろん、お好みで別のサイズを選択できます。)

警告

覚えておいてください:コンポーネントのサイズは常に設定してください。このステップを省略することは JUCE で非常によくあるバグの原因です。

これがMainWindowクラスで Component::getWidth()と Component::getHeight()関数の呼び出しがウィンドウのサイズを把握し、メインコンポーネントが正しいサイズで表示される理由です。MainComponentオブジェクトのサイズは、MainWindowオブジェクトが配置されサイズ設定される前に、独自のコンストラクタで設定されます。

これで必要な部品がすべて揃いました。今アプリをコンパイルして実行すると、メインコンポーネントがアプリケーションウィンドウ内に正しく描画されているのが見えるはずです:

新しいMainComponentオブジェクトが画面にレンダリングされる
新しいMainComponentオブジェクトが画面にレンダリングされる
注記

演習:setContentOwned()関数の 2 番目の引数(ここではtrueに設定)の意味と、変更した場合の動作を調べてください。ヒント:ResizableWindow::setContentOwned()関数のドキュメントを確認してください。

resize 関数の実装

paint()関数について説明したので、次にMainComponentクラスがリサイズに反応する方法を見てみましょう。

まず、メインウィンドウがリサイズ可能であるべきことを伝える必要があります。方法を忘れた場合はチュートリアル:アプリケーションウィンドウを参照してください。

では、アプリをコンパイルして実行し、マウスを使ってウィンドウをリサイズしてください。MainComponentオブジェクトがメインウィンドウのサイズに合わせて自身をリサイズすることがわかります --- これを行うために必要なすべてのコードは、Component基本クラスにすでに実装されています。

しかし、コンポーネントがリサイズされるたびにカスタム作業を行いたい場合はどうでしょうか?メインコンポーネントのサイズに応じて異なるレイアウトにする必要がある子コンポーネントがあるかもしれません。シンプルなデモアプリでは、メインコンポーネント内のテキストを変更して、コンポーネントの現在のサイズを表示するようにしましょう。

コンポーネントをリサイズするときに行う必要がある、または更新する必要があるものはすべて、コンポーネントのresized()関数に入ります。現在、この関数は空です。ここに機能を追加しましょう。

警告

Component::resized()関数は、コンポーネントのサイズ変更につながる何かが起こるたびに自動的に呼び出されます。この関数を自分で呼び出さないでください!

コンポーネント内に表示されるテキストは現在、paint()関数内のリテラル文字列 --- 「Hello, World!」--- として与えられています。MainComponentクラスに新しいメンバー変数を導入してこれを変更しましょう。変数には常に説明的で意図を明らかにする名前を付けることが良い習慣です。これによりコードが読みやすく理解しやすくなり、追加のコードコメントの量が減ります。新しい変数にメインコンポーネントの現在のサイズを文字列として表現させたいので、currentSizeAsStringと呼びましょう。

メンバー変数は常にクラスの private セクションで宣言します:

class MainComponent : public juce::Component
{
// ...

private:
juce::String currentSizeAsString;
// ...
};

では、currentSizeAsStringオブジェクトの望ましい動作を実装しましょう。行うべきことは 2 つあります:

  • currentSizeAsStringオブジェクトの内容を画面にレンダリングする必要があります。
  • currentSizeAsStringオブジェクトはメインコンポーネントのサイズが変更されるたびに自身を更新する必要があります。

最初の部分を達成する方法はかなり簡単です:paint()関数内でg.drawText()関数が呼び出されるとき、そこのリテラル文字列をcurrentSizeAsStringオブジェクトに置き換えるだけです:

void MainComponent::paint (juce::Graphics& g)
{
//...
g.drawText (currentSizeAsString, getLocalBounds(), juce::Justification::centred, true);
}

2 番目の部分はより興味深いです。リサイズするたびにresized()関数が呼び出されることはすでに知っています。そこでcurrentSizeAsStringオブジェクトの値を更新しましょう:

void MainComponent::resized()
{
currentSizeAsString = juce::String (getWidth()) + " x " + juce::String (getHeight());
}

Component::getWidth()と Component::getHeight()はコンポーネントの現在のサイズを照会できる便利な関数です。また、これらの整数をStringオブジェクトに変換する必要があります。(Stringクラスの操作方法については、将来のチュートリアルで詳しく学べます。)

今アプリをコンパイルして実行すると、常に現在のサイズが表示されることがわかります:

完成したデモアプリ
完成したデモアプリ

ここで 2 つの興味深い観察ができます。まず、表示は自動的に更新されます --- paint()関数はresized()関数が呼び出された後に自動的に呼び出されます。次に、ウィンドウを自分で最初にリサイズする前でも、アプリが起動したときにサイズはすでに正しく表示されています。resized()関数は、コンポーネントのサイズを変更する何かが起こるたびに常に呼び出されることを覚えておいてください。これには、アプリ起動後にコンポーネントのサイズが最初に設定されてコンポーネントが描画されるときも含まれます。

注記

演習:MainComponent::resize()関数を変更して、リサイズするたびにMainComponentオブジェクトの背景色も変更されるようにしてください。

チュートリアルプロジェクトの完成版はこちらからダウンロードできます:PIP | ZIP。自分のものと比較してみてください。

まとめ

このチュートリアルでは、メインコンポーネントの概念、アプリへの追加方法、paint()resized()関数の実装方法を説明しました。このチュートリアルを読んだ後、以下の重要なことに慣れているはずです:

  • すべての JUCE アプリケーションウィンドウにはメインコンポーネントがあります。これはアプリの GUI を構成する他のすべてのコンポーネントの親です。
  • メインコンポーネントを含むすべてのコンポーネントには、オーバーライドする必要がある 2 つの重要な関数があります:paint()resized()
  • paint()関数には、コンポーネントを画面にレンダリングするコードを追加する必要があります。
  • コンポーネントがサイズ変更に反応するための特別な動作が必要な場合は、resized()関数を実装する必要があります。
  • paint()resized()関数は、必要なときに自動的に呼び出されるコールバック関数です。
  • メインコンポーネントのサイズを設定し、メインウィンドウに追加して表示することを忘れないでください。

関連項目