Qt習作之簡易圖像瀏覽器 v0.1

怒犯天條 (Dogma, 1999)

應該算是上一篇顯示圖片練習的延伸,透過這次實作,更進一步了解Qt的事件處理方式。不像GTK+每個事件只能傳遞一個指標,Qt可以透過Class的data member來傳遞資料,要多少有多少,而不用像GTK+總要費勁把全部要傳的資料都塞在一個struct裏,再迂迴地套指標來使用。

不知道理解上有沒有錯誤,我一樣把資料傳給singal/slot函式的data member,一開始都正常,都後來資料越加越多,就逐漸出現問題,可以成功編譯,但編譯出來的程式檔執行中會出錯,有時候刪掉一個data member可以正確執行,有時候則否,讓人毫無頭緒,完全找不出問題所在。

或許我的部分實作方式並不符合Qt的理念,所以到最後只有在Dev C++中編譯的程式檔可以正確執行,正規qmake編譯出來的則沒辦法打開圖片,還未顯示就出現錯誤訊息並強制關閉,真的有點讓人頭痛。

再者,程式執行所耗用的記憶體量並不低,開啟一張小圖片就要十幾mb,而尺寸大的圖片則要二十幾mb,甚至還可能達到三十mb。或許Qt會有一些針對圖片優化的函式,之後再找找看。

原本還想加入QScrollArea,讓過大的圖片可用捲軸方式呈現,但實作過程卻不斷受挫,不知怎麼搞的,當圖片開啟時總會出錯;還有不知如何把scrollArea內部的圖片尺寸反應出來,使得視窗大小無法跟著調整。種種問題接踵而至,最後只好暫且放棄這個打算。往後等認識更深入後,一定要把這個問題徹底解決了,以解今日之恨。

 


 

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* 圖片瀏覽器 v0.1
 
 功能:1.可開啟圖片。
     2.可讀取圖片資訊,檔名、路徑、尺寸和檔案大小。  
     3.可用+-鍵縮放視窗中的圖片尺寸,並顯示縮放後的比例數值。
     4.可用左右方向鍵對目錄中的圖片進行循序瀏覽。
     5.可用滑鼠滾輪對目錄中的圖片進行循序瀏覽。 
     6.可顯示目前圖片在目錄中的排序,與目錄下圖片總數。 
     7.可按滑鼠(左中右鍵均可)將視窗修正為最佳大小。 
       
 缺點:1.圖片過大會超出螢幕,無法以捲軸式呈現。 
    2.程式開啟大型圖檔所佔的記憶體過於龐大,可達30mb以上。 
    3.以cmd批次檔編譯出來的程式檔執行中會產生錯誤, 
     唯有在Dev C++中編譯的程式檔才能正確運行。
       

 日期:2010.6.14 
*/  
  

#include <QApplication>
#include <QBitmap>
#include <QPushButton>
#include <QVBoxLayout>
    
#include "ButtonClicked.h"
#include "OpenPicEvent.h"
   

QString toUnicode( char *strText )
// 使之可以顯示中文(big5轉unicode)
{
    return QString::fromLocal8Bit( strText ) ;
}

int main( int argc, char *argv[] )
{
    QApplication app( argc, argv ) ;

    PainterWidget *window = new PainterWidget;
    window->setWindowTitle( toUnicode( "圖片顯示小程式" ) );
   
    QLabel *labelText1 = new QLabel( toUnicode( "圖片路徑 : " ) ) ;
    QLabel *labelText2 = new QLabel( toUnicode( "圖片資訊 : " ) ) ; 
    QLabel *labelImage = new QLabel( "" ) ;

    QPushButton *btn = new QPushButton( toUnicode( "開啟檔案" ) ) ;
    
  
    QVBoxLayout *vBoxLayout = new QVBoxLayout ;
    window->setLayout( vBoxLayout ) ;

    vBoxLayout->addWidget( btn );
    vBoxLayout->addWidget( labelText1 ) ;
    vBoxLayout->addWidget( labelText2 ) ;
    vBoxLayout->addWidget( labelImage ) ;
    
    ButtonClicked bc ; // Signal與Slot的類別

    bc.setWidget( window ) ; // 用來修改window視窗大小與標題
    
    QDir dir( "" ) ; 
    bc.setDir( &dir ) ; 
      
    QFileInfoList list ;
    bc.setList( &list ) ;
    
    QFileInfoList::const_iterator iter ;
    bc.setIter( &iter ) ;
       
    QSize size( 0, 0 ) ; 
    bc.setSize( &size ) ; 
    
    // 使用於事件函式 
    window->setLabelText1( labelText1 ) ;
    window->setLabelText2( labelText2 ) ;
    window->setLabelImage( labelImage ) ;
    window->setDir( &dir ) ;
    window->setList( &list ) ;
    window->setIter( &iter ) ;
    window->setSize( &size ) ;

 
    // 將btn->clicked()的訊號送往bc.clicked()
    QObject::connect( btn, SIGNAL( clicked() ), &bc, SLOT( clicked() ) ) ;

    // 將bc.fileNameChanged()的訊號送往labelText1.setText()
    QObject::connect( &bc, SIGNAL( fileNameChanged( QString ) ),
                        labelText1, SLOT( setText( QString ) ) ) ;
                     
    // 將bc.fileInfoChanged()的訊號送往labelText2.setText()                    
    QObject::connect( &bc, SIGNAL( fileInfoChanged( QString ) ),
                        labelText2, SLOT( setText( QString ) ) ) ;

    // 將bc.pixmapChanged()的訊號送往labelImage.setPixmap()
    QObject::connect( &bc, SIGNAL( pixmapChanged( QPixmap ) ),
                        labelImage, SLOT( setPixmap( QPixmap ) ) ) ;

    window->resize( 200, 100 );
    window->show();

    return app.exec();

}


 

OpenPicEvent.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#ifndef OPEN_PIC_EVENT_H
#define OPEN_PIC_EVENT_H

#include <QMouseEvent>
#include <QKeyEvent> 

#include "ButtonClicked.h"
 
class PainterWidget : public QWidget {
    
public:
    PainterWidget() : m_labelText1( NULL ), 
                      m_labelText2( NULL ),
                      m_labelImage( NULL ),
                      m_dir( NULL ),
                      m_list( NULL ),
                      m_iter( NULL ),
                      m_pic( "" ),
                      m_size( NULL ) {}
    
    void setLabelText1( QLabel *label ) { m_labelText1 = label ; }
    void setLabelText2( QLabel *label ) { m_labelText2 = label ; }
    void setLabelImage( QLabel *label ) { m_labelImage = label ; }
    void setDir( QDir *dir ) { m_dir = dir ; }
    void setList( QFileInfoList *list ) { m_list = list ; }
    void setIter( QFileInfoList::const_iterator *iter ) { m_iter = iter ; }
    void setSize( QSize *size ) { m_size = size ; }
    
    void draw( QString &path, bool sizeChanged ) ; // 繪出主要畫面 
    
protected:
    void mousePressEvent( QMouseEvent *event ) ;
    void keyPressEvent( QKeyEvent *event ) ;
    void wheelEvent( QWheelEvent *event ) ;
    
private:
    QLabel *m_labelText1 ;
    QLabel *m_labelText2 ;
    QLabel *m_labelImage ;
    QDir *m_dir ;
    QFileInfoList *m_list ;
    QFileInfoList::const_iterator *m_iter ;
    QPixmap m_pic ;
    QSize *m_size ;
};


#endif


 

OpenPicEvent.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include "OpenPicEvent.h"


void PainterWidget::mousePressEvent( QMouseEvent *event ) 
// 按滑鼠左、中或右鍵可將視窗調整至最佳尺寸 
{
    QSize size = this->sizeHint() ; 
    this->resize( size.width()+10, size.height()+50 ) ;
}

void PainterWidget::wheelEvent( QWheelEvent *event ) 
// 以滑鼠滾輪穿梭瀏覽目錄下的圖片 
{
    bool imageChanged = false ;
    QString path = m_dir->path() + "/" ;
    
    if ( event->delta() EQU -120) { // 滾輪下推

        if ( (*m_iter) != m_list->begin() ) {
            -- (*m_iter) ;
            
            imageChanged = true ;
        } 
    }
    else if ( event->delta() EQU 120 ) {  

        if ( (*m_iter)+1 != m_list->end() ) { // 滾輪上推 
            ++ (*m_iter) ;
            
            imageChanged = true ;
        }
    }
    
    if ( imageChanged )
        draw( path, false ) ;
}

void PainterWidget::keyPressEvent( QKeyEvent *event ) 
// 以鍵盤的左右鍵控制目前視窗中讀取的圖片 
{
    bool imageChanged = false ;
    bool sizeChanged = false ;
    
    QString path = m_dir->path() + "/" ;
    
    if ( event->key() EQU Qt::Key_Right) {

        if ( (*m_iter) != m_list->begin() ) {
            -- (*m_iter) ;
            
            imageChanged = true ;
        } 
    }
    else if ( event->key() EQU Qt::Key_Left ) {

        if ( (*m_iter)+1 != m_list->end() ) {
            ++ (*m_iter) ;
            
            imageChanged = true ;
        }
    }
    else if ( event->key() EQU Qt::Key_Plus ) {
        // 每按加號一次,放大圖片到原本的1.1倍 
        
        QString path = m_dir->path() + "/" + (*m_iter)->fileName() ;
        
        // 更新圖片內容 
        m_pic.load( path ) ;
        
        m_size->setHeight( (int)( m_size->height()*1.1 ) );
        m_size->setWidth( (int)( m_size->width()*1.1 ) ) ;
        
        sizeChanged = true ;
    } 
    else if ( event->key() EQU Qt::Key_Minus ) {
        // 每按減號一次,縮小圖片到原本的0.9倍
        
        QString path = m_dir->path() + "/" + (*m_iter)->fileName() ;
         
        // 更新圖片內容 
        m_pic.load( path ) ;
        
        m_size->setHeight( (int)( m_size->height()*0.9 ) ) ;
        m_size->setWidth( (int)( m_size->width()*0.9 ) ) ;
        
        sizeChanged = true ;
    }
    
    if ( imageChanged || sizeChanged ) {
        
        draw( path, sizeChanged ) ;        
           
    }
}

void PainterWidget::draw( QString &path, bool sizeChanged )
{
    double ratio =  m_size->width() / (double) m_pic.size().width() ;
    int percent = (int)( ratio * 100 ) ;

    // 若非改變大小,則百分比固定為100% 
    percent = sizeChanged ? percent : 100 ;

    // 更新圖片路徑 
    path += (*m_iter)->fileName() ;
    m_labelText1->setText( "<center><h3><font color=blue>" + 
        path +
        "</font></h3></center>" ) ;

    // 更新圖片內容 
    m_pic.load( path ) ;

    QFileInfoList::const_iterator iter = (*m_iter) ;

    int count = m_list->count() ;
    int no = 0 ;

    // 找出目前開啟圖片的排序位置 
    while ( iter != m_list->end() ) {
        ++ iter ;
        ++ no ;
    }

    // 顯示圖片在目錄中的次序與目錄裡的圖片總數 
    QString order = " [ " +
        QString::number( no ) +
        " / " +
        QString::number( count ) +
        " ] " ;


    if ( !sizeChanged ) // 只要顯示原始圖片即可 
        m_labelImage->setPixmap( m_pic ) ;
    else { // 須重新設定大小 
        // 使用Qt::SmoothTransformation讓縮放後的圖片去鋸齒化,比較平滑一點 
        m_pic = m_pic.scaled( *m_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ;    
        m_labelImage->setPixmap( m_pic ) ;
    }

    // 更新圖片資訊 
    QFile srcFile( path ) ; 
    QString strFileInfo = order + 
        " ( " +
        QString::number( percent ) + 
        "% ) " +
        toUnicode( "圖片尺寸 :  長度:" ) + 
        QString::number( m_pic.height() ) + 
        toUnicode( "  ,  寬度:" ) +
        QString::number( m_pic.width() ) +
        toUnicode( "   檔案大小:" ) +
        QString::number( srcFile.size()/1024 ) + 
        " kb" ;

    m_labelText2->setText( "<center><h3><font color=green>" + 
        strFileInfo +
        "</font></h3></center>" ) ;

    // 紀錄改變後的大小 
    m_size->scale( m_pic.size().width(), m_pic.size().height(), Qt::IgnoreAspectRatio ) ;

    // 修正為建議的視窗大小(不靈敏,須等下一次才會生效)    
    this->resize( this->sizeHint().width(), this->sizeHint().height() ) ; 

} 


 

ButtonClicked.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#ifndef BUTTON_CLICKED_H
#define BUTTON_CLICKED_H
#define EQU ==

#include <QObject>
#include <QString>
#include <QFileDialog>
#include <QWidget>
#include <QDir>
#include <QFileInfo>
#include <QLabel>
#include <QSize>

QString toUnicode( char *strText ) ; // big5編碼轉為unicode

class ButtonClicked : public QObject {
    Q_OBJECT

public:
    // 成員初始列(member initialization list),效率高於傳統賦值 
    ButtonClicked() : m_fileName( "" ), 
                      m_widget( NULL ), 
                      m_dir( NULL ),
                      m_list( NULL ), 
                      m_iter( NULL ),
                      m_size( NULL )
                      { }
 
public slots:
    void clicked();
    void setWidget( QWidget *widget ) { m_widget = widget ; }
    void setDir( QDir *dir ) { m_dir = dir ; }
    void setList( QFileInfoList *list ) { m_list = list ; }
    void setIter( QFileInfoList::const_iterator *iter ) { m_iter = iter ; }
    void setSize( QSize *size ) { m_size = size ; }
    
signals:
    void fileNameChanged( QString fileName ) ; // 傳回新的檔案名稱
    void fileInfoChanged( QString fileName ) ; // 傳回新的檔案資訊  
    void pixmapChanged( QPixmap pic ) ; // 傳回新的圖片檔 
    
private:
    QString m_fileName ;
    QWidget *m_widget ; // 主視窗 
    QDir *m_dir ; // 目前檔案的目錄 
    QFileInfoList *m_list ; // 目錄下所有限定格式的檔案資料 
    QFileInfoList::const_iterator *m_iter ; // m_list的迭代器
    QSize *m_size ;    
};



#endif


 

ButtonClicked.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "ButtonClicked.h"

void ButtonClicked::clicked() {
    
    // 跳出開啟檔案視窗 
    // 目錄以m_fileName作為參數,作用是可以保存上一次的讀取目錄。 
    m_fileName = QFileDialog::getOpenFileName( 0, "Open Image",
                    m_fileName, "Image Files (*.png *.xpm *.jpg *.bmp) ") ;
     
    QFileInfo fileInfo( m_fileName ) ;
    
    m_dir->setPath( fileInfo.dir().path() ) ; // 取得不含檔案名稱的目錄字串 
           
    QPixmap pic( m_fileName ) ;
    QFile srcFile( m_fileName ) ; 
     
    // 隨圖片大小而改變視窗大小,但不知為何無法及時反應,需等下一次開檔才能重設大小
    // 使用update(), repaint(), hide()+show(), showMinimized()+showNormal()都沒有用...
    m_widget->resize( m_widget->sizeHint().width(), m_widget->sizeHint().height() ) ;
    
    
    QString strFileInfo( "" ) ;
    
    if ( m_fileName.isEmpty() )
        m_widget->setWindowTitle( toUnicode( "沒有開啟圖片" ) ); 
    else {
        strFileInfo = toUnicode( "圖片尺寸 :  長度:" ) + 
                      QString::number( pic.height() ) + 
                      toUnicode( "  ,  寬度:" ) +
                      QString::number( pic.width() ) +
                      toUnicode( "   檔案大小:" ) +
                      QString::number( srcFile.size()/1024 ) + 
                      " kb" ;
    }
    
    m_dir->setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks ) ;
    m_dir->setSorting( QDir::Name | QDir::Reversed ) ; 
     
    QStringList filters ;
    filters << "*.jpg" << "*.png" << "*.bmp" ; // 限定三種格式的圖片 
    m_dir->setNameFilters( filters ) ;
    
    (*m_list) = m_dir->entryInfoList() ;
      
    (*m_iter) = m_list->begin() ;
    
    int count = m_list->count() ; // 目錄下圖片總數 
    int no = 0 ; // 所選圖片在目錄下的排序位置 
    
    // 找出目前開啟圖片的排序位置 
    while ( (*m_iter) != m_list->end() ) {
        if ( fileInfo.fileName() EQU (*m_iter)->fileName() )
            break ;
            
        ++ (*m_iter);
        ++ no ;
    }
    no = count - no ;
    
    QString order = " [ " +
                    QString::number( no ) +
                    " / " +
                    QString::number( count ) +
                    " ] " ;
     
    
    // 傳回尺寸資訊  
    m_size->scale( pic.size().width(), pic.size().height(), Qt::IgnoreAspectRatio ) ;
                
    // Qt的label認得html標籤,可直接使用於字串當中。 
    emit fileNameChanged( "<center><h3><font color=blue>" + 
                          m_fileName +
                          "</font></h3></center>" ) ;
    emit fileInfoChanged( "<center><h3><font color=green>" + 
                          order + 
                          " ( 100% ) " + 
                          strFileInfo +
                          "</font></h3></center>" ) ; 
    emit pixmapChanged( pic ) ;
    
}


 

moc_ButtonClicked.cpp:(由編譯器自己生成的代碼)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/****************************************************************************
** Meta object code from reading C++ file 'ButtonClicked.h'
**
** Created: Mon Jun 14 10:24:20 2010
**      by: The Qt Meta Object Compiler version 59 (Qt 4.3.3)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "ButtonClicked.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'ButtonClicked.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 59
#error "This file was generated using the moc from 4.3.3. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

static const uint qt_meta_data_ButtonClicked[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       9,   10, // methods
       0,    0, // properties
       0,    0, // enums/sets

 // signals: signature, parameters, type, tag, flags
      24,   15,   14,   14, 0x05,
      49,   15,   14,   14, 0x05,
      78,   74,   14,   14, 0x05,

 // slots: signature, parameters, type, tag, flags
     101,   14,   14,   14, 0x0a,
     118,  111,   14,   14, 0x0a,
     142,  138,   14,   14, 0x0a,
     161,  156,   14,   14, 0x0a,
     190,  185,   14,   14, 0x0a,
     235,  230,   14,   14, 0x0a,

       0        // eod
};

static const char qt_meta_stringdata_ButtonClicked[] = {
    "ButtonClicked\0\0fileName\0"
    "fileNameChanged(QString)\0"
    "fileInfoChanged(QString)\0pic\0"
    "pixmapChanged(QPixmap)\0clicked()\0"
    "widget\0setWidget(QWidget*)\0dir\0"
    "setDir(QDir*)\0list\0setList(QFileInfoList*)\0"
    "iter\0setIter(QFileInfoList::const_iterator*)\0"
    "size\0setSize(QSize*)\0"
};

const QMetaObject ButtonClicked::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_ButtonClicked,
      qt_meta_data_ButtonClicked, 0 }
};

const QMetaObject *ButtonClicked::metaObject() const
{
    return &staticMetaObject;
}

void *ButtonClicked::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_ButtonClicked))
 return static_cast<void*>(const_cast< ButtonClicked*>(this));
    return QObject::qt_metacast(_clname);
}

int ButtonClicked::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        switch (_id) {
        case 0: fileNameChanged((*reinterpret_cast< QString(*)>(_a[1]))); break;
        case 1: fileInfoChanged((*reinterpret_cast< QString(*)>(_a[1]))); break;
        case 2: pixmapChanged((*reinterpret_cast< QPixmap(*)>(_a[1]))); break;
        case 3: clicked(); break;
        case 4: setWidget((*reinterpret_cast< QWidget*(*)>(_a[1]))); break;
        case 5: setDir((*reinterpret_cast< QDir*(*)>(_a[1]))); break;
        case 6: setList((*reinterpret_cast< QFileInfoList*(*)>(_a[1]))); break;
        case 7: setIter((*reinterpret_cast< QFileInfoList::const_iterator*(*)>(_a[1]))); break;
        case 8: setSize((*reinterpret_cast< QSize*(*)>(_a[1]))); break;
        }
        _id -= 9;
    }
    return _id;
}

// SIGNAL 0
void ButtonClicked::fileNameChanged(QString _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
void ButtonClicked::fileInfoChanged(QString _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

// SIGNAL 2
void ButtonClicked::pixmapChanged(QPixmap _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 2, _a);
}



完整程式碼+執行檔:
http://popodragon.myweb.hinet.net/Program/Qt_imageViewer_v_0_1.rar

No response to “Qt習作之簡易圖像瀏覽器 v0.1” ;

張貼留言