C++GUI Programming with Qt 4-10.4カスタムエージェント(delegate)の実装



カスタムエージェントの実装(delegate)
ビュー内の個々のエントリのレンダリングと編集は、プロキシによって行われます.ほとんどの場合、ビューによって提供されるデフォルトのエージェントは十分です.エントリのレンダリングをよりよく制御し、カスタムモデルを簡単に使用することで、常に要件を満たすことができます.再実装されたdata()では、Qt::FontRole,Qt::TextAlignnmentRole,Qt::TextColorRole,Qt::BackgroundColorRoleを持ち、デフォルトのエージェントで使用できます.例えば、以前の都市や通貨の例では、Qt::TextAlignmentRoleを持って右揃えの数字を取得していました.
より強力な制御を望む場合は、独自のエージェントクラスを作成し、使用しているビューに配置できます.次のTrack Editorダイアログボックスでは、カスタムエージェントを使用します.音楽曲のタイトルと持続時間が表示されます.モデルが持つデータは簡単なQStrings(タイトル)とints(秒数)になりますが、持続時間は分数と秒数に分けられ、QTimeEditで編集できます.
図10.15.Track Editorダイアログ
Track Editorダイアログボックスでは、QTableWidgetを使用します.QTableWidgetItem上で操作する便利なエントリビュー(convenince item view)サブクラスです.データはtracksリストとして提供されます.
class Track
{
public:
    Track(const QString &title = "", int duration = 0);
    QString title;
    int duration;
};

これらはコンストラクション関数の概要であり、表コンポーネントの作成と塗りつぶしが表示されます.
TrackEditor::TrackEditor(QList *tracks, QWidget *parent)
    : QDialog(parent)
{
    this->tracks = tracks;
    tableWidget = new QTableWidget(tracks->count(), 2);
    tableWidget->setItemDelegate(new TrackDelegate(1));
    tableWidget->setHorizontalHeaderLabels(
            QStringList() << tr("Track") << tr("Duration"));
    for (int row = 0; row < tracks->count(); ++row) {
        Track track = tracks->at(row);
        QTableWidgetItem *item0 = new QTableWidgetItem(track.title);
        tableWidget->setItem(row, 0, item0);
        QTableWidgetItem *item1
             = new QTableWidgetItem(QString::number(track.duration));
        item1->setTextAlignment(Qt::AlignRight);
        tableWidget->setItem(row, 1, item1);
    }
    ...
}

コンストラクション関数はテーブルコンポーネントを作成し、デフォルトのエージェントを簡単に使用することはできません.エージェントをカスタムTRackDelegateに設定し、時間データを持つ列を渡します.まず、リストヘッダを設定し、データを巡回し、各曲の名前と持続時間で行を埋めます.
コンストラクション関数の残りの部分とTRackEditorの残りの部分は平凡なので、データのレンダリングと編集をコントロールするtrackDelegateを見てみましょう.
class TrackDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    TrackDelegate(int durationColumn, QObject *parent = 0);
        void paint(QPainter *painter, const QStyleOptionViewItem &option,
                   const QModelIndex &index) const;
        QWidget *createEditor(QWidget *parent,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index) const;
        void setEditorData(QWidget *editor, const QModelIndex &index) const;
        void setModelData(QWidget *editor, QAbstractItemModel *model,
                          const QModelIndex &index) const;
    private slots:
        void commitAndCloseEditor();
    private:
    int durationColumn;
};

ベースクラスとしてQItemDelegateを使用すると、エージェントのデフォルトの実装の恩恵を受けることができます.最初からやりたいならQA bstractItemDelegateも使えます.データを編集できるエージェントを提供するには、createEditor()、setEditorData()、setModelData()を実装する必要があります.持続時間列のレンダリング効果を変更するためにpaint()も実現した.
TrackDelegate::TrackDelegate(int durationColumn, QObject *parent)
    : QItemDelegate(parent)
{
    this->durationColumn = durationColumn;
}

コンストラクション関数のdurationColumnパラメータは、エージェントが曲を持つカラムの持続時間を示します.
void TrackDelegate::paint(QPainter *painter,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    if (index.column() == durationColumn) {
        int secs = index.model()->data(index, Qt::DisplayRole).toInt();
        QString text = QString("%1:%2")
                       .arg(secs / 60, 2, 10, QChar('0'))
                       .arg(secs % 60, 2, 10, QChar('0'));
        QStyleOptionViewItem myOption = option;
        myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
        drawDisplay(painter, myOption, myOption.rect, text);
        drawFocus(painter, myOption, myOption.rect);
    } else{
        QItemDelegate::paint(painter, option, index);
    }
}

持続時間をminutes:secondsの形式でレンダリングしたいのでpaint()関数を再実装しました.arg()呼び出しには、テキストとしてレンダリングされた整数、文字列にあるべき文字数、整数のベース(10は10進数)、および塗りつぶし文字が含まれます.
テキストを右揃えにするには、現在のスタイルオプションをコピーし、デフォルトの揃え方法を上書きします.QItemDelegate::drawDisplay()を呼び出してテキストを描画し、QItemDelegate::drawFocus()を呼び出してフォーカス矩形を描画します.エントリにフォーカスがある場合は、他のことはしません.drawDisplay()を使用すると、特に独自のスタイルオプションを使用すると便利です.ブラシを直接使って描くこともできます.
QWidget *TrackDelegate::createEditor(QWidget *parent,
        const QStyleOptionViewItem &option,
        const QModelIndex &index) const
{
    if (index.column() == durationColumn) {
        QTimeEdit *timeEdit = new QTimeEdit(parent);
        timeEdit->setDisplayFormat("mm:ss");
        connect(timeEdit, SIGNAL(editingFinished()),
                this, SLOT(commitAndCloseEditor()));
        return timeEdit;
    } else {
        return QItemDelegate::createEditor(parent, option, index);
    }
}

曲の持続時間の編集のみを制御し、デフォルトのエージェントに曲名の編集を残します.この目的は、エージェントがどのカラムにエディタを提供するように要求されているかを確認することによって達成されます.持続時間の列であれば、QTimeEditを作成し、適切な表示フォーマットを設定し、editingFinished()信号とcommitAndCloseEditor()スロットを接続します.他のカラムについては、編集操作をデフォルトのエージェントに伝えます.
void TrackDelegate::commitAndCloseEditor()
{
    QTimeEdit *editor = qobject_cast(sender());
    emit commitData(editor);
    emit closeEditor(editor);
}

ユーザがリターンまたはQTimeEditを押してフォーカスを失った場合(ただし、Escを押す以外)、信号editingFinished()が送信され、スロットcommitAndCloseEditor()が呼び出されます.このスロットはcommitData()信号を送信してビューに既存のデータを編集済みのデータで置き換えることを通知する.また、closeEditor()信号を送信して、ビューがこのエディタを必要としないことを通知し、モデルが削除されます.QObject::sender()でエディタを取得し、このスロットをトリガーする信号を送信するオブジェクトを返します.ユーザが操作をキャンセルした場合(Escを押す)、ビューは簡単にエディタを削除します.
void TrackDelegate::setEditorData(QWidget *editor,
                                  const QModelIndex &index) const
{
    if (index.column() == durationColumn) {
        int secs = index.model()->data(index, Qt::DisplayRole).toInt();
        QTimeEdit *timeEdit = qobject_cast(editor);
        timeEdit->setTime(QTime(0, secs / 60, secs % 60));
    } else {
        QItemDelegate::setEditorData(editor, index);
    }
}

ユーザーが編集を開始すると、ビューはcreateEditor()を呼び出してエディタを作成し、setEditorData()を使用してエントリの現在のデータでエディタを初期化します.持続時間列のエディタであれば、曲の持続秒数を抽出し、QTimeEditの時間を対応する分と秒数に設定します.そうでなければ、デフォルトのエージェントで初期化します.
void TrackDelegate::setModelData(QWidget *editor,
                                 QAbstractItemModel *model,
                                 const QModelIndex &index) const
{
    if (index.column() == durationColumn) {
        QTimeEdit *timeEdit = qobject_cast(editor);
        QTime time = timeEdit->time();
        int secs = (time.minute() * 60) + time.second();
        model->setData(index, secs);
    } else {
        QItemDelegate::setModelData(editor, model, index);
    }
}

ユーザーが編集をキャンセルするのではなく、エディタコンポーネント以外の場所をクリックしたり、リターンキーやタブキーを押したりするなどの編集を完了した場合、モデルは必ずエディタのデータで更新されます.持続時間が編集されると,QTimeEditから分と秒数を取得し,対応する秒数に変換する.
この例では必要ありませんが、モデル内の任意のエントリの編集とレンダリングを細かく制御するためにカスタムエージェントを作成することは完全に可能です.あるカラムを制御することを選択しましたが、QModelIndexがQItemDelegateで再実装されたすべての関数に渡される以上、カラム、行、矩形領域、親、またはこれらの任意の組合せを制御することができます.必要に応じて、単一のエントリであってもよいです.
この章では,Qtのモデル/ビューアーキテクチャの概要を紹介した.ビュー便利サブクラスの使用方法,Qtの事前定義モデルの使用方法,カスタムモデルとカスタムエージェントの作成方法を示した.しかし、モデル/ビューアーキテクチャは、可能なことをすべてカバーするスペースがないほど豊富です.クラスたとえば、エントリをリスト、テーブル、またはツリーにレンダリングしないカスタムビューを作成できます.Chartの例はこれです.この例はQtのexamples/itemviews/chartディレクトリの下で、モデルデータを円グラフにレンダリングするカスタムビューを示しています.
マルチビューを使用して、同じモデルを何の拘束もなく表示することもできます.いずれのビューからの編集も、自動的に他のビューに反映されます.この機能は、特に、ビッグデータセットの論理距離が遠いいくつかのデータを表示するのに役立ちます.このアーキテクチャは、2つ以上のビューが同じモデルを使用し、各ビューが独立した選択を持つように設定されたり、選択がビュー間で共有されるように設定されたりする選択もサポートします.
Qtのオンラインドキュメントは、幅広いエントリ閲覧に関するプログラミングと実装をカバーするクラスを提供します.ブラウズhttp://doc.trolltech.com/4.1/model-view.html関連するクラスのリスト、およびhttp://doc.trolltech.com/4.1/model-view-programming.htmlいくつかの追加情報とQtに含まれる関連例との接続.