1. 程式人生 > >Qt Quick綜合例項之檔案檢視器

Qt Quick綜合例項之檔案檢視器

               

    如果你基於Qt SDK 5.3.1來建立一個Qt Quick App專案,專案模板為你準備的main.qml文件的根元素是ApplicationWindow或Window。這次我們就以ApplicationWindow為例,圍繞著它實現一個綜合例項:檔案檢視器。通過檔案檢視器的實現,我們來再次領略一下Qt Quick的犀利。

    本例項將會用到下列特性:

  • ApplicationWindow
  • MenuBar
  • ToolBar、ToolButton
  • Action
  • StatusBar
  • MediaPlayer
  • Image
  • XMLHttpRequest
  • ColorDialog
  • FileDialog
  • TextArea
  • 動態建立QML元件
  • 多介面切換

    我們之前的文章都是就某一個主題來展開的,這次要出個大招。先看點兒圖。

檔案檢視器效果

    圖1,初始狀態

                  圖1 初始狀態

    圖2 是瀏覽文字檔案的效果:

              圖2 瀏覽文字檔案

    圖3是瀏覽圖片:

           圖3 瀏覽圖片

    圖4是播放視訊:

              圖4 播放視訊

    圖5是播放音樂:

               圖5 播放音樂

    好啦,看程式碼前,先大概介紹下用到的Qt Quick元素。

    Image、動態建立元件、介面切換這些沒什麼好說的,我之前的文章中已經涉及到。只講新的了。

ApplicationWindow

    ApplicationWindow,類似Qt C++中的QMainWindow,請對照著理解。

    ApplicationWindow有選單欄(menuBar屬性)、工具欄(toolBar屬性)、狀態列(statusBar屬性)。咦,沒有centralWidget哈,別急,有的,就是ContentItem,就是那個你不與任何屬性繫結的Item。例如:

ApplicationWindow{    ...    Rectangle{        id: centralView;        anchors.fill: parent;        color: "red"
;    }}
    上面程式碼中的centralView就對應QMainWindow的centralWidget了。

    看起來沒有DockWidgets……兄弟,別要求太高了,你想要麼,真的想要麼,你真的確定你想要麼?好吧,自己實現就好了,QML裡很多元素的哈。

MenuBar

    MenuBar就是選單欄一長條區域了,它幹兩件事:

  1.     維護一個Menu列表(menus屬性)
  2.     繪製選單欄背景色(style屬性)

Menu

    看截圖中的“檔案”、“設定”、“幫助”三個選單,點一下彈出一個子選單,Menu就代表檔案這個選單和它下面的子選單。

    列表屬性items指向Menu的子選單項,它的型別是list<Object>,是預設屬性。

    title屬性是string型別,你看到的“檔案”、“設定”等字樣就是它指定的。

    有這兩個屬性,Menu就可以開始幹活了。看Qt幫助裡的示例程式碼片段:

Menu {    title: "Edit"    MenuItem {        text: "Cut"        shortcut: "Ctrl+X"        onTriggered: ...    }    MenuItem {        text: "Copy"        shortcut: "Ctrl+C"        onTriggered: ...    }    MenuItem {        text: "Paste"        shortcut: "Ctrl+V"        onTriggered: ...    }    MenuSeparator { }    Menu {        title: "More Stuff"        MenuItem {            text: "Do Nothing"        }    }}

MenuItem

    Menu的孩子啊,最受待見的就是MenuItem了。

    MenuItem代表一個具體的選單項,點一下就幹一件事情的角色。你可以實現onTriggered訊號處理響應使用者對它的選擇。

    MenuItem的text屬性指定選單文字,iconSource屬性指定選單圖示。

    Action屬性很強大哦,MenuItem的text、iconSource、trigger訊號等實現的效果,都可以通過Action來實現,這兩種方式是等同的。咱們來看Action。

Action    

    Action類有text、iconSource等屬性,還有toggled、triggered兩個訊號。這些在MenuItem裡有對應的屬性,不必多說了。

    使用Action的一大好處是,你可以給它指定一個id(比如叫open),然後在使用ToolButton構造ToolBar時指定ToolBatton的action屬性為之前定義的id為open的那個action,這樣工具欄的按鈕就和選單關聯起來了。

    好啦,總結一下,實現選單欄的典型程式碼結構是醬紫的:

MenuBar {    Menu{        title:"檔案";        MenuItem{            text:"開啟";            iconSource: "res/ic_open.png";            onTriggered:{                ...            }        }        MenuItem{            action: Action {                text:"儲存";                iconSource: "res/ic_save.png";                onTriggered:{                    ...                }            }        }            }}
    如你所見,我使用了兩種構造MenuItem的方式,一種用到了Action,一種沒有。

ToolBar

    ToolBar就是工具欄對應的類,它只有一個屬性,contentItem,型別為Item。一般我們可以將一個Row或者RowLayout物件賦值給contentItem,而Row或RowLayout則管理一組ToolButton來作為工具欄上的按鈕。

ToolButton

    ToolButton是Button的派生類,專為ToolBar而生,一般情況下定義ToolButton物件時只需要指定其iconSource屬性即可。例如:

ToolButton {    iconSource: "res/ic_open.png";}
    還有一種方式是將一個已定義好的Action物件關聯到ToolButton物件上。例如:
ToolButton{    action: openAction;}
    這樣的話,ToolButton會使用Action定義的iconSource或iconName作為其圖示。

    好啦,構造工具欄的典型程式碼結構如下:

ToolBar{    RowLayout {        ToolButton{            action: textAction;        }        ToolButton{            action: imageAction;        }        ToolButton{            iconSource: "res/exit.png";            onClicked:{                ...            }        }    }}

狀態列

    ApplicationWindow的屬性statusBar代表狀態列,其型別為Item,你可以將任意的Item賦值給它,可以隨心所欲構建你妖嬈多姿的狀態列。比如這樣:

ApplicationWindow{    statusBar: Text {        text: "status bar";        color: "blue";    }}

MediaPlayer

    Qt Quick裡,播放視訊、音訊檔案都直接使用MediaPlayer類,它是萬能的,不要說你萬萬沒想到哈。

    對於音樂,最簡單,設定source屬性,呼叫play()方法,就可以了。例如:

ApplicationWindow{    ...    MediaPlayer{        id: player;        source: "xxx.mp3";    }    Component.onCompleted:{        player.play();    }}
    對於視訊,MediaPlayer還得尋求場外幫助,求助物件就是它的好基友:VideoOutput !簡單的示例程式碼:
ApplicationWindow{    ...    MediaPlayer {        id: player;        source: "test.ts";    }        VideoOutput {        anchors.fill: parent;        source: player;    }    Component.onCompleted: player.play();    }
    哦,黃小琥唱過的歌:沒那麼簡單。是的,實際做專案比較複雜,你還要關注各種播放狀態,要seek,要pause,要處理錯誤,簡直煩死人了。請移步到Qt幫助裡瞭解詳情。

XMLHttpRequest

    在Qt Quick裡,要訪問網路腫麼辦泥?答案是:XMLHttpRequest !

    不要被它傲嬌的外表迷惑,以為它只接受XML文件,其實,它什麼都能處理,txt、html、json、binary……它溫柔堅定強悍無比。

    本例項用它來載入本地的文字檔案,耶,這樣都可以哈。誰叫Qt Quick不提供直接訪問本地檔案的類庫呢!我可不想跑到C++裡用QFile、QTextStream這對黃金搭檔。

    XMLHttpRequest的文件,Qt幫助里語焉不詳,只有一個示例,請看這裡:

TextArea

    這有什麼好講的,看Qt幫助吧。只提一點:

    TextArea自動處理翻頁按鍵、上下鍵、滑鼠中鍵,正確的滾動文字;而TextEdit,抱歉,我是來打醬油的。

標準對話方塊

    Qt Quick提供了很多標準對話方塊,比如FileDialog用來選擇檔案或資料夾,ColorDialog用來選擇顏色,MessageDialog用來顯示一些提示資訊。這些我們例項中用到了,參考Qt幫助吧。

    我只說點兒經驗。

    我一開始使用qmlscene來載入main.qml,出來的介面比較正常,工具欄的圖示、選單項前也有圖示。可是當我建立了一個Qt Quick App,靈異事件發生了:

    選單項前面沒有圖示了……

    工具欄圖示好大好大……

    顏色對話方塊、訊息框,點選右上角的關閉按鈕,收不到rejected訊號啊……

    查了老半天,猛回頭,警世鐘響起,我了悟了。原來是醬紫的:

    Qt Quick提供了這些標準對話方塊的預設實現,如果應用執行的平臺沒有可用的,就用這些預設實現。那在Windows上,如果你的main()函式,使用QGuiApplication而非QApplication,就會用到Qt Quick實現的版本,一切都變了模樣

資源管理

    Qt SDK 5.3之後,Qt Creator建立的Qt Quick App專案,就為我們建立了一個qrc檔案,把main.qml扔裡啦。我在例項中也把很多圖示扔裡了。

    使用qrc來管理資源,這是跨平臺的,推薦使用。

    我還自己畫了些圖示,真費勁,弄得也不好看。不過應用的大眼睛圖示,看起來還像那麼一回事兒。

原始碼

    前戲太長,也許你已經失去了興趣。好吧,G點來咧。

    QML程式碼

    所有QML程式碼都在這裡了,竟然有450行啊親。

import QtQuick 2.2import QtQuick.Window 2.1import QtQuick.Controls 1.2import QtQuick.Controls.Styles 1.2import QtQuick.Layouts 1.1import QtQuick.Dialogs 1.1import QtMultimedia 5.0ApplicationWindow {    visible: true    width: 480    height: 360;    color: "black";    title: "檔案檢視器";    id: root;    property var aboutDlg: null;    property var colorDlg: null;    property color textColor: "green";    property color textBackgroundColor: "black";    menuBar: MenuBar{        Menu {            title: "檔案";            MenuItem{                iconSource: "res/txtFile.png";                action: Action{                    id: textAction;                    iconSource: "res/txtFile.png";                    text: "文字檔案";                    onTriggered: {                        fileDialog.selectedNameFilter = fileDialog.nameFilters[0];                        fileDialog.open();                    }                    tooltip: "開啟txt等文字檔案";                }            }            MenuItem{                action: Action {                    id: imageAction;                    text: "圖片";                    iconSource: "res/imageFile.png";                    onTriggered: {                        fileDialog.selectedNameFilter = fileDialog.nameFilters[1];                        fileDialog.open();                    }                    tooltip: "開啟jpg等格式的圖片";                }            }            MenuItem{                action: Action {                    id: videoAction;                    iconSource: "res/videoFile.png";                    text: "視訊";                    onTriggered: {                        fileDialog.selectedNameFilter = fileDialog.nameFilters[2];                        fileDialog.open();                    }                    tooltip: "開啟TS、MKV、MP4等格式的檔案";                }            }            MenuItem{                action: Action {                    id: audioAction;                    iconSource: "res/audioFile.png";                    text: "音樂";                    onTriggered: {                        fileDialog.selectedNameFilter = fileDialog.nameFilters[3];                        fileDialog.open();                    }                    tooltip: "開啟mp3、wma等格式的檔案";                }            }            MenuItem{                text: "退出";                onTriggered: Qt.quit();            }        }        Menu {            title: "設定";            MenuItem {                action: Action {                    id: textColorAction;                    iconSource: "res/ic_textcolor.png";                    text: "文字顏色";                    onTriggered: root.selectColor(root.onTextColorSelected);                }            }            MenuItem {                action: Action{                    id: backgroundColorAction;                    iconSource: "res/ic_bkgndcolor.png";                    text: "文字背景色";                    onTriggered: root.selectColor(root.onTextBackgroundColorSelected);                }            }            MenuItem {                action: Action{                    id: fontSizeAddAction;                    iconSource: "res/ic_fontsize2.png";                    text: "增大字型";                    onTriggered: textView.font.pointSize += 1;                }            }            MenuItem {                action: Action{                    id: fontSizeMinusAction;                    iconSource: "res/ic_fontsize1.png";                    text: "減小字型";                    onTriggered: textView.font.pointSize -= 1;                }            }        }        Menu {            title: "幫助";            MenuItem{                text: "關於";                onTriggered: root.showAbout();            }            MenuItem{                text: "訪問作者部落格";                onTriggered: Qt.openUrlExternally("http://blog.csdn.net/foruok");            }        }    }    toolBar: ToolBar{        RowLayout {            ToolButton{                action: textAction;            }            ToolButton{                action: imageAction;            }            ToolButton{                action: videoAction;            }            ToolButton{                action: audioAction;            }            ToolButton{                action: textColorAction;            }            ToolButton {                action: backgroundColorAction;            }            ToolButton {                action: fontSizeAddAction;            }            ToolButton {                action: fontSizeMinusAction;            }        }    }    statusBar: Rectangle {        color: "lightgray";        implicitHeight: 30;        width: parent.width;        property alias text: status.text;        Text {            id: status;            anchors.fill: parent;            anchors.margins: 4;            font.pointSize: 12;        }    }    Item {        id: centralView;        anchors.fill: parent;        visible: true;        property var current: null;        BusyIndicator {            id: busy;            anchors.centerIn: parent;            running: false;            z: 3;        }        Image {            id: imageViewer;            anchors.fill: parent;            visible: false;            asynchronous: true;            fillMode: Image.PreserveAspectFit;            onStatusChanged: {                if (status === Image.Loading) {                    centralView.busy.running = true;                }                else if(status === Image.Ready){                    centralView.busy.running = false;                }                else if(status === Image.Error){                    centralView.busy.running = false;                    centralView.statusBar.text = "圖片無法顯示";                }            }        }        TextArea {            id: textView;            anchors.fill: parent;            readOnly: true;            visible: false;            wrapMode: TextEdit.WordWrap;            font.pointSize: 12;            style: TextAreaStyle{                backgroundColor: root.textBackgroundColor;                textColor: root.textColor;                selectionColor: "steelblue";                selectedTextColor: "#a00000";            }            property var xmlhttp: null;            function onReadyStateChanged(){                if(xmlhttp.readyState == 4){                    text = xmlhttp.responseText;                    xmlhttp.abort();                }            }            function loadText(fileUrl){                if(xmlhttp == null){                    xmlhttp = new XMLHttpRequest();                    xmlhttp.onreadystatechange = onReadyStateChanged;                }                if(xmlhttp.readyState == 0){                    xmlhttp.open("GET", fileUrl);                    xmlhttp.send(null);                }            }        }        VideoOutput {            id: videoOutput;            anchors.fill: parent;            visible: false;            source: player;            onVisibleChanged: {                playerState.visible = visible;            }            MouseArea {                anchors.fill: parent;                onClicked: {                    switch(player.playbackState){                    case MediaPlayer.PausedState:                    case MediaPlayer.StoppedState:                        player.play();                        break;                    case MediaPlayer.PlayingState:                        player.pause();                        break;                    }                }            }        }        Rectangle {            id: playerState;            color: "gray";            radius: 16;            opacity: 0.8;            visible: false;            z: 2;            implicitHeight: 80;            implicitWidth: 200;            anchors.horizontalCenter: parent.horizontalCenter;            anchors.bottom: parent.bottom;            anchors.bottomMargin: 16;            Column {                anchors.fill: parent;                anchors.leftMargin: 12;                anchors.rightMargin: 12;                anchors.topMargin: 6;                anchors.bottomMargin: 6;                spacing: 4;                Text {                    id: state;                    font.pointSize: 14;                    color: "blue";                }                Text {                    id: progress;                    font.pointSize: 12;                    color: "white";                }            }        }        MediaPlayer {            id: player;            property var utilDate: new Date();            function msecs2String(msecs){                utilDate.setTime(msecs);                return Qt.formatTime(utilDate, "mm:ss");            }            property var sDuration;            onPositionChanged: {                progress.text = msecs2String(position) + sDuration;            }            onDurationChanged: {                sDuration = " / " + msecs2String(duration);            }            onPlaybackStateChanged: {                switch(playbackState){                case MediaPlayer.PlayingState:                    state.text = "播放中";                    break;                case MediaPlayer.PausedState:                    state.text = "已暫停";                    break;                case MediaPlayer.StoppedState:                    state.text = "停止";                    break;                }            }            onStatusChanged: {                switch(status){                case MediaPlayer.Loading:                case MediaPlayer.Buffering:                    busy.running = true;                    break;                case MediaPlayer.InvalidMedia:                    root.statusBar.text = "無法播放";                case MediaPlayer.Buffered:                case MediaPlayer.Loaded:                    busy.running = false;                    break;                }            }        }    }    function processFile(fileUrl, ext){        var i = 0;        for(; i < fileDialog.nameFilters.length; i++){            if(fileDialog.nameFilters[i].search(ext) != -1) break;        }        switch(i){        case 0:            //text file            if(centralView.current != textView){                if(centralView.current != null){                    centralView.current.visible = false;                }                textView.visible = true;                centralView.current = textView;            }            textView.loadText(fileUrl);            break;        case 1:            if(centralView.current != imageViewer){                if(centralView.current != null){                    centralView.current.visible = false;                }                imageViewer.visible = true;                centralView.current = imageViewer;            }            imageViewer.source = fileUrl;            break;        case 2:        case 3:            if(centralView.current != videoOutput){                if(centralView.current != null){                    centralView.current.visible = false;                }                videoOutput.visible = true;                centralView.current = videoOutput;            }            player.source = fileUrl;            player.play();            break;        default:            statusBar.text = "抱歉,處理不了";            break;        }    }    function showAbout(){        if(aboutDlg == null){            aboutDlg = Qt.createQmlObject(                        'import QtQuick 2.2;import QtQuick.Dialogs 1.1;MessageDialog{icon: StandardIcon.Information;title: "關於";\ntext: "僅僅是個示例撒";\nstandardButtons:StandardButton.Ok;}'                        , root, "aboutDlg");            aboutDlg.accepted.connect(onAboutDlgClosed);            aboutDlg.rejected.connect(onAboutDlgClosed);            aboutDlg.visible = true;        }    }    function selectColor(func){        if(colorDlg == null){            colorDlg = Qt.createQmlObject(                        'import QtQuick 2.2;import QtQuick.Dialogs 1.1;ColorDialog{}',                        root, "colorDlg");            colorDlg.accepted.connect(func);            colorDlg.accepted.connect(onColorDlgClosed);            colorDlg.rejected.connect(onColorDlgClosed);            colorDlg.visible = true;        }    }    function onAboutDlgClosed(){        aboutDlg.destroy();        aboutDlg = null;    }    function onColorDlgClosed(){        colorDlg.destroy();        colorDlg = null;    }    function onTextColorSelected(){        root.textColor = colorDlg.color;    }    function onTextBackgroundColorSelected(){        root.textBackgroundColor = colorDlg.color;    }    FileDialog {        id: fileDialog;        title: qsTr("Please choose an image file");        nameFilters: [            "Text Files (*.txt *.ini *.log *.c *.h *.java *.cpp *.html *.xml)",            "Image Files (*.jpg *.png *.gif *.bmp *.ico)",            "Video Files (*.ts *.mp4 *.avi *.flv *.mkv *.3gp)",            "Audio Files (*.mp3 *.ogg *.wav *.wma *.ape *.ra)",            "*.*"        ];        onAccepted: {            var filepath = new String(fileUrl);            //remove file:///            if(Qt.platform.os == "windows"){                root.statusBar.text = filepath.slice(8);            }else{                root.statusBar.text = filepath.slice(7);            }            var dot = filepath.lastIndexOf(".");            var sep = filepath.lastIndexOf("/");            if(dot > sep){                var ext = filepath.substring(dot);                root.processFile(fileUrl, ext.toLowerCase());            }else{                root.statusBar.text = "Not Supported!";            }        }    }}

    C++程式碼

    其實,我只對模板生成的C++程式碼改動了三行,一行include,一行QApplication,一行設定應用圖示。main.cpp如下:

#include <QApplication>#include <QQmlApplicationEngine>#include <QIcon>int main(int argc, char *argv[]){    QApplication app(argc, argv);    app.setWindowIcon(QIcon(":/res/eye.png"));    QQmlApplicationEngine engine;    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));    return app.exec();}

PRO檔案

    有人喊,兄弟,這也要貼!你湊字數呢……你管,就放這裡了:

TEMPLATE = appQT += qml quick network multimedia widgetsSOURCES += main.cppRESOURCES += qml.qrc# Additional import path used to resolve QML modules in Qt Creator's code modelQML_IMPORT_PATH =# Default rules for deployment.include(deployment.pri)HEADERS +=

    好啦,彪悍的人生不需要解釋,都扒光了,你自己仔細看吧。

    回顧一下我的Qt Quick系列文章: