Qt/Gcc


                                                                                                                                                               2012.08.16作成
2-4mdichildクラス

MDIサンプルは、テキストエディタになっています。これを画像表示できるように改造します。
画像表示、データ表示は子ウィンドウに表示できるようにします。
●mdichildクラス
画像表示を行う子ウィンドウを定義します。子ウィンドウに処理用のデータを持たせて、何か処理を実施するときには、
このウィンドウからデータをmainwindowに渡して、mainwindowからのデータは新しく作った子ウィンドウに表示させるため
ちょっとわかりにくいコードとなっています。
子ウィンドウに表示と表示されている画像データを保持して、必要なときに子ウィンドウからデータを取り出します。
取り出されるウィンドウは、アクティブになっている子ウィンドウが対象となります。

MdiChildクラスの親クラスをQTextEditからQWidgetに変更します。

・MdiChild.h
//class MdiChild : public QTextEdit

 class MdiChild : public QWidget
 
SDIと同様にSceneとViewを生成して、画像はGraphicItemとして表示させます。
そのために、SceneとViewクラスを宣言します。
SceneとViewは子ウィンドウ毎に持たせるようにするため、MdiChild.hの中で宣言します。

//#include <QTextEdit> これはTextEditクラスを使う場合の宣言。ここではコメントアウト。
//add
#include <QGraphicsScene>
#include <QGraphicsView>
//
#include <QMdiSubWindow>//これはおまじない。いらない。

//add class
class Scene;//Sceneクラスの宣言
class View;//Viewクラスの宣言

//以下のヘッダファイルもインクルードしておく。
#include <QtGui>//menu
#include<qimage.h>//pixmap用のincludeファイル設定
#include<qpixmap.h>//image用のincludeファイル設定
これらは、画像を扱うためのqimageクラスとピックスマップを扱うための
qpixmapクラスです。


以下のファイルをインクルードします。
これらはそれぞれ、二次元配列を動的に確保するためのクラスです。
#include "cdatacreate2d.h"//二次元配列を動的に確保するクラス。例:data[hsize][vsize]
#include "cfftdatacreate2d.h"//FFTを実装する場合は、実部と虚数部の配列を確保するクラスを定義

以下はサンプルプログラムをそのまま利用します。
/////
     MdiChild();

     void newFile();
     bool loadFile(const QString &fileName);
     bool save();
     bool saveAs();
     bool saveFile(const QString &fileName);
     QString userFriendlyCurrentFile();
     QString currentFile() { return curFile; }
///


画像を読み込んだときにウィンドウサイズと画像サイズをあわせる(Fitting)させたいため、
コンストラクタでViewとSceneのサイズを設定できるようにします。
具体的には、以下のコンストラクタを多重継承します。

 MdiChild(int fxsize,int fysize);

ここで、SDIウィンドウでも実施したようにSceneとViewクラス変数を定義して、MDIでもScene、Viewモデルとします。

    Scene *m_scene;
    View *m_view
;
 
