サンプルテーブルを使用してQtインターフェイススタイルを作成する

ご存じのとおり、Qtは開発者にインターフェイスを作成するためのほぼ無限の可能性を提供します。 ほとんどの場合、標準のウィジェットを使用すると、アプリケーション(およびそのインターフェイス要素)の外観はオペレーティングシステムの外観に対応します。 これを行うために、Qtにはいわゆるスタイルのシステムがあります。これは、標準インターフェイス要素のレンダリングを担当するクラスです。 この記事では、 QTableWidgetウィジェットに基づいて美しいテーブルを取得するために、独自のスタイルを作成し、シンプルなアプリケーションに適用しようとします。



理論部



それでは、理論から始めましょう。 Qtには、ご想像のとおり 、アプリケーションのスタイル設定を行う抽象クラスQStyleがあります。 多くのクラスがそこから継承され( QWindowStyleQMacStyleなど)、オペレーティングシステムの標準的な外観をエミュレートするスタイルです。 私たちは、彼らのイメージと肖像でアプリケーションの独自のスタイルを作ります。 Qtでは、カスタムスタイルをQStyleから直接ではなく 、その派生クラスの1つから継承する必要があります。 ドキュメントでは、目的のシステムスタイルに最も近いシステムスタイルを選択し、それを「for you」に変更することを推奨しています。 クラスをQStyleの抽象クラスQCommonStyleから継承します。



それでは、スタイル内に要素をどのように描画しますか? QStyleクラスには、名前がdrawで始まる関数のセットがあります:







それらのそれぞれは、要素をレンダリングする作業の小さな部分を行います。



drawComplexControl()は 、複合ウィジェット、つまり、内部に他のいくつかのウィジェットを含むウィジェットを描画するために使用されます。

例はQSpinBoxです 。これは、ご想像のとおり 、通常のSpinBoxを実装し、入力フィールドと2つの小さなボタンで構成されています。





drawControl()は、ボタンや入力フィールドなどの標準ウィジェットを描画します。



drawPrimitive()は、フレームなどのいわゆるプリミティブ要素を描画します。



drawItemPixmap()およびdrawItemText()には非常にわかりやすい名前があり、それらから期待どおりの動作を正確に実行します。 すべての関数の詳細な説明とその引数は、Qtのドキュメントで非常に簡単に見つけることができるため、これについては詳しく説明しません。



この例では、通常のテーブルであるQTableWidget要素のスタイルを設定します。 残りの要素のスタイルはまったく同じ方法で行われます。



はじめに



まず、新しいC ++クラスを作成します。 クラスを作成するとき、Qtはその名前と、継承したいクラスの名前を書くように助けてくれます。 簡単にするためにmyStyleという名前を付け、 QCommonStyleからの継承を指定しましょう。 その後、Qtはいくつかのファイル(.hおよび.cpp)を作成します。まず、次のようなファイルを取得します。



myStyle.h
#include <QCommonStyle> class myStyle : public QCommonStyle { Q_OBJECT public: explicit myStyle(); signals: public slots: };
      
      





myStyle.cpp
 #include "mystyle.h" myStyle::myStyle() : QCommonStyle() { }
      
      









メイン()関数は次のようになります。



 int main(int argc, char *argv[]) { QApplication::setStyle( new myStyle ); QApplication a(argc, argv); QTableWidget w(4,3); w.setGeometry(QRect(0,0,300,250)); w.show(); return a.exec(); }
      
      







ご覧のとおり、スタイルを設定し、ウィジェットを作成して表示するだけです。 この段階では、 QCommonStyleから継承された空のスタイルしかありませんので、テーブルは...よく見えますが、あまり魅力的ではありません。







テーブルが何で構成されているかをさらに詳しく考えてみましょう。





一般に、構造は非常にシンプルで簡単です。 見出しで1秒間停止することは価値がありますか。 Qtでは、2種類のヘッダーが分離されています。水平(上部)と垂直(左側)です。 「タイトル領域」とは、タイトルが後に表示される領域全体を意味します。 セクションは、特定のヘッダーセルです。 未割り当て領域とは、セクションがないヘッダーの部分です(これは、すべてのセクションの合計サイズがテーブルのサイズより小さい場合に発生します)。



