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

チュートリアル高度なGUIレイアウトテクニック

シンプルかつ強力なテクニックを使ってコンポーネントをレイアウトすれば、バグが少なくエレガントなコードを作成できます。このテクニックでは、コンポーネントの矩形をさまざまな方法で何度か細分化し、コンポーネント全体をコンテンツで満たします。

レベル:中級

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

クラス: 長方形, テキストボタン, カラー

はじめる

Download the demo project for this tutorial here: ピップ | ジップ. Unzip the project and open the first header file in the Projucer.

If you need help with this step, see チュートリアルProjucerパート1:Projucerを始める.

The demo project

デモ・プロジェクトでは、少数のボタン・コンポーネントを使用し、親コンポーネント内に配置しています。この例では、プレースホルダとしてボタンを使用していますが、JUCEコンポーネントであればどのようなものでも構いません。IDEからアプリケーションをビルドして実行すると、メインウィンドウは次のスクリーンショットのようになります。

Simple application layout
Simple application layout

Rectangle layouts

このシンプルなアプリケーションでは、メイン・ウィンドウにいくつかのセクションがあります:

  • A ヘッダー section that might contain a title or perhaps a toolbar.
  • A フッター section that might contain some other information about the application.
  • A サイドバー that might contain a series of sections or other content.
  • ウィンドウの残りの部分には、いくつかのコンテンツが表示されます。

These are added in the メインコンテンツコンポーネント constructor (see チュートリアル親子コンポーネント and チュートリアル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 ("フッター");
addAndMakeVisible (footer);

sidebar.setColour (juce::TextButton::buttonColourId, juce::Colours::grey);
sidebar.setButtonText ("サイドバー");
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()をオーバーライドして行うのが一般的だ。

Traditional laying out

従来は、さまざまな位置とサイズを計算し、それらの合計が常に正しいサイズになるように注意しながら、コンポーネントをレイアウトしていた。ウィンドウのメイン部分にカラーボタンを配置するのでさえ、面倒になるし、間違いも起こしやすい。同じ大きさのボタンを4つ並べるには、次のようにします:

//...
limeContent .setBounds (0, 0, 200, 24);
グレープフルーツContent.setBounds(0、24、200、24);
lemonContent .setBounds (0, 48, 200, 24);
orangeContent .setBounds (0, 72, 200, 24);
//...

(I proved my own point when writing this tutorial by getting the final オレンジコンテンツ component in the wrong place twice!)

At the very least the calculations are time-consuming when you can be focusing your coding efforts on more important things! The 長方形 class provides some simple yet powerful features for making the job of laying out components more flexible, and in some ways easier, once you are familiar with the technique. This involves subdividing the main rectangle into smaller and smaller sub-rectangles.

Layout by subdividing rectangles

メインの長方形を細分化してコンポーネントをレイアウトする方法は、従来の方法と同等に思えるかもしれない。しかし、多くの利点がある:

  • It encourages you to use fewer マジックナンバー (hard-coded values) in your code, which make modifying and maintaining your layout harder in the future.
  • 多くの場合、レイアウトは、値を変更する必要はなく、コードの順序を変えるだけで変更できる!
  • レイアウトが親コンポーネントからはみ出したり、まったく埋まらなかったりするよりも、利用可能なスペースを正確に埋める方がはるかに簡単です。
  • サイズ変更可能なコンポーネントを扱い、特定のセクションが少なくとも一定のサイズでなければならないというルールを作る方が簡単だ。

The code for the MainContentComponent::resized() function in the demo application looks like this:

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] グレープフルーツ。
グレープフルーツContent.setBounds(area.removeFromTop(contentItemHeight));
lemonContent.setBounds(area.removeFromTop(contentItemHeight));
}

Let's look in detail at the first few lines of this function. First we get the ローカル・バウンズ of the component we are laying out, using the Component::getLocalBounds() function. This always returns a rectangle that is at position (0, 0) with the same width and height as the component:

これは、子コンポーネントをレイアウトするために細分化する矩形である。最初の分割はヘッダーをレイアウトすることです:

Here we take the rectangle that represents the whole component and effectively create two rectangles. The 矩形::removeFromTop() function returns a rectangle that is at the position of the original rectangle, the same width, but only the height requested by the argument. In this case we ask for a rectangle that is 36 pixels high. The other thing that this function does is that it モディファイ the original rectangle removing the rectangle that we just returned. Essentially, it slices the rectangle at 36 pixels from the top, returns the upper rectangle and modifies the original rectangle to be equal to the lower rectangle.

単体ではこのようになる:

Our first subdivision
Our first subdivision

番目の細目は、フッターのレイアウトである:

The 矩形::removeFromBottom() function does the same as the 矩形::removeFromTop() function, except it removes a rectangle from the bottom of the main rectangle and retains the upper rectangle. At this point, our component looks like this:

Our second subdivision
Our second subdivision
  • 次に、残りの長方形の左から80ピクセルを削除してサイドバーを作成する。
  • We then subdivide the remaining rectangle multiple times by using the 矩形::removeFromTop() function.

最後に、完全にレイアウトされたコンポーネントが完成する。

Reordering items

As mentioned earlier, it is really easy to reorder items using this technique. For example, we could move the orange content at the top simply by listing it first in the リサイズ function [1]:

auto contentItemHeight = 24;
orangeContent.setBounds (area.removeFromTop (contentItemHeight));
limeContent.setBounds (area.removeFromTop (contentItemHeight)); // [1].
グレープフルーツContent.setBounds(area.removeFromTop(contentItemHeight));
lemonContent.setBounds(area.removeFromTop(contentItemHeight));

こんな感じだ:

Reordering items
Reordering items
注記

固定数のコンポーネントでは、このアプローチの方が明らかにエレガントだ。可変コンテンツをレンダリングする場合には、さらに有効である。

We can't move the sidebar to the right-hand side simply by reordering items, but it is just matter of using the 矩形::removeFromRight() function rather than 矩形::removeFromLeft() [2]:

auto sidebarWidth = 80;
sidebar.setBounds (area.removeFromLeft (sidebarWidth)); // [2].

今はこうなっている:

Moving the sidebar to the right
Moving the sidebar to the right

Resizing the component

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

Resizing our simple layout
Resizing our simple layout

レイアウトの一部または全部をプロポーショナルにしたい場合、コードに組み込むのは簡単だ。例えば、サイドバーの幅を常に全体の4分の1にしたい場合などです:

sidebar.setBounds (area.removeFromRight (area.getWidth() / 4));

これを試してみれば、有用な下限があることがわかるだろう。この方法を取り入れるのも簡単です。サイドバーの幅を全幅の4分の1に設定し、80ピクセルを下限とする:

sidebar.setBounds (area.removeFromRight (juce::jmax (80, area.getWidth() / 4)));

Create several more buttons with different colours and add them, arranged horizontally, to the section below the オレンジコンテンツ, ライムコンテンツ, グレープフルーツコンテンツ, and レモンコンテンツ components. Make them fill the entire remaining width.

Other scenarios

ここまでの例では、シーケンスの次のコンポーネントを配置するために、残りの矩形を細分化し続けてきた。サブ矩形の1つを保存して、代わりにそれを細分化する必要がある場合もあるでしょう。

For example, to place items in a list the サイドバー in our example, we would need to store the sidebar rectangle temporarily, then subdivide that. To illustrate this, add three more components to the demo project [3], [4], and [5]:

private:
juce::TextButton header;
juce::TextButton サイドバー;

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 フッター;

Then configure them in the constructor, while removing the text from the sidebar button [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 ("アイテムA");
sideItemB.setButtonText ("アイテムB");
sideItemC.setButtonText(「アイテムC」);
addAndMakeVisible (sideItemA);
addAndMakeVisible (sideItemB);
addAndMakeVisible (sideItemC);
//...

Finally, change the リサイズ function to the following:

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));
グレープフルーツContent.setBounds(area.removeFromTop(contentItemHeight));
lemonContent.setBounds(area.removeFromTop(contentItemHeight));
}

Notice also the use of the 矩形::縮小() function which insets the edges of the rectangle, effectively placing the rectangle within a margin. Build and run the application and it should now look like this.

More sophisticated subdivisions
More sophisticated subdivisions

概要

In this tutorial we have explored the use of a particular set of functions within the 長方形 class for subdividing rectangles. In particular, we have seen that using this technique for laying out components. We can:

  • よりエレガントなコードでコンポーネントをレイアウトする。
  • レイアウトコードのマジックナンバーの使用を減らす。
  • 最小限のコード変更で、レイアウト位置やコンポーネントの配置順序を変更できます。

関連項目