そして、以下のように画像の読み込み、書き出し、表示に必要な変数を定義します。
画像は今回もQGraphicsPixmapItemを使いました。
 
    QGraphicsPixmapItem* gpmapI;//pixmap描画用のItemの確保
    int flag;//画像読み込み用のフラグ
    int flaggetfunc();//子ウィンドウのflagを確認するインターフェース関数
    QImage img;
    QImage inImage;//画像読み込み、保持用
    QPixmap pixmap_in;//デバイス依存のオフスクリーン構造体
   
 以下は、自分で定義した配列確保のクラスです。
 
    CDataCreate2D *m_dcrt2d;//CDataCreate2Dで二次元配列を確保するクラス変数。今回は使用しません。
    Cfftdatacreate2d *m_fftdc2d;//画像データだけでなくFFTを実施するときに必要な配列を確保するクラス変数
 
 子ウィンドウに表示させる画像データを渡すための関数です。
    int datainp(CDataCreate2D *m_impd2d);//画像を子ウィンドウに渡すための関数。こちらは今回は使わない。
    int datainp(Cfftdatacreate2d *m_impd2d);
    int Fft2d_setchild();//FFT処理を実施して、そのデータを子ウィンドウに渡した場合にフラグ(fftwindowflag)をonにする。on:1、off:0
    int IFft2d_setchild();//逆FFT処理を実施して、そのデータを子ウィンドウに渡した場合にフラグ(fftwindowflag)をoffにする。on:1、off:0
    int childxsize();//子画面画像データの水平方向サイズを返す
    int childysize();//子画面画像データの垂直方向サイズを返す
    int fftwindowflag;//FFTした結果が存在するウィンドウか。初期値:0、FFTデータがある場合は1とする。
    int getchilddata(Cfftdatacreate2d *);//子ウィンドウにあるデータ(画像、FFTデータ)をコピーする。
    int getchilddata(CDataCreate2D *);//子ウィンドウにあるデータ(画像)をコピーする。
    int fftchildxsize();//FFTデータ(Cfftdatacreate2dクラス)の水平サイズを返す
    int fftchildysize();//FFTデータ(Cfftdatacreate2dクラス)の垂直サイズを返す
    int Winer_setchild();//Winerフィルタを実施したデータを子ウィンドウに渡したときにfftflagを立てる。

これらは、すべて、データの受け渡しと受け渡し結果のフラグのon、offの関数となっています。


 そしてprotectedに描画が発生したときに呼び出させる、paintEventを定義します。
 ただ、QGraphicsItemを使う場合は不要かもしれません。
 
 void paintEvent(QPaintEvent *event);
 
 以下は、サンプルプログラムから変更しません。
 protected:
    void closeEvent(QCloseEvent *event);

private slots:
    void documentWasModified();
   
private:
    bool maybeSave();
    void setCurrentFile(const QString &fileName);
    QString strippedName(const QString &fullFileName);

    QString curFile;
    bool isUntitled;

・midchild.cpp
子ウィンドウクラスの実装です。このファイルでは、主に画像表示と画像データの受け取りをします。
基本的にはサンプルプログラムとの差異を記載していきます。
#include <QtGui>

#include "mdichild.h"

//以下のインクルードファイルが追加した分です。
#include <QGraphicsScene>
#include "scene.h"//QGraphicsSceneクラスを基底オブジェクトとして生成する。
#include "view.h"

#include "cdatacreate2d.h"//画像を配列に入れるためのクラス。データをまとめておく。
#include "cfftdatacreate2d.h"
#include"cimagefunc.h"
#include "CFft_2D.h"

コンストラクタは、サンプルからの追加が多いです。
以下コードを示しながら説明します。
MdiChild::MdiChild()
{
    setAttribute(Qt::WA_DeleteOnClose);
    isUntitled = true;

    this->setGeometry(100,100,480,320);//追加しています。ウィンドウの初期位置と初期の水平垂直の幅を定義しています。
//以下描画用のビューとシーンをつくる。
    m_view = new View();//ビューを確保する。
    m_scene = new Scene();//シーンを確保する。

    gpmapI=new QGraphicsPixmapItem();//pixmap描画用のItemの確保
    m_scene->setSceneRect(0,0,640,480);//シーンのサイズを初期化する。

    //これは必要ならば追加、背景を塗るm_scene->addRect(0, 0, 640, 480, QPen(Qt::black), QBrush(Qt::white));

 m_view->setScene(m_scene);//ビューにシーンをセットする。

 QVBoxLayout *layout = new QVBoxLayout(this);//レイアウトを作る。
layout->setMargin(0);
layout->addWidget(m_view);//先ほどののビューをレイアウトに貼り付ける。
 setLayout(layout);//レイアウトはウィンドウに貼り付ける

 flag = 0;//画像読み込み用のフラグ

 m_dcrt2d = NULL;//画像確保用クラスの変数を初期化。この変数は今回使用しない。
 m_fftdc2d = NULL;//画像確保用およびFFTデータクラスの変数を初期化。
 fftwindowflag=0;//子ウィンドウのデータとしてFFT後のデータがあるか。これは、IFFTのときにFFTデータがあるかないかを確認するため。

}

