Ubuntu18.04中配置QT5.11開發環境


准備工作

參考 https://wiki.qt.io/Install_Qt_5_on_Ubuntu .

# 安裝g++
sudo apt install build-essential # sudo apt install libfontconfig1 # 安裝openGL支持 sudo apt install mesa-common-dev libglu1-mesa-dev

ustc鏡像直接下載安裝包, 地址是 http://mirrors.ustc.edu.cn/qtproject/official_releases/qt/5.11/5.11.1/

執行chmod u+x使其可執行

安裝

直接運行 qt-opensource-linux-x64-5.11.1.run, 會出現安裝界面, 這一步可以配置網絡代理, 往下進行需要填寫自己在QT注冊的賬戶和口令, 會進行在線驗證.

設置安裝目錄時, 可以將路徑配置到/opt下, 例如 /opt/qt/Qt5.11.1, 會在最后一步時彈出提示輸入su口令.

組件選擇: 普通使用一個都不用選。全選的話整個安裝下來占地5.4GB

在菜單中找到Qt Creator, 啟動非常快

HiDPI支持

默認安裝時在3K以上高分屏上顯示會有問題,部分界面尺寸及字體非常小,需要配置一下環境變量,修改 .profile,增加

export QT_AUTO_SCREEN_SCALE_FACTOR=1

 在Ubuntu20.04, Qt5.14下測試對系統桌面設置為125%的縮放顯示正常。也可以使用 QT_ENABLE_HIGHDPI_SCALING=1

網上搜到的 export QT_DEVICE_PIXEL_RATIO 在運行時會提示warning。

 

運行一個例子

在網上找了一個例子, 項目名叫First, 但是編譯中出現了錯誤

$ gcc   first.o all   -o first
gcc: error: all: No such file or directory

這個是因為文件名稱first引起了歧義. 將項目名改為MyFirst就正常編譯通過了.

原因說明 https://www.qtcentre.org/threads/32635-compilation-errors-quot-gcc-all-No-such-file-or-directory-quot 

The Makefile contains a generated (fake) target called "first" and it also contains a set of explicit and implied rules related to the source file "first.cpp". The make command builds your TARGET (which is named after the directory if not specified) and then decides that because "first.o" changed it must rebuild the "first" target: at this point it gets confused.

$ make
/usr/bin/moc -DQT_NO_DEBUG -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSql -I/usr/include/qt4 -I. -I. first.cpp -o first.moc
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSql -I/usr/include/qt4 -I. -I. -o first.o first.cpp
g++ -Wl,-O1 -Wl,-rpath,/usr/lib/qt4 -o simple_example first.o    -L/usr/lib/qt4 -lQtSql -L/usr/lib/mysql -L/usr/lib/qt4 -lQtGui -L/usr/X11R6/lib -lQtCore -lgthread-2.0 -lrt -lglib-2.0 -lpthread
gcc   first.o all   -o first   // <<<<< confused, decides to rebuild "first" target using a file called "all"
gcc: all: No such file or directory
make: *** [first] Error 1

You get similar confusion (that make recovers from) if "TARGET = first" even with no files called "first.cpp".

$ make
/usr/bin/moc -DQT_NO_DEBUG -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSql -I/usr/include/qt4 -I. -I. main.cpp -o main.moc
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSql -I/usr/include/qt4 -I. -I. -o main.o main.cpp
make: Circular all <- first dependency dropped.   // <<<< confused but recognises the situation
g++ -Wl,-O1 -Wl,-rpath,/usr/lib/qt4 -o first main.o    -L/usr/lib/qt4 -lQtSql -L/usr/lib/mysql -L/usr/lib/qt4 -lQtGui -L/usr/X11R6/lib -lQtCore -lgthread-2.0 -lrt -lglib-2.0 -lpthread

Adding

    .PHONY = first

to Makefile fixes this problem but probably generates another if the TARGET = first. The change would be overwritten by qmake anyway. Easier to just avoid it.

Qt5常用快捷鍵

