2011/05/16

Qt Designerとカスタムウィジェット

【Qt をもうちょっといじってみよう】

昨日に引き続き、Qt (+ OpenCV)のお勉強。表題でカスタムウィジェットとか言っているけど、単にこんなものを作っただけだったりして。
昨日と比べて、なんかスクロールバー的なものがくっついているあたりがおしゃれ。



【とにかく作り始める】

まず、昨日と同様、以下のようなメインウィンドウ(MainWindow クラス)を作り、画面いっぱいに ScrollArea ウィジェットを配置する。なお、ObjectName プロパティは scrollArea にした。

外観だけ作って、コードはまだ書かない。
このままでは、scrollArea は空っぽのままなので、これから作る『画像表示機能をくっつけた改造ウィジェット』をセットしてやる。

あ、その前に、ちゃんとプロジェクトファイル(~.pro)に
INCLUDEPATH += C:/OpenCV2.0/include/opencv
DEPENDPATH += C:/OpenCV2.0/include/opencv

win32:CONFIG(release, debug|release): LIBS += -LC:/OpenCV2.0/lib/release/ -lcv200
else:win32:CONFIG(debug, debug|release): LIBS += -LC:/OpenCV2.0/lib/debug/ -lcv200d

win32:CONFIG(release, debug|release): LIBS += -LC:/OpenCV2.0/lib/release/ -lcxcore200
else:win32:CONFIG(debug, debug|release): LIBS += -LC:/OpenCV2.0/lib/debug/ -lcxcore200d

win32:CONFIG(release, debug|release): LIBS += -LC:/OpenCV2.0/lib/release/ -lhighgui200
else:win32:CONFIG(debug, debug|release): LIBS += -LC:/OpenCV2.0/lib/debug/ -lhighgui200d
を書き加える必要がある。詳しくは昨日の日記参照(あんまり詳しくないけれど)。



【改造ウィジェット作成】

次に、Qt Designer フォームクラスを新規作成。テンプレートは Widget を選択して、Form クラスと名付ける(デザインは初期状態のまま手をつけない)。

すると form.hform.cpp が自動生成されるので、以下のように書き換えた。

form.h
#ifndef FORM_H
#define FORM_H

#include <QImage>
#include <QPainter>
#include <QWidget>

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>

namespace Ui {
    class Form;
}

class Form : public QWidget
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = 0);
    ~Form();

    // 表示したいIplImageをセットする
    void setIplImage(const IplImage*);

protected:
    // オーバーライド∩( ・ω・)∩
    void Form::paintEvent(QPaintEvent*);

private:
    Ui::Form *ui;
    QImage *qImg;
};

#endif // FORM_H

form.cpp
#include "form.h"
#include "ui_form.h"

Form::Form(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Form),
    qImg(NULL) // qImgを初期化
{
    ui->setupUi(this);
}

Form::~Form()
{
    delete ui;
    if(qImg != NULL) delete qImg;
}

void Form::paintEvent(QPaintEvent *) {
    if(qImg != NULL) {
        QPainter widgetPainter(this);
        widgetPainter.drawImage(0, 0, *qImg);
    }
}

// IplImage から QImage に変換!
void Form::setIplImage(const IplImage *iplImg)
{
    if(iplImg == NULL) return;

    int h = iplImg->height;
    int w = iplImg->width;
    int channels = iplImg->nChannels;

    if (qImg != NULL) delete qImg;
    qImg = new QImage(w, h, QImage::Format_ARGB32);

    char *data = iplImg->imageData;
    for (int y = 0; y < h; y++, data += iplImg->widthStep)
    {
        for (int x = 0; x < w; x++)
        {
            char r, g, b, a = 0;
            if (channels == 1)
            {
                r = g = b = data[x * channels];
            }
            else if (channels == 3 || channels == 4)
            {
                r = data[x * channels + 2];
                g = data[x * channels + 1];
                b = data[x * channels];
            }

            if (channels == 4)
            {
                a = data[x * channels + 3];
                qImg->setPixel(x, y, qRgba(r, g, b, a));
            }
            else
            {
                qImg->setPixel(x, y, qRgb(r, g, b));
            }
        }
    }
    setMinimumSize(iplImg->width, iplImg->height);
    update();
}

別に大した事はやっておらず、QWidget を継承して、与えられた画像を表示するようにちょこちょこ書き換えただけ。というか、昨日 MainWindow に書いたような処理をほとんど移しただけである(が、ほんのちょっとだけ改良したりもした)。



ふたたび MainWindow クラスに戻り、ヘッダとソースを以下のように書き換える。

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "form.h"   // さっき作ったFormを使う

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_action_Open_triggered();

private:
    Ui::MainWindow *ui;
    Form *form;
};

#endif // MAINWINDOW_H

mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include<QFileDialog>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    form(new Form)
{
    ui->setupUi(this);
    ui->scrollArea->setWidget(form);
}

MainWindow::~MainWindow()
{
    // delete form; ←これを書かなくてもちゃんとメモリが解放された。すごい。
    delete ui;
}

void MainWindow::on_action_Open_triggered()
{
    QString fileName =
        QFileDialog::getOpenFileName
            (this, tr("Open Image"), ".",
             tr("Image (*.jpg *.jpeg *.png *.bmp)"));

    if(!fileName.isEmpty()) {
        IplImage *img =
            cvLoadImage(fileName.toLocal8Bit());

        if(img != NULL) {
            // formにIplImageをセット
            form->setIplImage(img);

            // そしてすぐ解放
            cvReleaseImage(&img);
        }
    }
}

スクロールエリアは、QScrollArea::setWidget() に子ウィジェット(ここでは form )を渡す。

すると、form の親ウィジェットが自動的に設定される。

ちなみに Qt は、親ウィジェットが死ぬとき、自動的に子ウィジェットも delete されるので、ここでは敢えて form の解放コードを書く必要はない。

今回の肝は、ウィジェットをいじくり回したいなら、(当該ウィジェットの)基底クラスを継承しててきとうにオーバーライドすれば OK というお話である。めでたし。



【おまけ】

そういえば昨日、てきとうに作り過ぎた事もあって、ファイルを開こうとするたびに、 "." から目的のファイルを探しにいかなければならなかった。これは地味に使い勝手が悪く、デバッグするたびに溜飲が上がっていた。

そこで、『(例えば)一度マイピクチャから画像を開いたら次からはマイピクチャを拠点にファイルを探しに行けるように改良したいな』と思ったので、頭の体操がてら on_action_Open_triggered() を(ほんの2、3行だけ)いじってみた。

void MainWindow::on_action_Open_triggered()
{
    static QDir dir(tr("."));   // 追加!
    QString fileName =
        QFileDialog::getOpenFileName
            (this, tr("Open Image"), dir.absolutePath(),
             tr("Image (*.jpg *.jpeg *.png *.bmp)"));

    if(!fileName.isEmpty()) {
        IplImage *img =
            cvLoadImage(fileName.toLocal8Bit());

        if(img != NULL) {
            // 前回開いたファイルの場所を静的変数に格納
            // (次に開くときは、この場所が拠点になる)
            dir = QFileInfo(QFile(fileName)).dir();

            form->setIplImage(img);
            cvReleaseImage(&img);
        }
    }
}

ここでは拠点となるディレクトリを静的記憶領域 dir に格納している。private なメンバ変数でもよいかも知れないが、一つの関数でしか使わないような変数をいちいちヘッダファイルに宣言するのも面倒だったので、今回は敢えて static 変数を選んだ。

これで幸せになれるね。

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)