//今回のプログラムはこちらのコンストラクタを使用している。
//fxsize:読み込んだ画像、あるいは、処理後の画像データサイズの水平方向サイズ
//fysize:読み込んだ画像、あるいは、処理後の画像データサイズの垂直方向サイズ
MdiChild::MdiChild(int fxsize,int fysize)
{
    setAttribute(Qt::WA_DeleteOnClose);
    isUntitled = true;

    this->setGeometry(0,0,fxsize,fysize);//追加。ウィンドウの初期位置と初期の水平垂直の幅を定義しています。
//以下描画用のビューとシーンをつくる。
    m_view = new View();//ビューを確保する。
    m_scene = new Scene();//シーンを確保する。

    gpmapI=new QGraphicsPixmapItem();//pixmap描画用のItemの確保

    m_scene->setSceneRect(0,0,fxsize,fysize);//シーンのサイズを渡されたサイズで初期化する。
 
 m_view->setScene(m_scene);//ビューにシーンをセットする。

 QVBoxLayout *layout = new QVBoxLayout(this);//レイアウトを作る。
layout->setMargin(0);
layout->addWidget(m_view);//先ほどののビューをレイアウトに貼り付ける。
 setLayout(layout);//レイアウトはウィンドウに貼り付ける

 flag = 0;//画像読み込み用のフラグ

 m_dcrt2d = NULL;//画像確保用クラスの変数を初期化。この変数は今回使用しない。
 m_fftdc2d = NULL;//画像確保用およびFFTデータクラスの変数を初期化。
 fftwindowflag=0;//子ウィンドウのデータとしてFFT後のデータがあるか。これは、IFFTのときにFFTデータがあるかないかを確認するため。

}
この関数は再描画の時に発生するイベントですが、実行するとワーニングが発生します。
不要かもしれませんが、念のため実装しています。
void MdiChild::paintEvent(QPaintEvent *event){

    m_view->show();//ウィンドウの再描画
}
void MdiChild::newFile()関数は、サンプルから一部コメントアウトしました。
以下がコメントアウト分です。documentWasModified()関数がどのような動きをしているか
理解できてません。

   // connect(document(), SIGNAL(contentsChanged()),
   //         this, SLOT(documentWasModified()));

以下は画像ファイルの読み込みです。mainwindowクラスでも読み込みしていますが、mainwindowクラスは画像サイズのみ
取得したかった(子ウィンドウのサイズを決めるため)のですが、ここでは、画像データそのものを読み込んでいます。
画像データを読み込んで読み込んだ画像を子ウィンドウに表示させます。
bool MdiChild::loadFile(const QString &fileName)
{

    //サンプルでは、file.openで読み込んでいる。ここでは画像のみを対象として読み込む。
    if(!inImage.load(fileName)){
           QMessageBox::warning(0,"No file",fileName);//ファイルが読み込めなかった場合メッセージを表示する。
           return false;//読み込めなかった場合はなにもせずに関数から抜け出す。
      }
//画像が一度でも読み込まれていて、画像を確保する配列が確保されていたら、一度廃棄する。
//同時に一度でもsceneに画像アイテムをセットしているならば、とりはずす。取り外さないとセットされる毎に
//sceneに画像アイテムがたまっていく。
    if(flag==1){
           delete m_dcrt2d;//一度配列用のデータクラスを確保していれば、廃棄する。
           m_scene->removeItem(gpmapI);//一度ファイルを読み込んでいたらItemをsceneから取り除く
    }

    m_dcrt2d=new CDataCreate2D((int)inImage.width(),(int)inImage.height());//処理用配列の確保のためのクラス作成
    m_dcrt2d->mImage2data(&inImage);//QImageクラスに取り込んだ画像データをCDataCreate2Dクラスのm_dcrt2dにコピーする。
   
    this->resize(inImage.width()+30,inImage.height()+80);//子ウィンドウのサイズをリサイズしたかった。できない。このコードは不要。

    m_scene->setSceneRect(0,0,(int)inImage.width(),(int)inImage.height());//読み込んだ画像データサイズに合わせてシーンサイズも設定しなおす。

    pixmap_in.convertFromImage(inImage,Qt::AutoColor);//QImageクラスをpixmapデータに変換する。
    gpmapI->setPixmap(pixmap_in);//GraphicsPixmapItemに上記のピックスマップをセットする。
    m_scene->addItem(gpmapI);//sceneにピックスマップを追加する。
    m_view->setScene(m_scene);//viewにsceneをセットする。

    flag=1;//flagに1をセットして、画像配列が確保済み、かつ、pixmapItemに画像がセットされたことを示す。

    this->update();//再描画する。paintEventを呼び出す
 
    setCurrentFile(fileName);

   //以下サンプル通り。使い方がわかりませんでした。
   // connect(document(), SIGNAL(contentsChanged()),
   //         this, SLOT(documentWasModified()));

    return true;
}
子画面に表示されたデータをセーブします。ここはサンプルプログラムと同じです。
isUntitledの変数によって子画面ウィンドウの画像が読み込んだ画像か、新規に作成した画像かを見分けて
saveAs()かsaveFile()関数を呼び出します。
 bool MdiChild::save()

