表のセルには、JUCEコンポーネントを埋め込むことができるみたいです。
応用の幅が広がるね〜
引き続き、JUCEチュートリアル、「Interface Design」、「The TableListBox class」チュートリアルです。前回までで、プログラムを実装するベースができたので、チュートリアルの続きである「Custom Cell Components」の項目から進めていきます。
公式のチュートリアルページはこちらになります。
https://docs.juce.com/master/tutorial_table_list_box.html
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEチュートリアル「The TableListBox class」をやっている人
・JUCEの表に、入力可能なテキストボックスを埋め込みたい人
実装の概要
コンポーネントを表の項目として埋め込むことで、「Description」はユーザー入力ができるような項目になります。また、「Select」のように、トグルボタンを埋め込むとチェック項目のようにすることもできます。
今回は、表のDiscriptionの列を入力可能なテキストとする実装を行います。
エディット可能なテキスト
Descriptionの項目を、ダブルクリックでエディット可能なテキストにしていきます。Labelクラスを継承した「EditableTextCustomComponentクラス」を新規に作成していきます。C++の、インナークラス(内部クラス)としてTableTutorialComponentクラス内に実装していきます。
TableTutorialComponentクラス
TableTutorialComponentクラスのprivateなメンバとして、以下のクラスを追加しました。
class TableTutorialComponent : public juce::Component,
public juce::TableListBoxModel
{
//...略...
private:
//...略...
//[1]Labelクラスを継承するクラスを作成します。
class EditableTextCustomComponent : public juce::Label
{
public:
//[3]コンストラクタです。
EditableTextCustomComponent(TableTutorialComponent& td)
: owner(td)
{
setEditable(false, true, false);
}
//[4]マウスクリック時の処理です。
void mouseDown(const juce::MouseEvent& event) override
{
owner.table.selectRowsBasedOnModifierKeys(row, event.mods, false);
Label::mouseDown(event);
}
//[5]テキスト編集状態の処理です。
void textWasEdited() override
{
owner.setText(columnId, row, getText());
}
//[6]文字列を設定します。
void setRowAndColumn(const int newRow, const int newColumn)
{
row = newRow;
columnId = newColumn;
setText(owner.getText(columnId, row), juce::dontSendNotification);
}
private:
//[2]メンバも定義します。
TableTutorialComponent& owner;
int row, columnId;
juce::Colour textColour;
};
//...略...
[1]のように、EditableTextCustomComponentクラスを作成します。Labelクラスを継承します。privateなメンバとして、[2]のように、TableTutorialComponentクラスのアドレス、表のセル位置を表現する行と列の整数、文字色のColourクラスのtextColourなどを準備します。
TableTutorialComponent&は、メンバに参照型をもつクラスのページも参考にさせていただきました。
[3]はコンストラクタで、メンバイニシャライザでownerに親テーブルのアドレスを入れて初期化します。また、setEditable関数の第二引数をtrueにすることでダブルクリックでエディット状態に変化するように設定しています。
[4]では、マウスでクリックされた時の処理を記載します。クリックされたら、ownerのtableからselectRowsBasedOnModifierKeys()関数を呼び出しています。これは、複数選択の時の修飾キーに対応する関数とドキュメントから読み解きましたが、table自体を複数選択にしていなくても(table.setMultipleSelectionEnabled(true)を消しても)、この行がないと、テキスト上のクリックで行の選択ができない現象が出ましたので、テンプレートのようにして記載しておくのが良いと思いました。
[5]は、Labelを編集状態にした時に処理される関数です。ここでsetText関数の引数として実行されているgetText関数は、ラベルクラスのもので、入力したテキストを取得するものです。setText関数は後ほどTableTutorialComponentクラスに定義するものです。(正直、同じ名前の関数なので、とても混乱しました・・・)
取得したテキストはownerのsetText関数でownerのXmlElementsオブジェクト、dataListに格納されます。
[6]は、このクラス自体のインスタンス、TableTutorialComponentクラスのインスタンスに文字列を設定する関数です。
このクラスのインスタンスの文字列が変更される流れとしては、
①:ダブルクリックで編集状態になり、ユーザー入力します。
②:setText関数でTableTutorialComponentクラス(owner)のdataList書き換えます。
③:refreshComponentForCell関数が実行されて、setRowAndColumnが呼び出されます。
④:setRowAndColumnがownerのdataListからデータを取得してラベルクラスのsetText関数で文字列を設定します。
ownerのsetTextとgetText、LabelクラスのsetTextとgetTextの両方が存在していることを考慮しなければ、結構混乱しますよね・・・
少しだけでも関数名に配慮が欲しいよね〜
refreshComponentForCell関数から利用
ここで作成したEditableCustomComponentクラスは、TableTutorialComponentクラスのpublicな関数、refreshComponentForCell関数で次のように、インスタンス化して表の中に埋め込んでいます。
[1]の、refreshComponentForCell関数は、全てのセルについて実行されますので、このように列番号で条件分岐させることで、特定の列の処理だけとすることができます。
class TableTutorialComponent : public juce::Component,
public juce::TableListBoxModel
{
public:
//...略...
//[1]埋め込んだコンポーネントをインスタンス化、描画する関数です。
Component* refreshComponentForCell(int rowNumber, int columnId, bool /*isRowSelected*/
Component* existingComponentToUpdate) override
{
//[2]列ID8(Description)の時実行する処理です。
if (columnId == 8)
{
//[3]初回の描画時のみ、インスタンス化します。
auto* textLabel = static_cast<EditableTextCustomComponent*> (existingComponentToUpdate);
if (textLabel == nullptr)
textLabel = new EditableTextCustomComponent(*this);
//[4]文字列を更新します。
textLabel->setRowAndColumn(rowNumber, columnId);
return textLabel;
}
jassert(existingComponentToUpdate == nullptr);
return nullptr;
}
//...略...
[2]では、列番号が8の時、に実行されるよう条件としています。[3]では、existingComponentToUpdateに渡されてくるコンポーネントがなければ、textLabelがnullptrとなるので、この時だけEditableTextCustomComponentをnewします。EditableTextComponentの引数として与える「*this」については、C++のthisポインタを利用していて、こちらのサイトも参考にさせていただきました。thisポインタには、自オブジェクトのアドレスが格納されているので、「*this」都することで「thisの値=自オブジェクトのアドレス値」を渡すことができます。
そして、[4]の、setRowAndColumnで文字列を更新しています。[4]は、列番号8のセルの更新時に毎回実行されます。
最後に、TableTutorialComponentクラスのgetText関数とsetText関数を設定しておきます。publicな関数として定義します。
//...略...
//[1]getText関数です。
juce::String getText(const int columnNumber, const int rowNumber) const
{
return dataList->getChildElement(rowNumber)->getStringAttribute(getAttributeNameForColumnId(columnNumber));
}
//[2]setText関数です。
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);
}
//...略...
[1]のsetText関数は、dataListから行番号、列番号を元に文字列データを取得してreturnするのみです。
[2]のsetText関数は、dataListに新しい文字列をXmlElementsクラスのsetAttribute関数で設定しています。setAttribute関数には、列の名前が必要なので、一つ前でgetHeader.getColumnNameとして列番号から列名を取得しています。
ここまでで、編集可能なラベルを表のDescription列に埋め込むことができました。