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

チュートリアル親子コンポーネント

📚 Source Page

このチュートリアルではComponentクラスでは、1つのコンポーネントが1つ以上のネストした子コンポーネントを含むことができます。これは、JUCEでユーザインタフェースをレイアウトする際の鍵となります。

レベル初心者

プラットフォーム:Windows, macOS, Linux, iOS, Android

クラス: Component,Path,Colours

スタート

注記

このチュートリアルはTutorial: The Graphics classそれは最初に読んで理解すべきだった。

このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

このステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

デモ・プロジェクトは、次のスクリーンショットのように、家の簡単な図面を含むシーンを表示します:

別々の要素からなるシーン。
別々の要素からなるシーン。

見覚えがあるだろうか?の結末とよく似ている。Tutorial: The Graphics class!ここでの違いは、各パーツがそれぞれ独立したComponentオブジェクトを使用します。paint()関数である。これからわかるように、これらは論理的にグループ化されている。例えば、家の壁と屋根はひとつの "house "オブジェクトにまとめられている。

これがどのように組み合わされるのか、なぜこのようにコンポーネントを構成するのが良いのか、その理由を探ってみよう。

Componentクラスの階層

ほとんどのユーザー・インターフェースは、テキスト、ボタン、スライダー、メニューなど、多くの要素で構成されています。例えば、次のスクリーンショットはAudioDeviceSelectorComponentクラス(オーディオのハードウェア設定を制御するクラス。Tutorial: The AudioDeviceManager classを参照してください)。これには、ボタン、ラベル、メニュー(コンボボックス)、ラジオボタン、オーディオレベルインジケータが含まれます。

オーディオハードウ�ェアの設定を制御するためのユーザーインターフェース。
オーディオハードウェアの設定を制御するためのユーザーインターフェース。

個々のユーザー・インターフェース要素の中には、他のユーザー・インターフェース要素をグループ化して、より有用なコントロールを形成するものもあります。例えば、JUCESliderクラスは、スライダー自体だけでなく、スライダーの現在値を示すテキストボックスも含むことができます。これを次のスクリーンショットに示します:

JUCE Sliderクラス。
JUCE Sliderクラス。

このような場合、個々の要素を別々の階層部分に分離することで、インターフェースのレイアウト設計(およびユーザーとのインタラクションへの対応)が非常に容易になる。コンポーネントによってはpaint()関数を使って描画する。他のコンポーネントは、単に他のコンポーネントを含んでいるだけかもしれない。あるコンポーネントは他のコンポーネントを含むかもしれないそしてドローイングを行う。デザインの選択肢はかなり柔軟だ。

MainContentComponentクラス

このチュートリアルではMainContentComponentクラスは、他のコンポーネント・クラスのインスタンスをメンバーとして含んでいる。これがSceneComponentクラスが実際のシーンを描く。クラスを見てみよう。MainContentComponentクラスが追加されます。SceneComponent オブジェクトは、プライベートメンバーとして追加されます:

private:
SceneComponent scene;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

子コンポーネントの追加

その中でMainContentComponentコンストラクタではSceneComponentオブジェクトが子供コンポーネントとMainContentComponentオブジェクトがその.

警告

子コンポーネントは常に1つの親を持たなければならない。必要であれば、子コンポーネントを親から削除し、別の親に追加することができます。

子コンポーネントを表示するためには、子コンポーネントを可視化する必要があります。この2つのステップは別々に実行することもできますが、Component::addAndMakeVisible()関数を使用して、これら2つのアクションを1つのステップで実行するのがJUCEの一般的なイディオムです:

    MainContentComponent()
{
addAndMakeVisible (scene);
setSize (600, 400);
}

子コンポーネントの境界を設定する

その一方でMainContentComponentクラスは構築時に自身のサイズを設定するため、多くのコンポーネント・オブジェクトの初期サイズはゼロです。Component::setSize()関数を呼び出すと、今度は私たちのMainContentComponent::resized()関数を使用します。これは、子コンポーネントのサイズと位置を設定するのに適した場所である:

    void resized() override
{
scene.setBounds (0, 0, getWidth(), getHeight());
}

ここでの重要なポイントは、以下の呼び出しの座標である。SceneComponent::setBounds()関数は、親コンポーネント(この場合はMainContentComponentオブジェクト)。これが意味するのは、親コンポーネントの左上隅が点(0, 0)子コンポーネントは、その左上隅がこの点に相対するように配置されます。実際にはSceneComponentオブジェクトの中身はすべてMainContentComponentオブジェクトを返します。別の書き方としては、Component::getLocalBounds()関数を使う方法があります。これはRectangleオブジェクトを呼び出します。この結果、位置(0, 0)と、その幅と高さを表すサイズを指定する。このRectangleオブジェクトに渡すことができる。SceneComponent::setBounds()関数を使用します。代替コードは以下のコード・スニペットに示されている:

void resized() override
{
scene.setBounds (getLocalBounds());
}

このチュートリアルの次のセクションは、このチュートリアルの構成を反映したものです。SceneComponentオブジェクトがある。

注記

子コンポーネントは親コンポーネントの境界を超えるように配置できますが、親コンポーネントの境界の外側にあるものはすべて描画されません。コンポーネントが表示されない場合は、境界が適切に設定されていることを確認してください(たとえば、親コンポーネントのresized()関数)。

その様子

についてSceneComponentクラスは独自の描画を行い、2つの子コンポーネント(床と家を表す)を含んでいる。子コンポーネントはSceneComponent宣言は以下の通りである:

class SceneComponent    : public juce::Component
{
// ...
private:
FloorComponent floor;
HouseComponent house;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SceneComponent)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...Definition juce_PlatformDefs.h:262

についてFloorComponentそしてHouseComponentオブジェクトが追加され、コンストラクタで可視化される:

    SceneComponent()
{
addAndMakeVisible (floor);
addAndMakeVisible (house);
}

空を描くために、コンポーネント全体を水色で塗りつぶす。SceneComponent::paint()関数である。

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

フロアとハウスの境界線は、その中に位置している。SceneComponent::resized()関数である:

    void resized() override
{
floor.setBounds (10, 297, 580, 5);
house.setBounds (300, 70, 200, 220);
}
注記

コンポーネントはまず自分自身を描画し、その上に子コンポーネントを描画します。子コンポーネントの上に描画する必要がある場合は、Component::paintOverChildren()関数をオーバーライドできます。子コンポーネントは、親コンポーネントに追加された順に描画されます。これは、Component::toFront(), Component::toBack(), Component::toBehind(), または Component::setAlwaysOnTop() 関数を使用して後で調整できます。

それぞれのクラスの中で、床と家がどのように描かれているかを見てみよう。

フロア

床は緑色の水平線で描かれる。Tutorial: The Graphics class)5ピクセルの厚さで、コンポーネント内の垂直方向の中央に配置され、その全幅にわたります。以下はFloorComponent::paint()関数 (FloorComponentクラス):

    void paint (juce::Graphics& g) override
{
g.setColour (juce::Colours::green);
g.drawLine (0.0f, (float) getHeight() / 2.0f, (float) getWidth(), (float) getHeight() / 2.0f, 5.0f);
}

この家自体は、それ自体で絵を描くことはない。paint()関数)であるが、他の2つのコンポーネント(家の壁と屋根を表す)で構成されている。HouseComponentクラスである:

class HouseComponent   : public juce::Component
{
// ...
private:
WallComponent wall;
RoofComponent roof;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HouseComponent)
};

についてWallComponentそしてRoofComponentオブジェクトが追加され、コンストラクタで可視化される:

    HouseComponent()
{
addAndMakeVisible (wall);
addAndMakeVisible (roof);
}

これらは、比例して配置されている。HouseComponent::resized()関数である:

    void resized() override
{
auto separation = juce::jlimit (2, 10, getHeight() / 20); // [1]

roof.setBounds (0, 0, getWidth(), (int) (getHeight() * 0.2) - separation / 2); // [2]
wall.setBounds (0, (int) (getHeight() * 0.20) + separation / 2, getWidth(), (int) (getHeight() * 0.80) - separation); // [3]
}
  • [1]まず、屋根と壁の間隔を計算します。これを1⁄20を使用して、家の高さを2ピクセル以下にします。jlimit()機能である。このため、高さが小さくても屋根と壁の間には常に隙間がある。高さが大きい場合、隙間は高さに比例する。
  • [2]屋根は家の全幅に設定されている。1⁄5家の高さのこれは離れを考慮して調整される。
  • [3]壁は屋根の下に配置されている。4⁄5家の高さのこれはまた、離れを考慮して調整される。

についてWallComponentクラスはシンプルだ。このクラスは、市松模様で自分自身を塗りつぶすだけである。WallComponent::paint()関数 (WallComponentクラス):

    void paint (juce::Graphics& g) override
{
g.fillCheckerBoard (getLocalBounds().toFloat(), 30, 10,
juce::Colours::sandybrown, juce::Colours::saddlebrown);
}

屋根

についてRoofComponentクラスはPathオブジェクトをRoofComponent::paint()関数である:

    void paint (juce::Graphics& g) override
{
g.setColour (juce::Colours::red);

juce::Path roof;
roof.addTriangle (0.0f, (float) getHeight(), (float) getWidth(), (float) getHeight(), (float) getWidth() / 2.0f, 0.0f);
g.fillPath (roof);
}

の幅をRoofComponentオブジェクトwと高さhとすれば、三角形を構成する3点は次のようになる:(0, h),(w, h),(w⁄2, 0).

エクササイズ

シーン内のオブジェクトの位置を変えてみてください。

太陽の追加

シーンに太陽を追加してみよう。いくつかの空の関数がSunComponentクラスにコードを追加する。

まず、以下の部分を変更する必要がある。SceneComponentクラスのインスタンスを追加する。のインスタンスを追加する。SunComponentクラスをプライベート・セクション[4]:

private:
FloorComponent floor;
HouseComponent house;
SunComponent sun; // [4]

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SceneComponent)
};

次に、太陽を追加し、それが見えるようにする必要がある。[5]:

    SceneComponent()
{
addAndMakeVisible (floor);
addAndMakeVisible (house);
addAndMakeVisible (sun); // [5]
}

そして、太陽を右上に配置する。[6]:

    void resized() override
{
floor.setBounds (10, 297, 580, 5);
house.setBounds (300, 70, 200, 220);
sun .setBounds (530, 10, 60, 60); // [6]
}

描画コードをSunComponent::paint()関数のSunComponentクラス):

    void paint (juce::Graphics& g) override
{
g.setColour (juce::Colours::yellow);

auto lineThickness = 3.0f;
g.drawEllipse (lineThickness * 0.5f,
lineThickness * 0.5f,
(float) getWidth() - lineThickness * 2,
(float) getHeight() - lineThickness * 2,
lineThickness);
}

楕円をコンポーネントの境界内にわずかに配置する必要があることに注意してください。これは線の太さに依存するはずです。これは、指定された座標に正確に位置する線の中心だからです。例えば、コンポーネントの端に線を引いた場合、線の太さの半分はコンポーネントの枠外に位置することになります。楕円の位置とサイズを少し調整しないとどうなるか、次のスクリーンショットをご覧ください。

描画はコンポーネントの境界にクリップされる。
描画はコンポーネントの境界にクリップされる。
注記

これを回避するもう1つの方法は、Component::setPaintingIsUnclipped()関数を使用して、コンポーネントがその境界を超えて描画できるようにすることです。必ずAPIこの関数の使用にはいくつかの注意点があるため、ドキュメントを参照のこと。

最終的なシーンはこのようになるはずだ:

最後のシーンだ。
最後のシーンだ。
注記

このステップで変更されたコードはComponentParentsChildrenTutorial_02.hファイルにある。

エクササイズ

上のコードを使うと、太陽は常に楕円として描かれる。このコードではSunComponentオブジェクトを正方形にすると、この潜在的な問題に気づかない。を修正する。SunComponentクラスは、その幅と高さが同じでなくても、楕円ではなく常にその境界内に円を描くようにする。

コンポーネントの再利用

で使用される座標系の主な利点のひとつは、この座標系を使用することである。Componentクラスでは、描画は常にコンポーネントの左上を基準として行われる。描画を別のクラスにカプセル化するもう1つの利点は、簡単に再利用できることです。

例えば、次のように簡単に家を増やすことができる。[7]に対するSceneComponentクラスである:

private:
FloorComponent floor;
HouseComponent house;
HouseComponent smallHouse; // [7]
SunComponent sun;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SceneComponent)
};

そして、それを追加し、見えるようにする。[8]でのSceneComponentビルダー

    SceneComponent()
{
addAndMakeVisible (floor);
addAndMakeVisible (house);
addAndMakeVisible (smallHouse); // [8]
addAndMakeVisible (sun);
}

そして、それを配置する[9]でのSceneComponent::resized()関数である:

    void resized() override
{
floor .setBounds (10, 297, 580, 5);
house .setBounds (300, 70, 200, 220);
smallHouse.setBounds (50, 50, 50, 50); // [9]
sun .setBounds (530, 10, 60, 60);
}

小さな家を追加すると、シーンはこのようになるはずだ:

私たちの最後のシーンには、さらに小さな家がある。
私たちの最後のシーンには、さらに小さな家がある。
注記

この最後のステップで変更されたコードはComponentParentsChildrenTutorial_03.hファイルにある。

エクササイズ

新しいクラスを作るStreetComponentを数多く含んでいる。HouseComponentオブジェクトを並べて通りを形成し、プロジェクトに追加する。を修正する。SceneComponentクラスは、いくつかの道路と個々の家を含んでいる。

概要

このチュートリアルでは、"親 "と "子 "の階層システムを紹介した。Componentクラスである。特に、私たちは学んだ:

  • Component::addAndMakeVisible() 関数を使用して、子コンポーネントを他のコンポーネントに追加する方法。
  • 親コンポーネントに対する子コンポーネントの相対的な位置とサイズの設定方法。
  • そのコンポーネントは、それ自身が描画を行うことも、描画を行う子コンポーネントを含むことも、その両方を行うこともできる。
  • そのコンポーネントが最初に描画を実行し、次に子コンポーネントが親コンポーネントに追加された順に描画される。
  • 描画は通常、コンポーネントの境界にクリッピングされる。

こちらも参照