画像ファイルのセーブです。処理した結果の画像(FFTは結果の画像のみセーブ)
サンプルプログラムはテキストでしたので、QImageクラスのsave関数を使っています。

bool MdiChild::saveFile(const QString &fileName)
{
    if(!inImage.save(fileName,0,-1))//2番目の引数に0を設定すると拡張子でセーブするフォーマットが自動的に決まる。3番目はデフォルトで0にしておく。
    {
        QMessageBox::warning(0,"can't save","fail!!");
    }
    return true;
}

以下の関数はサンプルプログラムと同じです。
QString MdiChild::userFriendlyCurrentFile()
void MdiChild::closeEvent(QCloseEvent *event)
QString MdiChild::strippedName(const QString &fullFileName)

以下の関数は実装していますが、中身はコメントアウトしています。
私にはどのように動作するか理解できませんでした。コメントアウトの部分はサンプルプログラムと同じです。
void MdiChild::documentWasModified()、bool MdiChild::maybeSave()

この関数もほとんどサンプルプログラムと同じです。よくわからない部分はサンプルファイルからコメントアウトしてます。
void MdiChild::setCurrentFile(const QString &fileName)

3つの**_setchild()は、ほぼ同じことをしています。ここでは、Fft2d_setchild()のみ説明します。
int MdiChild::Fft2d_setchild()
int MdiChild::IFft2d_setchild()
int MdiChild::Winer_setchild()

この関数はFFTを実施したときにmainwindowから呼び出されます。
この関数が呼び出されるということは、FFTを実施してそのデータを新しく作った子ウィンドウに渡したということなので、
fftwindowflagに1をセットして、子ウィンドウにFFTのデータがあることを示します。
int MdiChild::Fft2d_setchild()
{

    QString fftname;

    fftname = tr("fftdata");
    setWindowTitle(fftname);//ウィンドウタイトルをセットする。
    fftwindowflag=1;//fftdataの存在する子ウィンドウであることを示すために1をセットする。


    return 1;
}
注意:"FFTのデータでない"という表現は、周波数領域を表したデータでない、ということです。JPEGなどのデータは実空間のデータで、FFTした後のデータは周波数空間のデータです。

この関数はmainwindowで画像処理関数を呼び出して、画像処理したデータを子ウィンドウにセットするときによびだします。
この関数が呼ばれる順番は、以下のように想定してます。
 1.画像ファイルから画像を読み込む
 2.子ウィンドウ(以下、初期子ウィンドウとする)を作成して、初期子ウィンドウに画像を表示する。
 3.画像処理を実施するときは、初期子ウィンドウ(画像が表示されている)をアクティブ*1にして、メニューから実施したい画像処理を選択する。
 4.アクティブになった子ウィンドウ(この場合は初期子ウィンドウ)のgetchilddataでアクティブな子ウィンドウからデータをmainwindowが用意している配列にコピーします。
 5.画像処理されたデータは、mainwindowの配列に一度コピーされる。
 6.画像処理後のデータを表示させるために、新しい子ウィンドウ(以下、新子ウィンドウとする)を作成する。
 7.新子ウィンドウのdatainp関数(以下の関数)をmainwindowから呼び出して、画像処理データを渡す。
 8.画像データをQImageクラスにコピーして画像を表示する。

//子ウィンドウにデータを渡す。
//m_impd2d:fftのデータと画像画像データを格納するクラス変数
int MdiChild::datainp(Cfftdatacreate2d *m_impd2d)
{

    //注意
    //この関数は、処理した結果を新しいウィンドウを作成して
    //結果を表示するために、他の子ウィンドウからのデータを
    //mainwindow経由で受け渡ししている。よって、たとえばopen関数内で作成したQImageやCDataCreate2Dのm_dcrt2dは
    //新たに確保している。つまり、他からデータの引渡しがあるいがいnewやopenをよびだしているのと一緒と考えること。
   
    if(flag==1){//flag==1:ファイルを読み込んでいる。flag==0:ファイルを読み込んでいない。
   
     //datainp関数が呼ばれるときは、新規に子ウィンドウを作っているため、基本的にはここの処理が発生することはないはず。念のために実装する。
        m_scene->removeItem(gpmapI);//一度ファイルを読み込んでいたらItemをsceneから取り除く
    }

    if(m_fftdc2d != NULL) //datainp関数が呼ばれるときは、新規に子ウィンドウを作っているため、基本的にはここの処理が発生することはないはず。念のために実装する。
        delete m_fftdc2d ;

     m_fftdc2d =new Cfftdatacreate2d((int)m_impd2d->hsize,(int)m_impd2d->vsize);//新子ウィンドウ
    QImage outImage(m_impd2d->hsize,m_impd2d->vsize,QImage::Format_RGB32);//一時保管用のQImageクラスを作る。

    //新子ウィンドウのデータ保持クラスCfftDataCreate2Dにmainwindowクラスから渡された画像データのサイズをセットする。
    m_fftdc2d->hsize=m_impd2d->hsize;
    m_fftdc2d->vsize=m_impd2d->vsize;
  
   //mainwindowクラスから渡された画像データを新子ウィンドウのデータ保持クラスCfftDataCreate2Dにコピーする。
    for(int y=0;y<m_fftdc2d->vsize;y++){
        for(int x=0;x<m_fftdc2d->hsize;x++){
            m_fftdc2d->Rdata[x][y]=m_impd2d->Rdata[x][y];
            m_fftdc2d->Gdata[x][y]=m_impd2d->Gdata[x][y];
            m_fftdc2d->Bdata[x][y]=m_impd2d->Bdata[x][y];

           m_fftdc2d->Rar[x][y]=m_impd2d->Rar[x][y];
           m_fftdc2d->Rai[x][y]=m_impd2d->Rai[x][y];
           m_fftdc2d->Gar[x][y]=m_impd2d->Gar[x][y];
           m_fftdc2d->Gai[x][y]=m_impd2d->Gai[x][y];
           m_fftdc2d->Bar[x][y]=m_impd2d->Bar[x][y];
           m_fftdc2d->Bai[x][y]=m_impd2d->Bai[x][y];

        }
    }

            m_fftdc2d->mdata2Image(&outImage);
            if(!flag)//flag==0なら画像は読み込んでいないので、inImageにコピーする。
                inImage=QImage(outImage);//一時的保管用のQimageクラスからmdichildクラスのメンバー変数QImage inImageにコピーする。

             m_scene->setSceneRect(0,0,(int)inImage.width(),(int)inImage.height());

      //以下は描画処理
     pixmap_in.convertFromImage(outImage,Qt::AutoColor);
          gpmapI->setPixmap(pixmap_in);//GraphicsPixmapItemに上記のピックスマップをセットする。
        m_scene->addItem(gpmapI);//sceneにピックスマップを追加する。
        m_view->setScene(m_scene);//viewにsceneをセットする。

        flag=1;

        this->update();//再描画する。paintEventを呼び出す
        if(m_impd2d->Rai!=NULL)//ここは不要か?バグになる可能性あり。
            fftwindowflag=1;

        return 1;
}


インデックスに戻る
トップページに戻る