高度なGUIレイアウトテクニック
シンプルかつ強力なテクニックを使ってコンポーネントをレイアウトすれば、バグが少なくエレガントなコードを作成できます。このテクニックでは、コンポーネントの矩形をさまざまな方法で何度か細分化し、コンポーネント全体をコンテンツで満たします。
レベル:中級
プラットフォーム:Windows, macOS, Linux, iOS, Android
クラス: Rectangle,TextButton,Colours
スタート
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
デモ・プロジェクト
デモ・プロジェクトでは、少数のボタン・コンポーネントを使用し、親コンポーネント内に配置しています。この例では、プレースホルダとしてボタンを使用していますが、JUCEコンポーネントであればどのようなものでも構いません。IDEからアプリケーションをビルドして実行すると、メインウィンドウは次のスクリーンショットのようになります。

長方形レイアウト
このシンプルなアプリケーションでは、メイン・ウィンドウにいくつかのセクションがあります:
- Aヘッダーセクションは、タイトルやツールバーを含むかもしれない。
- Aフッターセクションには、アプリケーションに関する他の情報が含まれているかもしれません。
- Aサイドバーこれは、一連のセクションやその他のコンテンツを含む可能性がある。
- ウィンドウの残りの部分には、いくつかのコンテンツが表示されます。
これらはMainContentComponent
コンストラクタ (Tutorial: Parent and child componentsそしてTutorial: Colours in JUCE):
MainContentComponent()
{
header.setColour (juce::TextButton::buttonColourId, juce::Colours::cornflowerblue);
header.setButtonText ("Header");
addAndMakeVisible (header);
footer.setColour (juce::TextButton::buttonColourId, juce::Colours::cornflowerblue);
footer.setButtonText ("Footer");
addAndMakeVisible (footer);
sidebar.setColour (juce::TextButton::buttonColourId, juce::Colours::grey);
sidebar.setButtonText ("Sidebar");
addAndMakeVisible (sidebar);
limeContent.setColour (juce::TextButton::buttonColourId, juce::Colours::lime);
addAndMakeVisible (limeContent);
grapefruitContent.setColour (juce::TextButton::buttonColourId, juce::Colours::yellowgreen);
addAndMakeVisible (grapefruitContent);
lemonContent.setColour (juce::TextButton::buttonColourId, juce::Colours::yellow);
addAndMakeVisible (lemonContent);
orangeContent.setColour (juce::TextButton::buttonColourId, juce::Colours::orange);
addAndMakeVisible (orangeContent);
setSize (400, 400);
}
実際のレイアウトは、Component::resized()をオーバーライドして行うのが一般的だ。
伝統的なレイアウト
従来は、さまざまな位置とサイズを計算し、それらの合計が常に正しいサイズになるように注意しながら、コンポーネントをレイアウトしていた。ウィンドウのメイン部分にカラーボタンを配置するのでさえ、面倒になるし、間違いやすい。同じ大きさのボタンを4つ並べるには、次のようにします:
//...
limeContent .setBounds (0, 0, 200, 24);
grapefruitContent.setBounds (0, 24, 200, 24);
lemonContent .setBounds (0, 48, 200, 24);
orangeContent .setBounds (0, 72, 200, 24);
//...
(このチュートリアルを書くにあたって、最終的にorangeContent
コンポーネントを2度間違えた! )
少なくとも、コーディングの労力をもっと重要なことに集中できるのに、計算には時間がかかる! そのRectangleクラスは、コンポーネントをレイアウトする作業をより柔軟に、ある意味では簡単にするための、シンプルかつ強力な機能をいくつか提供している。これは、メインの矩形をより小さなサブ矩形に細分化するものである。
長方形の細分化によるレイアウト
メインの長方形を細分化してコンポーネントをレイアウトする方法は、従来の方法と同等に思えるかもしれない。しかし、多くの利点がある:
- そのため、使用量を減らすことができる。マジックナンバー(ハードコードされた値)がコードに含まれていると、将来レイアウトを変更したり保守したりするのが難しくなります。
- 多く の場合、レイアウトは、値を変更する必要はなく、コードの順序を変えるだけで変更できる!
- レイアウトが親コンポーネントからはみ出したり、まったく埋まらなかったりするよりも、利用可能なスペースを正確に埋める方がはるかに簡単です。
- サイズ変更可能なコンポーネントを扱い、特定のセクションが少なくとも一定のサイズでなければならないというルールを作る方が簡単だ。
のコードはMainContentComponent::resized()
関数は次のようになる:
void resized() override
{
auto area = getLocalBounds();
auto headerFooterHeight = 36;
header.setBounds (area.removeFromTop (headerFooterHeight));
footer.setBounds (area.removeFromBottom (headerFooterHeight));
auto sidebarWidth = 80;
sidebar.setBounds (area.removeFromLeft (sidebarWidth)); // [2]
auto contentItemHeight = 24;
orangeContent.setBounds (area.removeFromTop (contentItemHeight));
limeContent.setBounds (area.removeFromTop (contentItemHeight)); // [1]
grapefruitContent.setBounds (area.removeFromTop (contentItemHeight));
lemonContent.setBounds (area.removeFromTop (contentItemHeight));
}
この関数の最初の数行を詳しく見てみよう。最初にローカル・バウンズComponent::getLocalBounds()関数を使用します。これは常に、位置 (0, 0) にあるコンポーネントと同じ幅と高さの矩形を返します:
auto area = getLocalBounds();
これは、子コンポーネントをレイアウトするために細分化する矩形である。最初の分割はヘッダーをレイアウトすることです:
header.setBounds (area.removeFromTop (headerFooterHeight));
ここでは、コンポーネント全体を表す長方形を取り出し、効果的に2つの長方形を作成します。そのRectangle::removeFromTop()関数は、元の矩形の位置に、同じ幅で、引数で要求された高さだけの矩形を返す。この場合、高さ36ピクセルの矩形を要求している。この関数が行うもうひとつのことはモディファイ元の矩形から先ほど返した矩形を取り除く。基本的には、矩形を上から36ピクセルでスライスし、上の矩形を返し、元の矩形を下の矩形と等しくなるように修正します。
単体ではこのようになる:

番目の細目は、フッターのレイアウトである:
footer.setBounds (area.removeFromBottom (headerFooterHeight));
についてRectangle::removeFromBottom()関数はRectangle::removeFromTop()ただし、メイン矩形の下部から矩形を削除し、上部の矩形を保持する。この時点で、私たちのコンポーネントは次のようになります:

- 次に、残りの 長方形の左から80ピクセルを削除してサイドバーを作成する。
- 次に、残りの矩形をRectangle::removeFromTop()関数である。
最後に、完全にレイアウトされたコンポーネントが完成する。
アイテムの並び替え
前述したように、このテクニックを使えばアイテムの並び替えは実に簡単だ。例えば、オレンジ色のコンテンツを一番上に移動させることができる。resized()
機能[1]:
auto contentItemHeight = 24;
orangeContent.setBounds (area.removeFromTop (contentItemHeight));
limeContent.setBounds (area.removeFromTop (contentItemHeight)); // [1]
grapefruitContent.setBounds (area.removeFromTop (contentItemHeight));
lemonContent.setBounds (area.removeFromTop (contentItemHeight));
こんな感じだ:

固定数のコンポーネントでは、このアプローチの方が明らかにエレガントだ。可変コンテンツをレンダリングする場合には、さらに有効である。
項目の並び替えだけではサイドバーを右側に移動させることはできない。Rectangle::removeFromRight()関数ではなくRectangle::removeFromLeft() [2]:
auto sidebarWidth = 80;
sidebar.setBounds (area.removeFromLeft (sidebarWidth)); // [2]
今はこうなっている:

コンポーネントのサイズ変更
このアプローチで得られるもうひとつの利点は、リサイズがしばしば「うまくいく」ことだ。これは、幅を広く、高さを低くしたコンポーネントです:

レイアウトの一部 または全部をプロポーショナルにしたい場合、コードに組み込むのは簡単だ。例えば、サイドバーの幅を常に全体の4分の1にしたい場合などです:
sidebar.setBounds (area.removeFromRight (area.getWidth() / 4));
これを試してみれば、有用な下限があることがわかるだろう。この方法を取り入れるのも簡単です。サイドバーの幅を全幅の4分の1に設定し、80ピクセルを下限とする:
sidebar.setBounds (area.removeFromRight (juce::jmax (80, area.getWidth() / 4)));
さらに異なる色のボタンをいくつか作成し、それらを水平に並べて、以下のセクションに追加する。orangeContent
,limeContent
,grapefruitContent
そしてlemonContent
コンポーネントを使用する。残りの幅いっぱいになるようにする。
その他のシナリオ
ここまでの例では、シーケンスの次のコンポーネントを配置するために、残りの矩形を細分化し続けてきた。サブ矩形の1つを保存して、代わりにそれを細分化する必要がある場合もあるでしょう。
例えば、リストに項目を配置するには内そのサイドバーこの例では、サイドバーの矩形を一時的に保存し、それを細分化する必要があります。これを説明するために、デモ・プロジェクトにさらに3つのコンポーネントを追加します。[3],[4]そして[5]:
private:
juce::TextButton header;
juce::TextButton sidebar;
juce::TextButton sideItemA; // [3]
juce::TextButton sideItemB; // [4]
juce::TextButton sideItemC; // [5]
juce::TextButton limeContent;
juce::TextButton grapefruitContent;
juce::TextButton lemonContent;
juce::TextButton orangeContent;
juce::TextButton footer;
次に、サイドバーのボタンからテキストを削除しながら、コンストラクタでそれらを設定する。[7]:
//...
sidebar.setColour (juce::TextButton::buttonColourId, juce::Colours::grey);
// [7]
addAndMakeVisible (sidebar);
sideItemA.setColour (juce::TextButton::buttonColourId, juce::Colours::maroon);
sideItemB.setColour (juce::TextButton::buttonColourId, juce::Colours::maroon);
sideItemC.setColour (juce::TextButton::buttonColourId, juce::Colours::maroon);
sideItemA.setButtonText ("Item A");
sideItemB.setButtonText ("Item B");
sideItemC.setButtonText ("Item C");
addAndMakeVisible (sideItemA);
addAndMakeVisible (sideItemB);
addAndMakeVisible (sideItemC);
//...
最後にresized()
関数を以下のように変更する:
void resized() override
{
auto area = getLocalBounds();
auto headerFooterHeight = 36;
header.setBounds (area.removeFromTop (headerFooterHeight));
footer.setBounds (area.removeFromBottom (headerFooterHeight));
auto sideBarArea = area.removeFromRight (juce::jmax (80, area.getWidth() / 4));
sidebar.setBounds (sideBarArea);
auto sideItemHeight = 40;
auto sideItemMargin = 5;
sideItemA.setBounds (sideBarArea.removeFromTop (sideItemHeight).reduced (sideItemMargin));
sideItemB.setBounds (sideBarArea.removeFromTop (sideItemHeight).reduced (sideItemMargin));
sideItemC.setBounds (sideBarArea.removeFromTop (sideItemHeight).reduced (sideItemMargin));
auto contentItemHeight = 24;
orangeContent.setBounds (area.removeFromTop (contentItemHeight));
limeContent.setBounds (area.removeFromTop (contentItemHeight));
grapefruitContent.setBounds (area.removeFromTop (contentItemHeight));
lemonContent.setBounds (area.removeFromTop (contentItemHeight));
}
の使用にも注目してほしい。Rectangle::reduced()この関数は矩形の端をはめ込み、矩形を効果的にマージン内に配置します。ア プリケーションをビルドして実行すると、次のようになります。

概要
このチュートリアルではRectangleクラスを使って矩形を細分化できる。特に、このテクニックを使ってコンポーネントをレイアウトすることを見てきた。できる:
- よりエレガントなコードでコンポーネントをレイアウトする。
- レイアウトコードのマジックナンバーの使用を減らす。
- 最小限のコード変更で、レイアウト位置やコンポーネントの配置順序を変更できます。