したがって、この知識があれば、各要素をスタイル化できます。 クラスに関数を追加することから始めましょう。

 void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const; void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const; void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
      
      







drawControl()関数から始めましょう。 コードを実装に追加します。



 switch(element) { default: QCommonStyle::drawControl(element, opt, p, w); break; }
      
      







この関数のキー属性はelementで 、これは描画するもののタイプを示します。 スイッチ 'a内で、自分で描画する要素ごとにcase ' yを追加します。 他のすべては、親クラスの同様の関数を使用してデフォルトセクションで処理されます



テーブル全体を囲むフレームから始めましょう。 グラデーションフレームを作成します。 上下の境界線は白になり、垂直方向の境界線はグラデーションで描画されます。境界線は白で、中央は明るい灰色になります。



これを行うには、次のコードをdrawControl()関数に追加します。



 case CE_ShapedFrame: { //  QLinearGradient gradient(0, 0, 0, opt->rect.height()); gradient.setColorAt(0, QColor(255,255,255)); gradient.setColorAt(0.5, QColor(225,225,225)); gradient.setColorAt(1, QColor(255,255,255)); QPen pen; pen.setBrush( gradient ); p->setPen(pen); //  p->drawRect( 0,0,opt->rect.width()-1, opt->rect.height()-1); } break;
      
      







解説
ここで何が起こっているのか。 最初に、上から下へ線形グラデーションを作成します。 次に、開始点、終了点、中間点の3つのキーポイントの色を設定します。 私が言ったように、最初と最後を白くし、真ん中を少し灰色にします。



その後、ペンを作成し、それにグラデーションを設定します。 Qtのペンはブラシ( QBrush )で描かれ、これがグラデーションになります。 最後に、 drawRect()関数を使用して、フレームを描画します。





それでは、見出しに取りかかりましょう。 ヘッダー領域自体( CE_Header )を処理する必要はありません。 セクションと空の領域を扱います。 空の領域を使用すると、すべてが非常に単純になります。単純な灰色で塗りつぶします。



 case CE_HeaderEmptyArea: p->fillRect( opt->rect, QBrush( QColor( 233, 233, 233 ) ) ); break;
      
      







セクションを使用すると、やや複雑になります。 わかっているように、テーブルの見出しをクリックすると、行全体または列全体が選択されます(クリックした場所によって異なります)。 この場合、ヘッダーの外観が変わるため、ユーザーは自分の状態の変化を見ることができます。 通常、タイトルは「押された」または色が付いています。 この機能を維持したいです。 また、セルまたはセルを選択するときに、対応するヘッダーにも色が付けられるようにしたいと思います。



そのため、次のコードを追加します。



 case CE_HeaderSection: { //  if( opt->state & State_Sunken || opt->state & State_On ) { //    p->fillRect(opt->rect, QBrush( QColor(255,170,80) )); p->setPen( QPen( QColor( 170,170,170) ) ); //   p->drawRect(opt->rect.x(), opt->rect.y(),opt->rect.width()-1,opt->rect.height()-1); } else//  { //    QLinearGradient gradient(0, 0, 0, opt->rect.height()); gradient.setSpread(QGradient::PadSpread); gradient.setColorAt(0, QColor(255,255,255)); gradient.setColorAt(1, QColor(220,220,220)); //  p->fillRect(opt->rect, QBrush( gradient )); //     gradient.setColorAt(0, QColor(230,230,230)); gradient.setColorAt(0.5, QColor(175,175,175)); gradient.setColorAt(1, QColor(230,230,230)); QPen pen; pen.setStyle(Qt::SolidLine); pen.setBrush(gradient); p->setPen(pen); //   p->drawLine( opt->rect.width() + opt->rect.x() - 1, opt->rect.y() + 3, opt->rect.width() + opt->rect.x() - 1, opt->rect.height() + opt->rect.y() - 3 ); } }
      
      







解説
そのため、セクションはアクティブと非アクティブの2つの状態になります。 これを判断するために、フラグを確認します。 State_Sunkenフラグはセクションが押されていることを示し、 State_Onフラグはこのセクションの列(または行)に属するセルが選択されていることを示します。 それらの少なくとも1つが設定されている場合、セクションをオレンジ色で塗りつぶし、ヘッダー全体の他の(選択されていない)明るい灰色の部分の背景に対してオレンジ色のセクションが粗く見えないように境界線全体を描画します。



セクションが非アクティブの場合、白から明るいグレーのグラデーションでペイントし、境界線を描画します。 セクションの垂直方向の境界線のみを描画することにしたので、必要なのは各セクションの右側に垂直方向のストリップを描画することだけです。 ストリップが粗く見えないように、テーブル全体のフレームとほぼ同じ勾配でストリップを描画します。ストリップの端は明るく、真ん中は暗くなります。 これを行うには、グラデーションを再構成し、それをペンに設定して、ストリップを描画します。





この段階で、かなりいい見出しができました。 ただし、選択すると、デフォルトではセルはかなりい色で塗りつぶされます。 また、標準のフォーカスフレームも見た目があまり美しくありません。



CE_ItemViewItem属性は、セルのレンダリングを担当します。 次のコードを追加します。



 case CE_ItemViewItem: { //  ,       drawPrimitive(PE_PanelItemViewItem, opt, p, w); //       //    ,      . const QStyleOptionViewItemV4 * option = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt); if( !option ) { //  -    ,     QCommonStyle::drawControl(element, opt, p, w); return; } //       . if (option->state & QStyle::State_HasFocus) { QPen pen(QColor( 170,170,170 )); p->save(); p->setPen(pen); p->drawRect(opt->rect.x(), opt->rect.y(), opt->rect.width()-1, opt->rect.height()-1); p->restore(); } //  ,      QRect textRect = subElementRect(SE_ItemViewItemText, option, w); // ,    ""    textRect.setX( textRect.x() + 5 ); textRect.setY( textRect.y() + 5 ); if( !option->text.isEmpty() ) { // . p->drawText(textRect, option->text); } }
      
      







解説
ここでは、もっと多くのことをしなければなりませんでした。 まず、 drawPrimitive()関数で描画されるPE_PanelItemViewItem要素は、選択したセルを異なる色で着色する役割を果たします。 したがって、そこにあるパラメーターを渡すことにより、この関数を呼び出す必要があります。 その後、オプションへのポインターをQStyleOption基本クラスから必要なQStyleOptionViewItemV4クラスに変換します。 これは、特に、テキストとこのテキストの描画領域を取得するために必要です。



描画するセルが選択されている場合
 if (option->state & QStyle::State_HasFocus)
      
      



次に、セル全体の周りに小さな灰色のフレームを描画します。



その後、テキストを表示する領域のサイズを取得し、テキストが左上隅近くに表示されないように少しインデントします。 最後に、 drawText()関数を使用してテキストを描画します。





セルを処理するとき、PE_PanelItemViewItem要素のdrawPrimitive関数を呼び出すため、この要素をレンダリングして、選択したセルがより快適な色で描画されるようにします。



drawControl()と同様に、まったく同じスイッチdrawPrimitive()に追加し、必要なセルの強調表示をすぐに実装します。



 switch( pe ) { case PE_PanelItemViewItem: //  ,    ,   . if (const QStyleOptionViewItemV4 *option = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) if (option->state & QStyle::State_Selected) { p->fillRect(option->rect, QBrush( QColor( 220,220,220,100 ))); } break; default: QCommonStyle::drawPrimitive( pe, opt, p, w); break; }
      
      







最後に、テキストを表示するフォントを変更します。 次のコードをdrawItemText()関数に追加します。

 painter->setPen( QPen( QColor( 30, 30, 30 ))); painter->setFont(QFont("Consolas")); painter->drawText(rect, text);
      
      







したがって、プレーンタイルを変換すると、次のようになります。







私の意見では、これは当初のものよりもはるかに優れています。



このアプローチの利点





もちろん、この方法でアプリケーション全体のスタイルを作成するのは非常に時間がかかります。 1つの要素のスタイル(この記事のように)は非常に迅速に行うことができますが、すべてのウィジェットを処理する場合(たとえば、他の人が使用できるようにスタイルを広げたい場合)、多くの時間がかかります。 たとえば、個々のウィジェットごとに描画される要素の説明が見つからなかったため、「科学的な突く」方法を使用してそれらを決定する必要がありました。 ただし、この方法でのスタイリングには多くの重要な利点があります。



まず、これらのスタイルはすべての要素に適用されます。 300のテーブルがある場合、各テーブルを手動で構成する必要はありません。アプリケーションのスタイルは1行で適用されます。



第二に、この方法で作成されたスタイルは変更に便利です。 これはおそらく、設定を通じてアプリケーションのスタイルを変更する機会をユーザーに与えるための最良の方法です。 さらに、作成するスタイルが毎回同じに見えることを確認できます。



完全にソース:



myStyle.h
 #ifndef MYSTYLE_H #define MYSTYLE_H #include <QCommonStyle> #include <QtGui> class myStyle : public QCommonStyle { Q_OBJECT public: explicit myStyle(); void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const; void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const; void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const; signals: public slots: }; #endif // MYSTYLE_H
      
      





myStyle.cpp
 #include "mystyle.h" myStyle::myStyle() : QCommonStyle() { } void myStyle::drawControl(QStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const { switch( element ) { case CE_ShapedFrame: { QLinearGradient gradient(0, 0, 0, opt->rect.height()); gradient.setColorAt(0, QColor(255,255,255)); gradient.setColorAt(0.5, QColor(225,225,225)); gradient.setColorAt(1, QColor(255,255,255)); QPen pen; pen.setBrush( gradient ); p->setPen(pen); p->drawRect( 0,0,opt->rect.width()-1, opt->rect.height()-1); } break; case CE_ItemViewItem: { drawPrimitive(PE_PanelItemViewItem, opt, p, w); const QStyleOptionViewItemV4 * option = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt); if( !option ) { QCommonStyle::drawControl(element, opt, p, w); return; } if (option->state & QStyle::State_HasFocus) { QPen pen(QColor( 170,170,170 )); p->save(); p->setPen(pen); p->drawRect(opt->rect.x(), opt->rect.y(), opt->rect.width()-1, opt->rect.height()-1); p->restore(); } QRect textRect = subElementRect(SE_ItemViewItemText, option, w); textRect.setX( textRect.x() + 5 ); textRect.setY( textRect.y() + 5 ); if( !option->text.isEmpty() ) { p->drawText(textRect, option->text); } } break; case CE_Header: QCommonStyle::drawControl(element, opt, p, w); break; case CE_HeaderEmptyArea: p->fillRect( opt->rect, QBrush( QColor( 233, 233, 233 ) ) ); break; case CE_HeaderSection: { if( opt->state & State_Sunken || opt->state & State_On ) { p->fillRect(opt->rect, QBrush( QColor(255,170,80) )); p->save(); p->setPen( QPen( QColor( 170,170,170) ) ); p->drawRect(opt->rect.x(), opt->rect.y(),opt->rect.width()-1,opt->rect.height()-1); p->restore(); } else { QLinearGradient gradient(0, 0, 0, opt->rect.height()); gradient.setSpread(QGradient::PadSpread); gradient.setColorAt(0, QColor(255,255,255)); gradient.setColorAt(1, QColor(220,220,220)); p->fillRect(opt->rect, QBrush( gradient )); gradient.setColorAt(0, QColor(230,230,230)); gradient.setColorAt(0.5, QColor(175,175,175)); gradient.setColorAt(1, QColor(230,230,230)); QPen pen; pen.setStyle(Qt::SolidLine); pen.setBrush(gradient); p->setPen(pen); p->drawLine( opt->rect.width() + opt->rect.x() - 1, opt->rect.y() + 3, opt->rect.width() + opt->rect.x() - 1, opt->rect.height() + opt->rect.y() - 3 ); } } break; default: QCommonStyle::drawControl(element, opt, p, w); break; } } void myStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const { painter->setPen( QPen( QColor( 30, 30, 30 ))); painter->setFont(QFont("Consolas")); painter->drawText(rect, text); } void myStyle::drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const { switch( pe ) { case PE_PanelItemViewItem: if (const QStyleOptionViewItemV4 *option = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) if ((option->state & QStyle::State_Selected)) p->fillRect(option->rect, QBrush( QColor( 220,220,220,100 ))); break; default: QCommonStyle::drawPrimitive( pe, opt, p, w); break; } }
      
      








All Articles