F2  跳轉到變量的聲明, 切換函數的聲明和實現
F4  在頭文件和C文件之間切換
Ctrl + B 構建
Ctrl + R 運行
Alt + Enter 在H文件的成員變量上, 會右鍵彈出創建setter, getter的選項, 非常省事

Qt5教程

bogotobogo.com的這個教程不錯, 值得推薦 http://www.bogotobogo.com/Qt/Qt_tutorial_list_New.php

一個帶菜單的圖片瀏覽器例子

來源是

http://www.bogotobogo.com/Qt/Qt5_QMainWindow_QAction_ImageViewer.php

http://www.bogotobogo.com/Qt/Qt5_QMainWindow_QAction_ImageViewer_B.php

其中的主要部分代碼為

MenuWindow.pro

#-------------------------------------------------
#
# Project created by QtCreator 2018-08-27T14:02:44
#
#-------------------------------------------------

QT       += core gui
QT       += printsupport

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = MenuWindow
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \
        main.cpp \
        mainwindow.cpp \
    mydialog.cpp

HEADERS += \
        mainwindow.h \
    mydialog.h

FORMS += \
        mainwindow.ui \
    mydialog.ui

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <QScrollArea>
#include <QMenu>
#include <QAction>
#include "mydialog.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void open();
    void print();
    void zoomIn();
    void zoomOut();
    void normalSize();
    void fitToWindow();
    void about();
    void newWindow();

private:
    Ui::MainWindow *ui;
    MyDialog *myDialog;
    QLabel *imageLabel;
    QScrollArea *scrollArea;
    QMenu *fileMenu;
    QMenu *viewMenu;
    QMenu *helpMenu;
    QAction *openAct;
    QAction *printAct;
    QAction *newWindowAct;
    QAction *exitAct;
    QAction *zoomInAct;
    QAction *zoomOutAct;
    QAction *normalSizeAct;
    QAction *fitToWindowAct;
    QAction *aboutAct;
    QAction *aboutQtAct;

    double scaleFactor;

    void updateActions();
    void scaleImage(double factor);
    void adjustScrollBar(QScrollBar *scrollBar, double factor);
};

#endif // MAINWINDOW_H

mainwindow.cpp

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

#include <QtWidgets>
#include <QFileDialog>
#include <QMessageBox>

#ifndef QT_NO_PRINTER
#include <QPrintDialog>
#endif

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);

    openAct = new QAction(tr("&Open..."), this);
    openAct->setShortcut(tr("Ctrl+O"));

    printAct = new QAction(tr("&Print..."), this);
    printAct->setShortcut(tr("Ctrl+P"));
    printAct->setEnabled(false);

    newWindowAct = new QAction(tr("&New Window"), this);
    newWindowAct->setShortcut(tr("Ctrl+N"));

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));

    zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
    zoomInAct->setShortcut(tr("Ctrl+="));   //(Ctrl)(+)
    zoomInAct->setEnabled(false);

    zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
    zoomOutAct->setShortcut(tr("Ctrl+-"));  //(Ctrl)(-)
    zoomOutAct->setEnabled(false);

    normalSizeAct = new QAction(tr("&Normal Size"), this);
    normalSizeAct->setShortcut(tr("Ctrl+S"));
    normalSizeAct->setEnabled(false);

    fitToWindowAct = new QAction(tr("&Fit to Window"), this);
    fitToWindowAct->setEnabled(false);
    fitToWindowAct->setCheckable(true);
    fitToWindowAct->setShortcut(tr("Ctrl+F"));

    aboutAct = new QAction(tr("&About"), this);

    aboutQtAct = new QAction(tr("About &Qt"), this);

    fileMenu = new QMenu(tr("&File"), this);
    fileMenu->addAction(openAct);
    fileMenu->addAction(printAct);
    fileMenu->addAction(newWindowAct);
    fileMenu->addSeparator();
    fileMenu->addAction(exitAct);

    viewMenu = new QMenu(tr("&View"), this);
    viewMenu->addAction(zoomInAct);
    viewMenu->addAction(zoomOutAct);
    viewMenu->addAction(normalSizeAct);
    viewMenu->addSeparator();
    viewMenu->addAction(fitToWindowAct);

    helpMenu = new QMenu(tr("&Help"), this);
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);

    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(viewMenu);
    menuBar()->addMenu(helpMenu);

    imageLabel = new QLabel(this);
    imageLabel->setBackgroundRole(QPalette::Base);
    imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    imageLabel->setScaledContents(true);

    scrollArea = new QScrollArea(this);
    scrollArea->setBackgroundRole(QPalette::Dark);
    scrollArea->setWidget(imageLabel);

    setCentralWidget(scrollArea);
    setWindowTitle(tr("Image Viewer"));
    resize(500, 400);

    connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
    connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
    connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
    connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
    connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
    connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
    connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
    connect(newWindowAct, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
}

MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::open() {
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath());
    if (!fileName.isEmpty()) {
        QImage image(fileName);
        if (image.isNull()) {
            QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
            return;
        }
        imageLabel->setPixmap(QPixmap::fromImage(image));
        scaleFactor = 1.0;

        printAct->setEnabled(true);
        fitToWindowAct->setEnabled(true);
        updateActions();

        if (!fitToWindowAct->isChecked())
            imageLabel->adjustSize();
    }
}

void MainWindow::print() {

}

void MainWindow::newWindow() {
    myDialog = new MyDialog(this);
    myDialog->resize(300, 200);
    /*myDialog->setModal(true);
    myDialog->exec();*/
    myDialog->show();
}

void MainWindow::zoomIn() {
    scaleImage(1.25);
}

void MainWindow::zoomOut() {
    scaleImage(0.8);
}

void MainWindow::normalSize() {
    imageLabel->adjustSize();
    scaleFactor = 1.0;
}

void MainWindow::fitToWindow() {
    bool fitToWindow = fitToWindowAct->isChecked();
    scrollArea->setWidgetResizable(fitToWindow);
    if (!fitToWindow) {
        normalSize();
    }
    updateActions();
}

void MainWindow::about() {
    QMessageBox::about(this, tr("About Image Viewer"), tr("<b>Image Viewer</b> example."));
}

void MainWindow::updateActions() {
    zoomInAct->setEnabled(!fitToWindowAct->isChecked());
    zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
    normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
}

void MainWindow::scaleImage(double factor) {
    Q_ASSERT(imageLabel->pixmap());
    scaleFactor *= factor;
    imageLabel->resize(scaleFactor * imageLabel->pixmap()->size());

    adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
    adjustScrollBar(scrollArea->verticalScrollBar(), factor);

    zoomInAct->setEnabled(scaleFactor < 3.0);
    zoomOutAct->setEnabled(scaleFactor > 0.333);
}

void MainWindow::adjustScrollBar(QScrollBar *scrollBar, double factor)
{
    int value = scrollBar->value();
    int step = scrollBar->pageStep();
    value = int(factor * value + ((factor - 1) * step/2));
    scrollBar->setValue(value);
}

QScrollBar組件

結合Qt5中對於QScrollBar的說明, 對void MainWindow::scaleImage(double factor) 和 void MainWindow::adjustScrollBar(QScrollBar *scrollBar, double factor) 這兩個方法進行分析.

 

a. 這是滾動條的滑塊, 提供了快速的定位, 但是不能提供精確的定位
b. 箭頭按鈕用於精確地瀏覽定位, 對於文本編輯器, 典型的步長是一行, 而對於圖片則是20像素.
c. 翻頁控制部分, 點擊的效果等價於一次翻頁, 正常情況下, 一個翻頁對於scrollbar的value的變化等於滑塊的長度.
每個滾動條有一個value字段, 用於標示此滑塊距離滾動條起點的距離. 這個值一定是介於 minimum() 和 maximum() 之間. 在 minimum value和 maximum value時, 滑塊的位置分別如上圖所示. 所以要注意, 這個滾動條的長度並非等於maximum().

