「Qtの基礎 - ウインドウ」の版間の差分
| 351行目: | 351行目: | ||
setTexture(palette, QPalette::Mid, midImage); | setTexture(palette, QPalette::Mid, midImage); | ||
setTexture(palette, QPalette::Window, backgroundImage); | setTexture(palette, QPalette::Window, backgroundImage); | ||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== ウインドウおよびウィジェットの形状を変更する ==== | |||
ウインドウおよびウィジェットの形状を変更するには、ウィジェットの形状をプログラムで全て記述する必要がある。<br> | |||
<br> | |||
例えば、ボタンの形状を変更する場合、外形を描画するだけではなく、<br> | |||
ボタンが立体的に見えるように陰影の描画、ボタン押下時の陰影の変化等も記述する必要がある。<br> | |||
(或いは、QSS + 背景画像で形状を変更できる可能性がある)<br> | |||
<br> | |||
ウインドウおよびウィジェットの形状を記述するには、<code>QStyle</code>クラスを継承した派生クラスを作成して、<br> | |||
<code>QStyle</code>クラスの<code>drawPrimitive</code>メソッドをオーバーライドする。<br> | |||
<br> | |||
以下の例では、ボタンの形状を変更している。<br> | |||
Qtに付属しているサンプルを参考にしている。(<Qtのインストールディレクトリ>/examples/widgets/styles/norwegianwoodstyle.cppファイル)<br> | |||
<syntaxhighlight lang="c++"> | |||
void MyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const | |||
{ | |||
switch(element) | |||
{ | |||
// ウィジェットのPrimitiveの種類を指定する(ウィジェットの種類ではない) | |||
case PE_PanelButtonCommand: | |||
int delta = (option->state & State_MouseOver) ? 64 : 0; | |||
QColor slightlyOpaqueBlack(0, 0, 0, 63); | |||
QColor semiTransparentWhite(255, 255, 255, 127 + delta); | |||
QColor semiTransparentBlack(0, 0, 0, 127 - delta); | |||
int x, y, width, height; | |||
option->rect.getRect(&x, &y, &width, &height); | |||
// 以下の3行のみ追記する | |||
// ボタンの形状を楕円にする | |||
QPainterPath path; | |||
path.addEllipse(option->rect); | |||
// これ以降の記述はコピーである | |||
// ボタンの押下時と非押下時の陰影等を記述する | |||
// 様々な形状に対応している | |||
int radius = qMin(width, height) / 2; | |||
QBrush brush; | |||
bool darker; | |||
const QStyleOptionButton *buttonOption = qstyleoption_cast<const QStyleOptionButton *>(option); | |||
if(buttonOption && (buttonOption->features & QStyleOptionButton::Flat)) | |||
{ | |||
brush = option->palette.background(); | |||
darker = (option->state & (State_Sunken | State_On)); | |||
} | |||
else | |||
{ | |||
if(option->state & (State_Sunken | State_On)) | |||
{ | |||
brush = option->palette.mid(); | |||
darker = !(option->state & State_Sunken); | |||
} | |||
else | |||
{ | |||
brush = option->palette.button(); | |||
darker = false; | |||
} | |||
} | |||
painter->save(); | |||
painter->setRenderHint(QPainter::Antialiasing, true); | |||
painter->fillPath(path, brush); | |||
if(darker) | |||
{ | |||
painter->fillPath(path, slightlyOpaqueBlack); | |||
} | |||
int penWidth; | |||
if(radius < 10) | |||
penWidth = 3; | |||
else if (radius < 20) | |||
penWidth = 5; | |||
else | |||
penWidth = 7; | |||
QPen topPen(semiTransparentWhite, penWidth); | |||
QPen bottomPen(semiTransparentBlack, penWidth); | |||
if(option->state & (State_Sunken | State_On)) | |||
{ | |||
qSwap(topPen, bottomPen); | |||
} | |||
int x1 = x; | |||
int x2 = x + radius; | |||
int x3 = x + width - radius; | |||
int x4 = x + width; | |||
if(option->direction == Qt::RightToLeft) | |||
{ | |||
qSwap(x1, x4); | |||
qSwap(x2, x3); | |||
} | |||
QPolygon topHalf; | |||
topHalf << QPoint(x1, y) << QPoint(x4, y) << QPoint(x3, y + radius) << QPoint(x2, y + height - radius) << QPoint(x1, y + height); | |||
painter->setClipPath(path); | |||
painter->setClipRegion(topHalf, Qt::IntersectClip); | |||
painter->setPen(topPen); | |||
painter->drawPath(path); | |||
QPolygon bottomHalf = topHalf; | |||
bottomHalf[0] = QPoint(x4, y + height); | |||
painter->setClipPath(path); | |||
painter->setClipRegion(bottomHalf, Qt::IntersectClip); | |||
painter->setPen(bottomPen); | |||
painter->drawPath(path); | |||
painter->setPen(option->palette.foreground().color()); | |||
painter->setClipping(false); | |||
painter->drawPath(path); | |||
painter->restore(); | |||
break; | |||
default: | |||
// ボタン以外はMotifStyleの形状を採用 | |||
QMotifStyle::drawPrimitive(element, option, painter, widget); | |||
break; | |||
} | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
2021年3月17日 (水) 07:02時点における版
概要
Qtにおいて、ウィンドウのメニューバー、ツールバー、ステータスバーの作成手順を記載する。
メニュー
サブメニュー名の&の直後の文字列がニーモニックとして使用される。
下図の"File"はQMenuクラス、"Open"はQActionクラスである。
"Open"の右側にあるアイコンを選択するとサブメニューを入力できる。
サブメニューを入力すると、その親メニュー(上図では、"Open")はQMenuクラスに変更される。
追加したアクションは、Qt Designer画面下にある[アクションエディタ]ペインに表示される。
ツールバー
ここでは、ツールバーのアイコン(24x24のpng形式が標準)を作成したとする。
リソースファイルの作成
[ファイル] - [ファイル/プロジェクトの新規作成] - 画面左の[ファイルとクラス] - [Qt] - [Qt リソースファイル]を選択する。
Qtのリソースディレクトリ名をResource、ディレクトリパスはプロジェクト直下とする。
リソースエディタを開く
Qtのメイン画面左にあるプロジェクトツリーの[Resources]直下に、リソースファイル(.qrc)が表示されるので、
リソースファイルを右クリックして、[エディタで開く]を選択する。
※注意
リソースファイルの新規作成時は、自動的にリソースエディタが開く。
新規登録
以下に、リソースファイルが空の場合の登録手順を示す。
- まず、リソースエディタを開いて、[Add Prefix]ボタンを押下する。
または、プロジェクトツリーにあるリソースファイルを右クリックして、[プレフィックスの追加...]を選択する。 - プレフィックス名に
/(スラッシュ)を入力する。 - 次に、[Add Files]ボタンを押下する。
- ファイル選択ダイアログ画面にて、リソースファイルを選択する。([Ctrl] + [A]キーで全選択できる)
※注意
プロジェクトツリーにあるリソースファイルを右クリックして、[既存ファイルの追加...]を選択する場合、既定のプレフィックスが作成される。
また、プレフィックスには任意の文字列を指定することができる。
追加登録
以下に、リソースを追加する手順を示す。
- まず、リソースエディタを開いて、プレフィックスの行を選択する。
または、プロジェクトツリーにあるプレフィックス名を右クリックして、[既存のファイルの追加...]を選択する。 - [Add Files]ボタンを押下する。
- ファイル選択ダイアログ画面にて、リソースファイルを選択する。([Ctrl] + [A]キーで全選択できる)
※注意
プロジェクトツリーのプレフィックス(/)を右クリックして、[既存のファイルを追加]を選択して登録することもできる。
リソースの削除
以下に、リソースを削除する手順を示す。
- 削除するリソースの行を選択する。
- [削除]ボタンを押下して、ファイルの削除画面を表示する。
- ファイル名を確認して、[OK]ボタンを押下する。
※注意
[Delete file permanently]にチェックを入力して削除する場合、ファイルの実体も削除される。
プロジェクトツリーで削除するリソースを右クリックして、[Remove...]を選択して削除することもできる。
リソースの保存
メニューを選択してリソースファイルを上書き保存する。
[ファイル] - [<リソースファイル名>の保存]
リソースをテキストエディタで編集する
テキストエディタを使用すると、リソースファイルの内容を直接変更することができる。
プロジェクトツリーのリソースファイル(.qrc)を右クリックして、[テキストエディタで開く]を選択する。
// リソースのテキストエディタでの編集例
<RCC>
<qresource prefix="/">
<file>images/drag.png</file>
<file>images/flower.png</file>
<file>images/photo.png</file>
</qresource>
</RCC>
※注意
テキストエディタでファイルパスの設定を削除しても、ファイルの実体が削除されることはない。
リソースの使用例
リソース名の文字列の取得
プロジェクトツリーのプレフィックスの下にリソース名が表示されている。
リソース名を右クリックして、[パス ":~" をコピー]を選択する。
使用例 1
ソフトウェアのタイトルバーのアイコンとして使用する。
// プロジェクトディレクトリ直下のimagesディレクトリ内にあるflower.pngファイル
setWindowIcon(QIcon(":/images/flower.png"));
使用例 2
ラベルにセットして、画像として表示する。
// プロジェクトディレクトリ直下のimagesディレクトリ内にあるphoto.pngファイル
std::unique_ptr<QLabel> pLabel = std::make_unique<QLabel>();
pLabel->setPixmap(QPixmap(":/images/photo.png"));
使用例 3
ドラッグ&ドロップにおいて、ドラッグ中の画像として使用する。
// プロジェクトディレクトリ直下のimagesディレクトリ内にあるdrag.pngファイル
std::unique_ptr<QDrag> pDrag = std::make_unique<QDrag>(this);
qDrag->setPixmap(QPixmap(":/images/drag.png"));
使用例 3
サブメニューにアイコンを割り当てる手順を示す。
- Qt Designer画面下のアクションエディタを右クリックして、[編集...]を選択する。
- [アクションを編集]画面が開くので、[アイコン(I):]項目の[...]ボタンを押下してリソースに登録した画像ファイルを選択する。
または、[▼]ボタンを押下して、[ファイルを選択...]から画像ファイルを選択することもできる。 - アクションエディタにて、アクションの左側に選択したアイコンが表示される。
使用例 4
ツールバーにアイコンを割り当てる手順を示す。
- ツールバーの追加は、Qt Designerに表示しているウィンドウを右クリックして、[ツールバーを追加]を選択する。
- ツールバーの追加後、アクションエディタから任意のアクションをツールバーへ、ドラッグ&ドロップする。
ステータスバー
ウインドウまたはダイアログにおいて、ステータスバーを表示する。
#include <QStatusBar>
QStatusBar StatusBar;
StatusBar->showMessage(tr("ステータスを表示中..."));
ステータスバーにプログレスバーを表示する場合、以下のように記述する。
#include <QStatusBar>
QStatusBar StatusBar;
QProgressBar bar;
StatusBar->addWidget(&bar);
bar.setMinimum(0);
bar.setMaximum(100);
for(int i = 0; i < 100; i++)
{
bar.setValue(i);
// 以下に処理を記述
// ...
}
StatusBar->removeWidget(&bar);
タブオーダーの設定
タブオーダーを無効化するには、ウインドウにある全てのウィジェットのタブオーダーを自分自身にする。
QList<QWidget *>widgets = findChildren<QWidget *>();
foreach(QWidget *widget, widgets)
{
widget->setTabOrder(widget, widget);
}
ウインドウおよびウィジェットの形状の変更
ウインドウおよびプッシュボタン等のウィジェットにおいて、形状を非矩形に変更する、および、背景を画像に変更する手順を記載する。
非矩形ウインドウ
まず、型抜き用画像を作成する。
型抜き用画像は、ウインドウの形状を表す部分を描画した画像のことであり、画像以外の箇所を透過ピクセルにする。
したがって、型抜き用画像は透過(アルファチャンネル)をサポートするフォーマットで保存する必要がある。
これは、Inkscape(ソフトウェア)を使用して、ウィンドウの形状を描画してPNG形式で保存する方法を推奨する。
次に、QMainWindowクラスまたはQDialogクラスのコンストラクタにおいて、以下のように記述する。
MainWindow::MainWindow()
{
Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::FramelessWindowHint;
setWindowFlags(flags);
QPixmap pixmap("window.png");
setMask(pixmap.mask());
// 型抜き画像のサイズに合わせる
resize( 300, 300 );
}
ウインドウおよびウィジェットの外観の変更
例えば、ウインドウの背景色を変更する場合、QMainWindowクラスまたはQDialogクラスのコンストラクタにおいて、以下のように記述する。
MainWindow::MainWindow()
{
// 背景色を設定する
setStyleSheet("* {background-color : rgb( 255, 255, 128 )}");
// 透過率を設定する
setWindowOpacity(0.7);
}
また、上記の変更は、Qt Designerから実行することもできる。
Qt Designer画面から、ウインドウまたはウィジェットの[プロパティ] - [styleSheet]項目を選択する。
[スタイルシートを変更]画面が開くので、[〜を追加]プルダウンから"background-color"と"Opacity"のQSSを追加する。
QSSをプロジェクト全体に適用する
一般的に、QSSの設定は各ウィジェットごとに設定するが、プロジェクト全体で共通のQSSを設定することもできる。
QSSの書式や構文は、CSSとほぼ同じである。
以下の例は、Qtに付属しているサンプルを参考にしている。(<Qtのインストールディレクトリ>/examples/widgets/stylesheetディレクトリ)
まず、MainWindow.qssファイルを作成する。
// MainWindow.qss
// QPushButtonの設定例
QPushButton
{
background-color: #000088;
border-color: #000044;
border-style: solid;
border-radius: 5;
}
QPushButton:hover
{
background-color: #880000;
}
QPushButton:pressed
{
background-color: #008800;
}
次に、MainWindow.qssファイルをQApplicationクラスのsetStyleSheetメソッドに渡す。
QApplicationクラスのインスタンスはシングルトンとして生成されているため、そのインスタンスから実行する。
また、QSSファイルはリソースとして登録しておくとよい。
以下の例では、main関数でQSSファイルを読み込み、設定している。
QSSには、他にも様々なクラスやセレクタが存在する。
#include "MainWindow.h"
#include <QtCore>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFile file(":/qss/MainWindow.qss");
file.open( QFile::ReadOnly );
QString strStyles = QLatin1String(file.readAll());
app.setStyleSheet( strStyles );
MainWindow w;
w.show();
return app.exec();
}
ウィンドウの背景を画像にする
QStyleクラスは、ウィンドウの外見を詳細に設定することができる。
QStyleクラスを継承した派生クラスを作成して、必要な項目のうち、比較的簡単に設定できる項目である。
QStyleクラスを継承した派生クラスは数種類用意されており、OSにより使用できるものが決まっている。
以下の例は、Qtに付属しているサンプルを参考にしている。(<Qtのインストールディレクトリ>/examples/widgets/stylesheetディレクトリ)
ここでは、examples/widgets/stylesディレクトリに合わせて、QMotifStyleクラスを継承した派生クラスを作成している。
設定後、QMainWindowクラスまたはQDialogクラスのコンストラクタにおいて、以下のように記述して適用する。
QApplication::setStyle(new MyStyle); // MyStyleクラスは、QMotifStyleクラスの派生クラス
ウィンドウの背景を画像にするには、まず、polishメソッドをオーバーライドする。
次に、画像を設定するためのsetTextureメソッド(メソッド名は任意)を作成する。
#pragma once
#include <QtGui/QMotifStyle>
class MyStyle : public QMotifStyle
{
public:
MyStyle();
~MyStyle();
void polish(QPalette &palette);
private:
static void setTexture(QPalette &palette, QPalette::ColorRole role, const QPixmap &pixmap);
};
polishメソッドとsetTextureメソッドは、以下のように記述する。
背景画像は、型抜き画像としても使用することができる。したがって、背景用画像と型抜き用画像の2つを用意する必要はない。
void MyStyle::polish(QPalette &palette)
{
QPixmap backgroundImage("window.png");
setTexture(palette, QPalette::Window, backgroundImage);
}
void MyStyle::setTexture(QPalette &palette, QPalette::ColorRole role, const QPixmap &pixmap)
{
for(int i = 0; i < QPalette::NColorGroups; ++i)
{
QColor color = palette.brush(QPalette::ColorGroup(i), role).color();
palette.setBrush(QPalette::ColorGroup(i), role, QBrush(color, pixmap));
}
}
ボタンのスタイルの変更
上記のセクションにも記述したpolishメソッドを使用して、ボタンのスタイルを変更することができる。
QPaletteクラスのコンストラクタでは、ボタンの色を定義する。
ウィンドウおよびコントロールの設定の詳細は、Qtの公式WebサイトのQPalleteリファレンスを参照すること。
void MyStyle::polish(QPalette &palette)
{
QPixmap backgroundImage("window.png");
// 追加部分 : ボタンスタイルを変更
palette = QPalette(QColor(0, 0, 0, 50));
palette.setBrush(QPalette::ButtonText, Qt::cyan);
setTexture(palette, QPalette::Window, backgroundImage);
}
また、ボタンの背景を画像に変更するには、以下のように記述する。
void MyStyle::polish(QPalette &palette)
{
QPixmap backgroundImage("window.png");
// 追加部分 : ボタンの背景を画像にする場合
QPixmap buttonImage("button.png");
// 追加部分 : ボタンを押下した時の背景
QPixmap midImage = buttonImage;
setTexture(palette, QPalette::Button, buttonImage);
setTexture(palette, QPalette::Mid, midImage);
setTexture(palette, QPalette::Window, backgroundImage);
}
ウインドウおよびウィジェットの形状を変更する
ウインドウおよびウィジェットの形状を変更するには、ウィジェットの形状をプログラムで全て記述する必要がある。
例えば、ボタンの形状を変更する場合、外形を描画するだけではなく、
ボタンが立体的に見えるように陰影の描画、ボタン押下時の陰影の変化等も記述する必要がある。
(或いは、QSS + 背景画像で形状を変更できる可能性がある)
ウインドウおよびウィジェットの形状を記述するには、QStyleクラスを継承した派生クラスを作成して、
QStyleクラスのdrawPrimitiveメソッドをオーバーライドする。
以下の例では、ボタンの形状を変更している。
Qtに付属しているサンプルを参考にしている。(<Qtのインストールディレクトリ>/examples/widgets/styles/norwegianwoodstyle.cppファイル)
void MyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
switch(element)
{
// ウィジェットのPrimitiveの種類を指定する(ウィジェットの種類ではない)
case PE_PanelButtonCommand:
int delta = (option->state & State_MouseOver) ? 64 : 0;
QColor slightlyOpaqueBlack(0, 0, 0, 63);
QColor semiTransparentWhite(255, 255, 255, 127 + delta);
QColor semiTransparentBlack(0, 0, 0, 127 - delta);
int x, y, width, height;
option->rect.getRect(&x, &y, &width, &height);
// 以下の3行のみ追記する
// ボタンの形状を楕円にする
QPainterPath path;
path.addEllipse(option->rect);
// これ以降の記述はコピーである
// ボタンの押下時と非押下時の陰影等を記述する
// 様々な形状に対応している
int radius = qMin(width, height) / 2;
QBrush brush;
bool darker;
const QStyleOptionButton *buttonOption = qstyleoption_cast<const QStyleOptionButton *>(option);
if(buttonOption && (buttonOption->features & QStyleOptionButton::Flat))
{
brush = option->palette.background();
darker = (option->state & (State_Sunken | State_On));
}
else
{
if(option->state & (State_Sunken | State_On))
{
brush = option->palette.mid();
darker = !(option->state & State_Sunken);
}
else
{
brush = option->palette.button();
darker = false;
}
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->fillPath(path, brush);
if(darker)
{
painter->fillPath(path, slightlyOpaqueBlack);
}
int penWidth;
if(radius < 10)
penWidth = 3;
else if (radius < 20)
penWidth = 5;
else
penWidth = 7;
QPen topPen(semiTransparentWhite, penWidth);
QPen bottomPen(semiTransparentBlack, penWidth);
if(option->state & (State_Sunken | State_On))
{
qSwap(topPen, bottomPen);
}
int x1 = x;
int x2 = x + radius;
int x3 = x + width - radius;
int x4 = x + width;
if(option->direction == Qt::RightToLeft)
{
qSwap(x1, x4);
qSwap(x2, x3);
}
QPolygon topHalf;
topHalf << QPoint(x1, y) << QPoint(x4, y) << QPoint(x3, y + radius) << QPoint(x2, y + height - radius) << QPoint(x1, y + height);
painter->setClipPath(path);
painter->setClipRegion(topHalf, Qt::IntersectClip);
painter->setPen(topPen);
painter->drawPath(path);
QPolygon bottomHalf = topHalf;
bottomHalf[0] = QPoint(x4, y + height);
painter->setClipPath(path);
painter->setClipRegion(bottomHalf, Qt::IntersectClip);
painter->setPen(bottomPen);
painter->drawPath(path);
painter->setPen(option->palette.foreground().color());
painter->setClipping(false);
painter->drawPath(path);
painter->restore();
break;
default:
// ボタン以外はMotifStyleの形状を採用
QMotifStyle::drawPrimitive(element, option, painter, widget);
break;
}
}
