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

チュートリアルTableListBoxクラス

📚 Source Page

JUCEユーザー・インターフェースにテーブルを組み込む。XMLファイルから読み込んだデータを表示し、テーブルのフォーマットをカスタマイズできます。

レベル:中級

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

クラス: TableListBox,TableListBoxModel,ListBox,ListBoxModel,TableHeaderComponent,XmlDocument,XmlElement

スタート

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

警告

このプロジェクトのPIPバージョンを使用する場合は、必ずResourcesフォルダを生成されたProjucerプロジェクトに追加します。

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

デモ・プロジェクト

デモ・プロジェクトは、JUCE モジュールに関する情報を含む XML ファイルから読み込まれたテーブルを表示します。この表は、好きな列でソートしたり、特定の項目を編集したり、列を非表示にしたりすることができます。

アプリのウィンドウ
アプリのウィンドウ

ListBox クラス

ベースComponentJUCEでテーブルを表示するためのクラスはListBoxクラスである。そのListBoxによって管理される。ListBoxModelクラスを使うことができます。これはスクロール可能なビューポートにアイテムのリストを表示するのに便利ですが、これをカラムを記述するヘッダーを持つ適切なテーブルに変えるには、それぞれTableListBoxそしてTableListBoxModelクラスがある。これらのクラスは、対応するクラスと同じ振る舞いをカプセル化しますが、代わりにTableHeaderComponentでカラムヘッダーを表示します。

を実施する場合TableListBoxを継承しTableListBoxModelオーバーライドする機能はいくつかある:

  • getNumRows()テーブルの現在の行数を返す必要がある。
  • paintRowBackground()を使用する。Graphicsコンテキストが提供された場合、行番号で指定された行の背景を描画しなければならない。
  • paintCell()を使用する。Graphicsコンテキストが提供されたら、行番号と列番号で指定されたセルを描画しなければならない。
  • refreshComponentForCell()オプションでこのメソッドをオーバーライドして、テーブルのカスタムコンポーネントを作成したり更新したりすることができます。
  • getColumnAutoSizeWidth()カラムの幅にオートサイズを使用する場合、このメソッドはカラムのサイズ変更方法をオプションで指定できます。
  • sortOrderChanged()カスタムソートを使用する場合は、カラムの並べ替え方法をオプションで指定できます。

オプションでオーバーライドして機能を追加できる関数は他にもあるが、このチュートリアルではここで紹介する関数を実装する。

XMLからデータを読み込む

まず、XMLドキュメントからテーブルに表示したいデータをロードすることから始めよう。

の中でResourcesというファイルに、このチュートリアルで使用するサンプルデータがあります。TableData.xmlには、次のような形式の情報が含まれている:




//...



//...


ここでは、ファイル全体をTABLE_DATAタグを使い、テーブルのヘッダーと実際のデータはHEADERSそしてDATAタグで定義されている。カラムは個々のCOLUMNタグで定義され、行はITEMタグは、そのエントリーの各列のコンテンツに属性を使用します。

このプロジェクトのコードの実装は、このファイル構造に合わせてあるが、XMLタグは好きなように変更できる。

の中でTableTutorialComponentクラスでは、ファイルの内容を一時的に保存するポインタを定義しています。XmlElementメンバ変数に2つのXmlElementを追加し、列の内容と行の内容を定義する:

class TableTutorialComponent    : public juce::Component,
public juce::TableListBoxModel
{
    std::unique_ptr tutorialData;
juce::XmlElement* columnList = nullptr;
juce::XmlElement* dataList = nullptr;

クラスのコンストラクタで[FileChooser](https://docs.juce.com/master/classFileChooser.html "Creates a dialog box to choose a file or directory to load or save.")で表示するデータファイルを選択する。を選択する。[FileChooser](https://docs.juce.com/master/classFileChooser.html "Creates a dialog box to choose a file or directory to load or save.")が完了するとcallbackラムダ関数で、ファイルの内容を[XmlElement](https://docs.juce.com/master/classXmlElement.html "Used to build a tree of elements representing an XML document.")オブジェクト[1].XMLファイルからデータを読み込んだら、for()ループと[XmlElement::getChildIterator](https://docs.juce.com/master/classXmlElement.html#ac620244d67b05fb572a37bc5f01f6d0d "Allows iterating the children of an XmlElement using range-for syntax.")を使用してテーブル・ヘッダーを割り当てます。addColumn()機能[2]このように:

    TableTutorialComponent()
{
const auto callback = [this] (const juce::FileChooser& chooser)
{
loadData (chooser.getResult()); // [1]
            if (columnList != nullptr)
{
for (auto* columnXml : columnList->getChildIterator())
{
table.getHeader().addColumn (columnXml->getStringAttribute ("name"), // [2]
columnXml->getIntAttribute ("columnId"),
columnXml->getIntAttribute ("width"),
50,
400,
juce::TableHeaderComponent::defaultFlags);
}
}

この関数は、カラムの名前、幅、IDを、カラムのソート可能性とリサイズ可能性を定義するプロパティ・フラグとともに指定する。

の中でloadData()コンストラクタ内で呼び出されたヘルパー関数を見つけるために、まずResourcesディレクトリとXMLファイルをロードし、パースする。Fileオブジェクトを使用する。parse()機能[3].次に、行と列を一時的なXmlElementXML構造を走査し、対応するタグをgetChildByName()機能[4].これは、データから行数を設定するのに良い場所です。XmlElement電話でgetNumChildElements()それについて[5]以下の通りである:

    void loadData (juce::File tableFile)
{
if (tableFile == juce::File() || ! tableFile.exists())
return;

tutorialData = juce::XmlDocument::parse (tableFile); // [3]

dataList = tutorialData->getChildByName ("DATA");
columnList = tutorialData->getChildByName ("HEADERS"); // [4]

numRows = dataList->getNumChildElements(); // [5]
}
警告

このチュートリアルのリソースで提供されている "TableData.xml "ファイルを必ず選択してください。

というヘルパー関数も定義しておこう。getAttributeNameForColumnId()これは後で便利になる:

    juce::String getAttributeNameForColumnId (const int columnId) const
{
for (auto* columnXml : columnList->getChildIterator())
{
if (columnXml->getIntAttribute ("columnId") == columnId)
return columnXml->getStringAttribute ("name");
}

return {};
}

ここでは、子XML要素を繰り返し処理し、一致するカラムIDを見つけてそのname属性を返します。

カスタムセルコンポーネント

ATableListBoxは、テキスト以外のカスタム・コンポーネントをセルに保持することができる。以下のセクションでToggleButtonをカラムの1つに入れ、編集可能なLabelユーザーの入力をリッスンする。

編集可能なラベル

の中でEditableTextCustomComponentクラスを継承する。Labelクラスをダブルクリックすると編集可能になるように設定します:

    class EditableTextCustomComponent  : public juce::Label
{
public:
EditableTextCustomComponent (TableTutorialComponent& td)
: owner (td)
{
setEditable (false, true, false);
}
    private:
TableTutorialComponent& owner;
int row, columnId;
juce::Colour textColour;
};

ここでは、このオブジェクトがどの行と列に表示されるかを記録し、実際のテーブルへの参照も保持する。

ユーザーがLabelを拡張しなければならない。mouseDown()機能により、テーブルの複数選択を考慮することができる。これはselectRowsBasedOnModifierKeys()を呼び出し、モディファイア・キーを引数として渡す。ここでは、元の動作を維持するためにベース・クラスの関数を呼び出す必要があることに注意してください:

        void mouseDown (const juce::MouseEvent& event) override
{
owner.table.selectRowsBasedOnModifierKeys (row, event.mods, false);

Label::mouseDown (event);
}

ユーザーがLabelからのコールバックを受け取る。textWasEdited()関数を呼び出し、ヘルパー関数setText()で定義されている。TableTutorialComponentクラスで変更した内容を、対応するXmlElementオブジェクトのようなものだ:

        void textWasEdited() override
{
owner.setText (columnId, row, getText());
}

以下の関数は[TableListBoxModel](https://docs.juce.com/master/classTableListBoxModel.html "One of these is used by a TableListBox as the data model for the table's contents.")を作成または更新するときにEditableTextCustomComponentオブジェクトから行、列、表示テキストを設定する。XmlElementを使用している。getText()の後に定義されたヘルパー関数である:

        void setRowAndColumn (const int newRow, const int newColumn)
{
row = newRow;
columnId = newColumn;
setText (owner.getText(columnId, row), juce::dontSendNotification);
}

についてgetText()そしてsetText()ヘルパー関数は以下のように定義されている:

    juce::String getText (const int columnNumber, const int rowNumber) const
{
return dataList->getChildElement (rowNumber)->getStringAttribute (getAttributeNameForColumnId (columnNumber));
}

ここでは、子要素に含まれる行番号と列番号からテキストを見つける。XmlElement.

    void setText (const int columnNumber, const int rowNumber, const juce::String& newText)
{
const auto& columnName = table.getHeader().getColumnName (columnNumber);
dataList->getChildElement (rowNumber)->setAttribute (columnName, newText);
}

ここでは、テキストをXmlElement行番号と列番号から。

選択可能なボタン

の中でSelectionColumnCustomComponentクラスを継承する。Componentクラスを設定する。ToggleButton幼いころComponentにコールバック関数を割り当てて、ユーザがその関数を操作したときに呼び出されるようにします:

    class SelectionColumnCustomComponent    : public Component
{
public:
SelectionColumnCustomComponent (TableTutorialComponent& td)
: owner (td)
{
addAndMakeVisible (toggleButton);

toggleButton.onClick = [this] { owner.setSelection (row, (int) toggleButton.getToggleState()); };
}
    private:
TableTutorialComponent& owner;
juce::ToggleButton toggleButton;
int row, columnId;
};

ここでは、このオブジェクトがどの行と列に表示されているかを追跡し、実際のテーブルへの参照も保持する。ラムダ関数はsetSelection()ヘルパー関数はTableTutorialComponentボタンのトグル状態を設定する。

        void resized() override
{
toggleButton.setBoundsInset (juce::BorderSize (2));
}

についてresized()関数はToggleButtonオブジェクトがある。

以下の関数は[TableListBoxModel](https://docs.juce.com/master/classTableListBoxModel.html "One of these is used by a TableListBox as the data model for the table's contents.")を作成または更新する際にSelectionColumnCustomComponentオブジェクトから行、列、トグル状態を設定する。XmlElementを使用している。getSelection()の後に定義されたヘルパー関数である:

        void setRowAndColumn (int newRow, int newColumn)
{
row = newRow;
columnId = newColumn;
toggleButton.setToggleState ((bool) owner.getSelection (row), juce::dontSendNotification);
}

についてgetSelection()そしてsetSelection()ヘルパー関数は以下のように定義されている:

    int getSelection (const int rowNumber) const
{
return dataList->getChildElement (rowNumber)->getIntAttribute ("Select");
}

ここでは、子要素に含まれる行番号と列番号から、トグル状態を見つける。XmlElement.

    void setSelection (const int rowNumber, const int newSelection)
{
dataList->getChildElement (rowNumber)->setAttribute ("Select", newSelection);
}

ここでは、トグル状態をXmlElement行番号と列番号から。

エクササイズ

を組み込んだ追加のカスタム・セル・コンポーネントを作成する。ComboBox,TextButtonあるいはSliderコンポーネントを使用している。

データの並べ替え

テーブルが選択されたカラムに基づいて要素をソートするためには、コンパレータ・クラスを定義しなければならない。sortChildElements()の機能である。XmlElementオブジェクトがある。

このクラスをTutorialDataSorterの名前を記録している。XmlElement属性と、ソートアルゴリズムの昇順または降順の方向は以下の通り:

    class TutorialDataSorter
{
public:
TutorialDataSorter (const juce::String& attributeToSortBy, bool forwards)
: attributeToSort (attributeToSortBy),
direction (forwards ? 1 : -1)
{}
    private:
juce::String attributeToSort;
int direction;
};

についてはsortChildElements()関数を使用してTutorialDataSorterという名前の関数を宣言しなければならない。compareElements()それは2つあるXmlElementオブジェクトを返します。

この関数は戻る必要がある:

  • が2番目より前なら負の値
  • 2つのオブジェクトが等価であれば、値は0になる
  • は、2つ目が1つ目より前に来る場合は正の値
        int compareElements (juce::XmlElement* first, juce::XmlElement* second) const
{
auto result = first->getStringAttribute (attributeToSort)
.compareNatural (second->getStringAttribute (attributeToSort)); // [1]

if (result == 0)
result = first->getStringAttribute ("ID")
.compareNatural (second->getStringAttribute ("ID")); // [2]

return direction * result; // [3]
}

したがって、上記の関数では、両方のXmlElementの文字列属性を比較するためにcompareNatural()のメソッドを使用する。Stringクラスは、同じ規則で int を返す。[1].もし2つの文字列が問題の属性に対して等価であれば、これら2つの要素のID列を比較します。[2].最後に、ソートの方向が逆の場合は、結果を反転させる必要がある。[3].

モデルの設定

を実装して、すべてのピースを組み立てよう。TableListBoxModel.

まずTableListBoxModelクラスのTableTutorialComponentクラスは次のようになる:

class TableTutorialComponent    : public juce::Component,
public juce::TableListBoxModel
{
private:
juce::TableListBox table { {}, this };
juce::Font font { 14.0f };

std::unique_ptr tutorialData;
juce::XmlElement* columnList = nullptr;
juce::XmlElement* dataList = nullptr;
int numRows = 0;

ここではTableListBoxメンバ変数に、このクラスをTableListBoxModelつまり、このシナリオでは、モデルクラスが実際にテーブル自身を保持していることを意味します。また、モデルが必要とするテーブルの行数も記録します。

クラスのコンストラクタにTableListBox幼いころComponent [1].また、輪郭の色や太さなど、表の外観に関するプロパティを指定することもできます。[2].

            addAndMakeVisible (table);                                                  // [1]

table.setColour (juce::ListBox::outlineColourId, juce::Colours::grey); // [2]
table.setOutlineThickness (1);
            table.getHeader().setSortColumnId (1, true);                                // [3]

table.setMultipleSelectionEnabled (true); // [4]

並べ替えカラムとカラムの可視性は、対応する関数をTableHeaderComponentテーブルの[3]また、テーブル上での複数選択も可能にしている。[4].

オーバーライドする最初の関数はgetNumRows()関数は、行数を保持するメンバ変数を返します。この関数は、モデルがテーブルを適切に更新するために必要です:

    int getNumRows() override
{
return numRows;
}

についてpaintRowBackground()関数は、まずテーブルのデフォルトの背景色を補完する代替色を見つけることによって実装される:

    void paintRowBackground (juce::Graphics& g, int rowNumber, int /*width*/, int /*height*/, bool rowIsSelected) override
{
auto alternateColour = getLookAndFeel().findColour (juce::ListBox::backgroundColourId)
.interpolatedWith (getLookAndFeel().findColour (juce::ListBox::textColourId), 0.03f);
if (rowIsSelected)
g.fillAll (juce::Colours::lightblue);
else if (rowNumber % 2)
g.fillAll (alternateColour);
}

その行が選択されていれば水色で塗りつぶされ、そうでなければ他のすべての行をこの代替色で塗りつぶす。

セルを内容で埋めるためにpaintCell()関数は次のようになる:

    void paintCell (juce::Graphics& g, int rowNumber, int columnId,
int width, int height, bool rowIsSelected) override
{
g.setColour (rowIsSelected ? juce::Colours::darkblue : getLookAndFeel().findColour (juce::ListBox::textColourId)); // [5]
g.setFont (font);

if (auto* rowElement = dataList->getChildElement (rowNumber))
{
auto text = rowElement->getStringAttribute (getAttributeNameForColumnId (columnId));

g.drawText (text, 2, 0, width - 4, height, juce::Justification::centredLeft, true); // [6]
}

g.setColour (getLookAndFeel().findColour (juce::ListBox::backgroundColourId));
g.fillRect (width - 1, 0, 1, height); // [7]
}
  • [5]まず、行が選択されているかどうかに応じてテキストの色を選択し、フォント・サイズを設定します。
  • [6]子行要素がXmlElementオブジェクトから、行から右の列を取得し、そのセルを対応するテキストで埋める。XmlElement.
  • [7]最後に、セルの右側にデフォルトの背景色で分離線を描きます。
    void sortOrderChanged (int newSortColumnId, bool isForwards) override
{
if (newSortColumnId != 0)
{
TutorialDataSorter sorter (getAttributeNameForColumnId (newSortColumnId), isForwards);
dataList->sortChildElements (sorter);

table.updateContent();
}
}

ユーザーによってソート順の変更が要求されるとsortOrderChanged()コールバック関数が呼び出され、ソートカラムが有効であればTutorialDataSorterオブジェクトに正しい属性と方向を与える。そのオブジェクトをsortChildElements()の機能である。XmlElementを呼び出してテーブルを強制的に更新する。updateContent()テーブルの上に。

についてrefreshComponentForCell()関数は、カスタム・セル・コンポーネントをインスタンス化し、以下のように更新することができる:

    Component* refreshComponentForCell (int rowNumber, int columnId, bool /*isRowSelected*/,
Component* existingComponentToUpdate) override
{
if (columnId == 9) // [8]
{
auto* selectionBox = static_cast (existingComponentToUpdate);

if (selectionBox == nullptr)
selectionBox = new SelectionColumnCustomComponent (*this);

selectionBox->setRowAndColumn (rowNumber, columnId);
return selectionBox;
}

if (columnId == 8) // [9]
{
auto* textLabel = static_cast (existingComponentToUpdate);

if (textLabel == nullptr)
textLabel = new EditableTextCustomComponent (*this);

textLabel->setRowAndColumn (rowNumber, columnId);
return textLabel;
}

jassert (existingComponentToUpdate == nullptr);
return nullptr; // [10]
}
  • [8]もしこの関数が選択セルの正しい "Select "列で呼び出されたなら、次にSelectionColumnCustomComponentが既にセルに存在する。もし存在しなければ、新しいインスタンスをインスタンス化してsetRowAndColumn()関数を実行しComponent.
  • [9]もしこの関数がテキストエディタ・セルの正しい「Description」列で呼び出されたなら、次にEditableTextCustomComponentが既にセルに存在する。もし存在しなければ、新しいインスタンスをインスタンス化してsetRowAndColumn()関数を実行しComponent.
  • [10]そうでない場合は、関数が通常のカラムで呼び出されたことを意味します。Componentオブジェクトは存在しないはずである。

最後にTableListBoxで定義された自動的な動作に従ってカラムのサイズを変更することができます。getColumnAutoSizeWidth()関数である:

    int getColumnAutoSizeWidth (int columnId) override
{
if (columnId == 9)
return 50;

int widest = 32;

for (auto i = getNumRows(); --i >= 0;)
{
if (auto* rowElement = dataList->getChildElement (i))
{
auto text = rowElement->getStringAttribute (getAttributeNameForColumnId (columnId));

widest = juce::jmax (widest, font.getStringWidth (text));
}
}

return widest + 8;
}

ここでは、特定の列内のすべての要素を検査し、セル内の最も幅の広いテキストを取得することにします。そして、パディングを追加した幅を返します。ToggleButton.

エクササイズ

列やデータを追加し、それに応じて実装を変更することで、XML文書の内容を修正する。

概要

このチュートリアルでは、情報を表に表示する方法を学びました。特に

  • XMLを使用してテーブルにデータをロード。
  • カスタムコンポーネントをテーブルセルに組み込みました。
  • カスタム・ソート動作に従ってテーブル・データをソートした。

参照