由此可知, 對於前面例子中圖片放大factor倍時, 對於scrollbar.value:
1. 如果value不變, 那么當圖片放大時, 因為maximum value增大, 故滑塊一邊縮小, 一邊位置會往起始方向收縮
2. 如果value與factor進行同等比例的放大, 那么滑塊一邊縮小, 一邊起始邊界的位置保持不變
3. 如果value與factor進行同等比例放大的同時, 增加滑塊長度(pageStep)的收縮長度, 那么滑塊一邊縮小, 一邊中心位置保持不變. 所以上面adjustScrollBar()方法的處理為

value = int(factor * value + ((factor - 1) * step/2));

Qt5的Layout

常用的Layout主要有以下幾種

  • QHBoxLayout 將組件按水平進行布局, 寬度平均分割, 高度占滿外框
  • QVBoxLayout 將組件按垂直進行布局, 高度平均分割, 寬度占滿外框
  • QGridLayout 將組件按表格進行布局, 類似於HTML中的table tr td, 可以水平跨列, 也可以垂直跨行
  • QFormLayout 用於有多個成對的標簽+輸入的界面, 可以設置不同風格以及在寬帶不足時自動換行
  • QGraphicsAnchorLayout 用於在組件上添加錨點, 並以錨點的相對位置來控制布局
  • QStackedLayout 對於內部的組件, 每次只顯示一個(可以用於制作標簽頁)

而在主窗口中, 常用的底層布局並非Layout, 而是QSplitter. 這個自帶了對內部widget的自動拉伸, 以及分隔欄的鼠標拖動調整,非常方便. 可以參考Redis Desktop Manager的布局設計(是qml格式的, 但是可以看出來布局的設計) https://github.com/uglide/RedisDesktopManager/blob/0.9/src/qml/app.qml

import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.2
import QtQml.Models 2.2
import QtQuick.Window 2.2
import Qt.labs.settings 1.0
import "."
import "./common"
import "./common/platformutils.js" as PlatformUtils
import "./value-editor"
import "./connections-tree"
import "./console"
import "./server-info"
import "./bulk-operations"

ApplicationWindow {
    id: approot
    visible: true
    objectName: "rdm_qml_root"
    title: "Redis Desktop Manager " + Qt.application.version
    width: 1100
    height: 800

    property double wRatio : (width * 1.0) / (Screen.width * 1.0)
    property double hRatio : (height * 1.0) / (Screen.height * 1.0)

    property var currentValueFormatter

    Component.onCompleted: {
        if (hRatio > 1 || wRatio > 1) {
            console.log("Ratio > 1.0. Resize main window.")
            width = Screen.width * 0.9
            height = Screen.height * 0.8
        }

        if (PlatformUtils.isOSXRetina(Screen)) {
            bottomTabView.implicitHeight = 100
        }
    }

    Settings {
        category: "windows_settings"
        property alias x: approot.x
        property alias y: approot.y
        property alias width: approot.width
        property alias height: approot.height
    }

    SystemPalette {
        id: sysPalette
    }

    FontLoader {
        id: monospacedFont
        Component.onCompleted: {
            source = "qrc:/fonts/Inconsolata-Regular.ttf"
        }
    }

    QuickStartDialog {
        id: quickStartDialog
        objectName: "rdm_qml_quick_start_dialog"

        width: PlatformUtils.isOSX() ? 600 : approot.width * 0.8
    }

    GlobalSettings {
        id: settingsDialog
    }

    ConnectionSettignsDialog {
        id: connectionSettingsDialog

        objectName: "rdm_connection_settings_dialog"

        onTestConnection: {                       
            if (connectionsManager.testConnectionSettings(settings)) {
                hideLoader()
                showMsg(qsTr("Successful connection to redis-server"))
            } else {
                hideLoader()
                showError(qsTr("Can't connect to redis-server"))
            }            
        }

        onSaveConnection: connectionsManager.updateConnection(settings)
    }

    MessageDialog {
        id: notification
        objectName: "rdm_qml_error_dialog"
        visible: false
        modality: Qt.WindowModal
        icon: StandardIcon.Warning
        standardButtons: StandardButton.Ok

        function showError(msg) {
            icon = StandardIcon.Warning
            text = msg
            open()
        }

        function showMsg(msg) {
            icon = StandardIcon.Information
            text = msg
            open()
        }
    }

    BulkOperationsDialog {
        id: bulkOperationDialog
    }

    Connections {
        target: bulkOperations

        onOpenDialog: {
            bulkOperationDialog.operationName = operationName
            bulkOperationDialog.open()
        }
    }

    Connections {
        target: connectionsManager

        onEditConnection: {
            connectionSettingsDialog.settings = config
            connectionSettingsDialog.open()
        }

        onError: {            
            notification.showError(err)
        }

        Component.onCompleted: {
            if (connectionsManager.size() == 0)
                quickStartDialog.open()
        }
    }

    toolBar: AppToolBar {}

    BetterSplitView {
        anchors.fill: parent
        orientation: Qt.Horizontal        

        BetterTreeView {
            id: connectionsTree
            Layout.fillHeight: true
            Layout.minimumWidth: 350
            Layout.minimumHeight: 500
        }

        BetterSplitView {
            orientation: Qt.Vertical

            BetterTabView {
                id: tabs
                objectName: "rdm_qml_tabs"
                currentIndex: 0

                Layout.fillHeight: true
                Layout.fillWidth: true
                Layout.minimumWidth: 650
                Layout.minimumHeight: 30

                onCurrentIndexChanged: {

                    if (tabs.getTab(currentIndex).tabType) {
                        if (tabs.getTab(currentIndex).tabType == "value") {

                            var realIndex = currentIndex - serverStatsModel.tabsCount();

                            if (welcomeTab) {
                                realIndex -= 1
                            }

                            viewModel.setCurrentTab(realIndex);
                        } else if (tabs.getTab(currentIndex).tabType == "server_info") {
                            var realIndex = currentIndex;

                            if (welcomeTab) {
                                realIndex -= 1
                            }

                            serverStatsModel.setCurrentTab(index);
                        }
                    }
                }

                WelcomeTab {
                    id: welcomeTab
                    clip: true
                    objectName: "rdm_qml_welcome_tab"

                    property bool not_mapped: true

                    onClose: tabs.removeTab(index)

                    function closeIfOpened() {
                        var welcomeTab = tabs.getTab(0)

                        if (welcomeTab && welcomeTab.not_mapped)
                            tabs.removeTab(0)
                    }
                }

                ServerInfoTabs {
                    model: serverStatsModel
                }

                Connections {
                    target: serverStatsModel

                    onRowsInserted: if (welcomeTab) welcomeTab.closeIfOpened()
                }

                ValueTabs {
                    objectName: "rdm_qml_value_tabs"
                    model: valuesModel
                }

                AddKeyDialog {
                    id: addNewKeyDialog
                    objectName: "rdm_qml_new_key_dialog"

                    width: approot.width * 0.7
                    height: {
                        if (approot.height > 500) {
                            return approot.height * 0.7
                        } else {
                            return approot.height
                        }
                    }
                }

                Connections {
                    target: valuesModel
                    onKeyError: {
                        if (index != -1)
                            tabs.currentIndex = index

                        notification.showError(error)
                    }

                    onRowsInserted: {
                        if (welcomeTab) welcomeTab.closeIfOpened()
                    }

                    onNewKeyDialog: addNewKeyDialog.open()
                }
            }

            BetterTabView {
                id: bottomTabView
                Layout.fillWidth: true
                Layout.minimumHeight: PlatformUtils.isOSXRetina()? 15 : 30

                tabPosition: Qt.BottomEdge

                Consoles {
                    objectName: "rdm_qml_console_tabs"
                    model: consoleModel
                }

                Connections {
                    target: consoleModel

                    onChangeCurrentTab: {
                        bottomTabView.currentIndex = i + 1
                    }
                }

                BetterTab {
                    closable: false
                    title: "Log"
                    icon: "qrc:/images/log.svg"

                    BaseConsole {
                        id: logTab
                        readOnly: true
                        textColor: "#6D6D6E"

                        Connections {
                            target: appLogger
                            onEvent: logTab.append(msg)
                            Component.onCompleted: appLogger.getMessages()
                        }
                    }
                }
            }
        }
    }
}

QT的多國化

在現有代碼中增加多國化的步驟

修改pro文件

增加翻譯配置, 這里可以增加多個

TRANSLATIONS += \
    $$PWD/languages/v2rock_zh_CN.ts

生成ts文件

點擊Tools -> External -> Linguist -> Update Translations (lupdate), 會根據上一步的pro文件, 生成對應的ts文件

如果項目中對tr()有改動, 也需要調用這個命令更新ts文件

編輯ts文件

切換到File System模式, 在ts文件上右鍵, 點擊 Open With -> Qt Linguist, 在界面里會列出每一個語言條目, 輸入翻譯后 Ctrl + Enter就會打勾並進入下一條

生成qm文件

可以通過菜單 Tools -> External -> Linguist -> Release Translations (lrelease), 如果項目路徑較復雜, 可以通過命令行執行 lrelease xxx.ts 直接生成

添加qm到資源文件

打開項目資源文件(qrc文件), 將qm文件添加到資源列表, 例如

<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
    <file>images/off.png</file>
    <file>languages/app_zh_CN.qm</file>
</qresource>
</RCC>

在程序中引用多國化資源

在main.cpp中, 增加 

#include <QTranslator>
....

int main(int argc, char *argv[])
{
    ...

    QTranslator translate;
    bool bret = translate.load(":/languages/app_zh_CN.qm");
    if(!bret)
    {
       qWarning() << "Load language file failed!";
    }
    a.installTranslator(&translate);

    MainDialog w;
    w.show();

    return a.exec();
}

 

Qt界面程序編寫的步驟

1. 設計好全局的QAction, 這個可以被頂欄菜單QMenuBar, 工具欄QToolBar 以及 右鍵彈出菜單QMenu引用.

2. 設計好主窗體布局和主要widget

3. 開始制作各種slot, 並且將QAction connect到對應的slot上, 這一步需要開始設計各個彈出的QDialog

一個未完成的主窗口例子

rockredismain.h

#ifndef ROCKREDISMAIN_H
#define ROCKREDISMAIN_H

#include <QMainWindow>
#include <QSplitter>
#include <QTreeWidget>
#include <QTableWidget>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QToolBar>
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>

namespace Ui {
class RockRedisMain;
}

class RockRedisMain : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void onCustomContextMenuRequested(const QPoint& point);
    void showContextMenu(QTreeWidgetItem* item, const QPoint& globalPos);
    void onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);

private:
    Ui::RockRedisMain *ui;
    QSplitter * splitter;
    QScrollArea *scrollArea;
    QTreeWidget *treeWidget;
    QTableWidget* tableWidget;
    QMenu *fileMenu;
    QMenu *helpMenu;
    QToolBar *fileToolBar;
    QAction *newConnectionAct;
    QAction *editConnectionAct;
    QAction *removeConnectionAct;
    QAction *exitAct;
    QAction *aboutAct;

    void initMenu();
    QTreeWidgetItem* addTreeNode(QString name, QString description);
    QTreeWidgetItem* addChildNode(QTreeWidgetItem *parent, QString name, QString description);
};

#endif // ROCKREDISMAIN_H

rockredismain.cpp

#include "rockredismain.h"
#include "ui_rockredismain.h"

RockRedisMain::RockRedisMain(QWidget *parent) : QMainWindow(parent), ui(new Ui::RockRedisMain) {
    ui->setupUi(this);
    initMenu();

    splitter = new QSplitter(Qt::Horizontal, this);
    setCentralWidget(splitter);

    treeWidget = new QTreeWidget();
    treeWidget->setHeaderHidden(true);
    //treeWidget->setHeaderLabels(QStringList() << "Connections");
    treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    QTreeWidgetItem* item = addTreeNode("Folder", "It's a folder");
    addChildNode(item, "A Connection Folder", "first");
    addChildNode(item, "B Connection File", "second");

    item = addTreeNode("File", "it's a file");
    addChildNode(item, "A", "first");
    addChildNode(item, "B", "second");

    addTreeNode("Folder", "It's a folder");
    addTreeNode("File", "it's a file");
    addTreeNode("Folder", "It's a folder");
    addTreeNode("File", "it's a file");
    addTreeNode("Folder", "It's a folder");
    addTreeNode("File", "it's a file");

    treeWidget->resize(100, 100);
    splitter->addWidget(treeWidget);

    tableWidget = new QTableWidget();
    splitter->addWidget(tableWidget);
    splitter->setStretchFactor(1,1);

    setWindowTitle(tr("RockRedis"));
    resize(800, 600);

    connect(treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint)));
    connect(treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(onCurrentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
}

RockRedisMain::~RockRedisMain() {
    delete ui;
}

void RockRedisMain::initMenu() {
    fileMenu = new QMenu(tr("&File"), this);
    helpMenu = new QMenu(tr("&Help"), this);
    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(helpMenu);

    newConnectionAct = new QAction(tr("&New Connection"), this);
    newConnectionAct->setShortcut(tr("Ctrl+N"));
    newConnectionAct->setStatusTip(tr("Add a new connection"));
    //connect(newConnectionAct, SIGNAL(triggered()), this, SLOT(open()));
    fileMenu->addAction(newConnectionAct);

    editConnectionAct = new QAction(tr("&Edit Connection"), this);
    editConnectionAct->setShortcut(tr("Alt+E"));
    editConnectionAct->setEnabled(false);
    //connect(editConnectionAct, SIGNAL(triggered()), this, SLOT(print()));
    fileMenu->addAction(editConnectionAct);

    removeConnectionAct = new QAction(tr("Remove Connection"), this);
    editConnectionAct->setShortcut(tr("Del"));
    removeConnectionAct->setEnabled(false);
    //connect(newWindowAct, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
    fileMenu->addAction(removeConnectionAct);

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
    fileMenu->addAction(exitAct);

    aboutAct = new QAction(tr("&About"), this);
    helpMenu->addAction(aboutAct);
}

QTreeWidgetItem* RockRedisMain::addTreeNode(QString name, QString description) {
    QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget, 0);
    item->setText(0, name);
    item->setText(1, description);
    return item;
}

QTreeWidgetItem* RockRedisMain::addChildNode(QTreeWidgetItem *parent, QString name, QString description) {
    QTreeWidgetItem *item = new QTreeWidgetItem(parent, 1);
    item->setText(0, name);
    item->setText(1, description);
    return item;
}

void RockRedisMain::onCustomContextMenuRequested(const QPoint& point) {
    QTreeWidgetItem* item = treeWidget->itemAt(point);

    if (item) {
        // Note: We must map the point to global from the viewport to
        // account for the header.
        showContextMenu(item, treeWidget->mapToGlobal(point));
    }
}

void RockRedisMain::showContextMenu(QTreeWidgetItem* item, const QPoint& point) {
    QMenu menu;
    switch (item->type()) {
        case 0:
            menu.addAction(editConnectionAct);
            menu.addAction(removeConnectionAct);
            break;
        case 1:
            menu.addAction("This is a type 2");
            break;
    }
    menu.exec(point);
}

void RockRedisMain::onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) {
    if (current == NULL) {
        editConnectionAct->setEnabled(false);
        removeConnectionAct->setEnabled(false);
    } else if (current->type() == 0) {
        editConnectionAct->setEnabled(true);
        removeConnectionAct->setEnabled(true);
    } else {
        editConnectionAct->setEnabled(false);
        removeConnectionAct->setEnabled(false);
    }
}

 

QT項目的圖標

