XMLの外で定義したデータを読み込むのは意外と汎用性がありそうです。
パラメータとかにも応用できそうだね~
引き続き、JUCEチュートリアル、「Interface Design」、「The TableListBox class」チュートリアルです。前回までの記事で、チュートリアル用のデモアプリの構造がわかりましたので、今回は、チュートリアルの項目「Reading Data from XML」をやっていきます。
公式のチュートリアルページはこちらになります。
https://docs.juce.com/master/tutorial_table_list_box.html
今回の実装は、前回のミニマムなTableTutorialComponentクラスに追加実装していきます。
https://panda-clip.com/juce-the-tablelistbox-class2
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEチュートリアル「The TableListBox class」をやっている人
・JUCE、XMLデータを読み込みたい人
コンストラクタ
TableTutorialComponentクラスのコンストラクタに以下のプログラムを追加しています。
class TableTutorialComponent : public juce::Component,
public juce::TableListBoxModel
{
public:
TableTutorialComponent()
{
loadData();//[1]
//[2]TableListBoxにヘッダー項目を読みこみます。
if (columnList != nullptr)
{
//[2-1]項目数分ループです。
for (auto* columnXml = columnList->getFirstChildElement();
columnXml != nullptr;
columnXml = columnXml->getNextElement())
{
//[2-2]tableに列項目として追加します。
table.getHeader().addColumn(columnXml->getStringAttribute("name"),
columnXml->getIntAttribute("columnId"),
columnXml->getIntAttribute("width"),
50,
400,
juce::TableHeaderComponent::defaultFlags);
}
}
}
//...略...
[1]のloadData関数は、後程定義します。ここで外部のXMLファイルを参照します。この関数内で「columnList」にXMLデータの「<HEADERS>」のデータが格納されます。
[2]では、先ほどのcolumnListが何かしら存在する場合、nullptrではなくなる(初期値はprivateなメンバ変数定義のときにnullptrと指定しています。)ので、条件に入り、for文でTableListBoxクラスの「table」の列項目として追加していきます。[2-1]は、チュートリアルのままでは動作しない点です。初期値をXmlElementsクラスのgetFirstChildElement関数とし、columnXmlの最初の要素を格納します。条件判定部でcolumnXmlがnullptrとなるとループから抜けます。インクリメント部にはXmlElementsクラスのgetNextElement関数で次の項目を取得しています。[2-2]で、TableListBoxクラスのtableに列項目(ヘッダー)を追加していきます。
table.getHeader()までで、TableHeaderComponenクラスのオブジェクトが得られます。TableHeaderComponentクラスは、表のヘッダーを高機能にするようなクラスで、列幅をリサイズしたりといったことができるようです。ここに、TableHeaderComponentクラスのaddColumn関数で項目を追加していきます。引数として、(項目名、ID、幅、列最小幅、列最大幅、フラグ)という値を与えます。最初の3つは、XMLのnameとcolumnID、widthのデータに対応していますので、XmlElementクラスの関数、文字列はgetStringAttribute関数、数値はgetIntAttribute関数で取得して引数として与えます。列幅は50~400という固定値を引数に与えます。最後のフラグには、リサイズを有効にしたりするこちらの値を与えることでいろいろと表の動作を変更できるようです。
privateなメンバ
コンストラクタでは、すでに利用していますが、TableTutorialComponentクラスのprivateなメンバとして、次のように追加しました。
private:
juce::TableListBox table{ {}, this };
//以下のメンバを追加しました。
std::unique_ptr<juce::XmlElement> tutorialData;//[1]
juce::XmlElement* columnList = nullptr;//[2]
juce::XmlElement* dataList = nullptr;//[3]
int numRows = 0;
[1]は、スマートポインタというC++のメモリ動的確保を補助してくれる仕組みを利用してXmlElementクラスのtutorialDataというメンバを定義しています。XMLのデータ内容は大きさが変動するので、プログラム中で動的に確保する必要があります。そのため、スマートポインタでの定義となりました。
[2]と[3]はtutorialDataに読み込んだ<header>の項目と<data>の項目のデータのポインタを格納するためのXmlElementクラス型のポインタです。この変数に格納されるのは、<header>と<date>へのメモリのアドレスなので、このメンバからXmlElementクラスの関数を呼び出すときはアロー演算子を利用することになります。
loadData関数
次に、loadData関数です。プロジェクトフォルダの「Resources」フォルダ内の「TableData.xml」というファイルを読み込みます。
void loadData()
{
//[1]現在階層を取得します。
auto dir = juce::File::getCurrentWorkingDirectory();
//DBG("dir:" << dir.getFullPathName());
int numTries = 0;
//[2]「Resources」フォルダが見つかるまで親階層へ移動します。
while (!dir.getChildFile("Resources").exists() && numTries++ < 15)
dir = dir.getParentDirectory();
//[3]XMLファイルを取得します。
auto tableFile = dir.getChildFile("Resources").getChildFile("TableData.xml");
//[4]XMLファイルが存在する場合ファイルのデータの取得
if (tableFile.exists())
{
//[4-1]
tutorialData = juce::XmlDocument::parse(tableFile);
//[4-2]
dataList = tutorialData->getChildByName("DATA");
columnList = tutorialData->getChildByName("HEADERS");
//[4-3]
numRows = dataList->getNumChildElements();
}
//[5]以下のようにしてデバッグ用出力でXMLを確認できます。
//DBG("DATA:" << dataList->toString());
//DBG("COLUMN:" << columnList->toString());
}
[1]は現在のディレクトリを取得します。上記コメントアウトしてあるDBGにて階層を出力したら、VisualStudioのdebugモードで実行している場合、「プロジェクトフォルダ\Build\VisualStudio2019」というVisualStudioのプロジェクトファイルの階層を示しました。
[2]では、FileクラスのgetChildFile関数で「Resources」という名称のファイルを探します。また、numTries変数が15より小さいときも条件として入れることで、最大whileループは15回ファイルを探します。処理内容としては、繰り返しをする場合、「Resources」が見つかっていないということで、dirをgetParentDirectory関数で親の階層に移動させることで、親階層にさかのぼって「Resources」を探します。
[3]では、「Resources」の中の「TableData.xml」のFileオブジェクトを取得します。
[4]で、[3]のFileがnull出なければ、TableDataの内容を取得しに行きます。XmlDocumentクラスのparse関数でtutorialDataに内容を取得します。XmlDocumentクラスはXMLデータ取得してXmlElementを作成できるようなクラスです。
tutorialDataにはXMLデータがはいっていますのでgetChildByName関数で要素「DATA」と「HEADERS」をそれぞれdataListとcolumnListに取得します。
[5]のように、内容をデバッガに出力することで、正常にXMLデータを読み取っていることが確認できました。