在Linux下發布項目, 圖標遵循的也是Linux下的規范, 現在較流行的是 freedesktop , 可以參考其最新的文檔. 應用的圖標必須按theme分類, theme下面再按圖標尺寸分目錄存儲. 必須實現的theme是hicolor. 在Ubuntu20.04下, 圖標的存放位置主要為 /usr/share/icons/ 和 $HOME/.local/share/icons/ 這兩個目錄, 其他的在/usr/share/ 下分應用, 在各自目錄下還有一些icons目錄.

/usr/share/icons/ 是系統主要的圖標目錄, 下面有hicolor, Yaru, locolor, default, gnome等多個目錄, 每個目錄對應的一種theme, 在每個theme目錄下, 是按尺寸分類的子目錄, 每個子目錄下是對應尺寸的應用圖標類別, 類別下才是圖標的png文件. 16x16這個尺寸的圖標是最完整的.

/usr/share/icons/hicolor/16x16/apps/$ ll
total 164
-rw-r--r-- 1 root root  658 Mar 13 16:32 bluetooth.png
-rw-r--r-- 1 root root 1418 Mar 24 22:05 display-im6.q16.png
-rw-r--r-- 1 root root  526 Apr 15 22:27 evolution-mail.png
-rw-r--r-- 1 root root  553 Apr 15 22:27 evolution-memos.png
-rw-r--r-- 1 root root  714 Apr 15 22:27 evolution-tasks.png
-rw-r--r-- 1 root root  459 Feb  8 18:59 filezilla.png
-rw-r--r-- 1 root root  722 May  8 22:42 firefox.png
-rw-r--r-- 1 root root  411 Mar 19 23:30 gcr-gnupg.png
-rw-r--r-- 1 root root  693 Mar 19 23:30 gcr-key-pair.png
-rw-r--r-- 1 root root  511 Mar 19 23:30 gcr-key.png
-rw-r--r-- 1 root root  267 Mar 19 23:30 gcr-password.png
-rw-r--r-- 1 root root  334 Mar 19 23:30 gcr-smart-card.png
-rw-r--r-- 1 root root  528 May  1 15:10 gnome-power-manager.png
-rw-r--r-- 1 root root  781 May  1 15:10 goa-panel.png
-rw-r--r-- 1 root root  689 May 10 01:30 google-chrome.png
-rw-r--r-- 1 root root  640 Apr  2 19:25 hook-notifier.png
-rw-r--r-- 1 root root  508 Mar 30 17:50 ibus-keyboard.png
-rw-r--r-- 1 root root  586 Apr 16 19:44 inkscape.png

同樣需要了解的是freedesktop的桌面應用圖標標准,  https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/ 

這個標准規定了桌面圖標文件(.desktop文件)的格式. desktop文件通常存儲於  /usr/share/applications/ 和 $HOME/.local/share/applications/ 目錄. 前者是系統的應用入口文件路徑, 可以被所有用戶訪問. 以下是QT自己的desktop文件作為例子

[Desktop Entry]
Type=Application
Exec="/opt/qt/Qt5.14.2/Tools/QtCreator/bin/qtcreator" %F
Name=Qt Creator
GenericName=The IDE of choice for Qt development.
Icon=QtProject-qtcreator
StartupWMClass=qtcreator
Terminal=false
Categories=Development;IDE;Qt;
MimeType=text/x-c++src;text/x-c++hdr;text/x-xsrc;application/x-designer;applicat
ion/vnd.qt.qmakeprofile;application/vnd.qt.xml.resource;text/x-qml;text/x-qt.qml
;text/x-qt.qbs;

  

查看QT生成的AppImage文件

有兩種類型,  

Type 1:

mkdir mountpoint
sudo mount -o loop my.AppImage mountpoint/

# You can now inspect the contents
# You can now also copy the contents to a writable location of your hard disk

sudo umount mountpoint/
# Do not forget the umount step!
# If you do forget it, your system may exhibit unwanted behavior.

Type 2:

mkdir mountpoint
my.AppImage --appimage-offset
123456 # This is just an example output

sudo mount my.AppImage mountpoint/ -o offset=123456

# you can now inspect the contents

sudo umount mountpoint/
# Do not forget the umount step!
# If you do forget it, your system may exhibit unwanted behavior.

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM