准备工作

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

  1. # 安装g++
  2. sudo apt install build-essential
  3. #
  4. sudo apt install libfontconfig1
  5. # 安装openGL支持
  6. 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口令.

组件选择: 如果空间足够的话, 除了一个deprecated的以外, 都选上吧. 实测, 整个安装下来占地5.4GB

然后就可以在菜单中找到Qt Creator, 启动似乎非常快, 一秒不到就打开界面了, 完全不像一个数百MB的程序啊

运行一个例子

在网上找了一个例子, 项目名叫First, 但是编译中出现了错误

  1. $ gcc first.o all -o first
  2. 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.

  1. $ make
  2. /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
  3. 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
  4. 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
  5. gcc first.o all -o first // <<<<< confused, decides to rebuild "first" target using a file called "all"
  6. gcc: all: No such file or directory
  7. make: *** [first] Error 1

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

  1. $ make
  2. /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
  3. 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
  4. make: Circular all <- first dependency dropped. // <<<< confused but recognises the situation
  5. 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

  1. .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

  1. #-------------------------------------------------
  2. #
  3. # Project created by QtCreator 2018-08-27T14:02:44
  4. #
  5. #-------------------------------------------------
  6.  
  7. QT += core gui
  8. QT += printsupport
  9.  
  10. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  11.  
  12. TARGET = MenuWindow
  13. TEMPLATE = app
  14.  
  15. # The following define makes your compiler emit warnings if you use
  16. # any feature of Qt which has been marked as deprecated (the exact warnings
  17. # depend on your compiler). Please consult the documentation of the
  18. # deprecated API in order to know how to port your code away from it.
  19. DEFINES += QT_DEPRECATED_WARNINGS
  20.  
  21. # You can also make your code fail to compile if you use deprecated APIs.
  22. # In order to do so, uncomment the following line.
  23. # You can also select to disable deprecated APIs only up to a certain version of Qt.
  24. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
  25.  
  26. SOURCES += \
  27. main.cpp \
  28. mainwindow.cpp \
  29. mydialog.cpp
  30.  
  31. HEADERS += \
  32. mainwindow.h \
  33. mydialog.h
  34.  
  35. FORMS += \
  36. mainwindow.ui \
  37. mydialog.ui

mainwindow.h

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include <QLabel>
  6. #include <QScrollArea>
  7. #include <QMenu>
  8. #include <QAction>
  9. #include "mydialog.h"
  10.  
  11. namespace Ui {
  12. class MainWindow;
  13. }
  14.  
  15. class MainWindow : public QMainWindow
  16. {
  17. Q_OBJECT
  18.  
  19. public:
  20. explicit MainWindow(QWidget *parent = 0);
  21. ~MainWindow();
  22.  
  23. private slots:
  24. void open();
  25. void print();
  26. void zoomIn();
  27. void zoomOut();
  28. void normalSize();
  29. void fitToWindow();
  30. void about();
  31. void newWindow();
  32.  
  33. private:
  34. Ui::MainWindow *ui;
  35. MyDialog *myDialog;
  36. QLabel *imageLabel;
  37. QScrollArea *scrollArea;
  38. QMenu *fileMenu;
  39. QMenu *viewMenu;
  40. QMenu *helpMenu;
  41. QAction *openAct;
  42. QAction *printAct;
  43. QAction *newWindowAct;
  44. QAction *exitAct;
  45. QAction *zoomInAct;
  46. QAction *zoomOutAct;
  47. QAction *normalSizeAct;
  48. QAction *fitToWindowAct;
  49. QAction *aboutAct;
  50. QAction *aboutQtAct;
  51.  
  52. double scaleFactor;
  53.  
  54. void updateActions();
  55. void scaleImage(double factor);
  56. void adjustScrollBar(QScrollBar *scrollBar, double factor);
  57. };
  58.  
  59. #endif // MAINWINDOW_H

mainwindow.cpp

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. #include <QtWidgets>
  5. #include <QFileDialog>
  6. #include <QMessageBox>
  7.  
  8. #ifndef QT_NO_PRINTER
  9. #include <QPrintDialog>
  10. #endif
  11.  
  12. MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
  13. ui->setupUi(this);
  14.  
  15. openAct = new QAction(tr("&Open..."), this);
  16. openAct->setShortcut(tr("Ctrl+O"));
  17.  
  18. printAct = new QAction(tr("&Print..."), this);
  19. printAct->setShortcut(tr("Ctrl+P"));
  20. printAct->setEnabled(false);
  21.  
  22. newWindowAct = new QAction(tr("&New Window"), this);
  23. newWindowAct->setShortcut(tr("Ctrl+N"));
  24.  
  25. exitAct = new QAction(tr("E&xit"), this);
  26. exitAct->setShortcut(tr("Ctrl+Q"));
  27.  
  28. zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
  29. zoomInAct->setShortcut(tr("Ctrl+=")); //(Ctrl)(+)
  30. zoomInAct->setEnabled(false);
  31.  
  32. zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
  33. zoomOutAct->setShortcut(tr("Ctrl+-")); //(Ctrl)(-)
  34. zoomOutAct->setEnabled(false);
  35.  
  36. normalSizeAct = new QAction(tr("&Normal Size"), this);
  37. normalSizeAct->setShortcut(tr("Ctrl+S"));
  38. normalSizeAct->setEnabled(false);
  39.  
  40. fitToWindowAct = new QAction(tr("&Fit to Window"), this);
  41. fitToWindowAct->setEnabled(false);
  42. fitToWindowAct->setCheckable(true);
  43. fitToWindowAct->setShortcut(tr("Ctrl+F"));
  44.  
  45. aboutAct = new QAction(tr("&About"), this);
  46.  
  47. aboutQtAct = new QAction(tr("About &Qt"), this);
  48.  
  49. fileMenu = new QMenu(tr("&File"), this);
  50. fileMenu->addAction(openAct);
  51. fileMenu->addAction(printAct);
  52. fileMenu->addAction(newWindowAct);
  53. fileMenu->addSeparator();
  54. fileMenu->addAction(exitAct);
  55.  
  56. viewMenu = new QMenu(tr("&View"), this);
  57. viewMenu->addAction(zoomInAct);
  58. viewMenu->addAction(zoomOutAct);
  59. viewMenu->addAction(normalSizeAct);
  60. viewMenu->addSeparator();
  61. viewMenu->addAction(fitToWindowAct);
  62.  
  63. helpMenu = new QMenu(tr("&Help"), this);
  64. helpMenu->addAction(aboutAct);
  65. helpMenu->addAction(aboutQtAct);
  66.  
  67. menuBar()->addMenu(fileMenu);
  68. menuBar()->addMenu(viewMenu);
  69. menuBar()->addMenu(helpMenu);
  70.  
  71. imageLabel = new QLabel(this);
  72. imageLabel->setBackgroundRole(QPalette::Base);
  73. imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
  74. imageLabel->setScaledContents(true);
  75.  
  76. scrollArea = new QScrollArea(this);
  77. scrollArea->setBackgroundRole(QPalette::Dark);
  78. scrollArea->setWidget(imageLabel);
  79.  
  80. setCentralWidget(scrollArea);
  81. setWindowTitle(tr("Image Viewer"));
  82. resize(500, 400);
  83.  
  84. connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
  85. connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
  86. connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
  87. connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
  88. connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
  89. connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
  90. connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));
  91. connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
  92. connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
  93. connect(newWindowAct, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
  94. }
  95.  
  96. MainWindow::~MainWindow() {
  97. delete ui;
  98. }
  99.  
  100. void MainWindow::open() {
  101. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath());
  102. if (!fileName.isEmpty()) {
  103. QImage image(fileName);
  104. if (image.isNull()) {
  105. QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
  106. return;
  107. }
  108. imageLabel->setPixmap(QPixmap::fromImage(image));
  109. scaleFactor = 1.0;
  110.  
  111. printAct->setEnabled(true);
  112. fitToWindowAct->setEnabled(true);
  113. updateActions();
  114.  
  115. if (!fitToWindowAct->isChecked())
  116. imageLabel->adjustSize();
  117. }
  118. }
  119.  
  120. void MainWindow::print() {
  121.  
  122. }
  123.  
  124. void MainWindow::newWindow() {
  125. myDialog = new MyDialog(this);
  126. myDialog->resize(300, 200);
  127. /*myDialog->setModal(true);
  128. myDialog->exec();*/
  129. myDialog->show();
  130. }
  131.  
  132. void MainWindow::zoomIn() {
  133. scaleImage(1.25);
  134. }
  135.  
  136. void MainWindow::zoomOut() {
  137. scaleImage(0.8);
  138. }
  139.  
  140. void MainWindow::normalSize() {
  141. imageLabel->adjustSize();
  142. scaleFactor = 1.0;
  143. }
  144.  
  145. void MainWindow::fitToWindow() {
  146. bool fitToWindow = fitToWindowAct->isChecked();
  147. scrollArea->setWidgetResizable(fitToWindow);
  148. if (!fitToWindow) {
  149. normalSize();
  150. }
  151. updateActions();
  152. }
  153.  
  154. void MainWindow::about() {
  155. QMessageBox::about(this, tr("About Image Viewer"), tr("<b>Image Viewer</b> example."));
  156. }
  157.  
  158. void MainWindow::updateActions() {
  159. zoomInAct->setEnabled(!fitToWindowAct->isChecked());
  160. zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
  161. normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
  162. }
  163.  
  164. void MainWindow::scaleImage(double factor) {
  165. Q_ASSERT(imageLabel->pixmap());
  166. scaleFactor *= factor;
  167. imageLabel->resize(scaleFactor * imageLabel->pixmap()->size());
  168.  
  169. adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
  170. adjustScrollBar(scrollArea->verticalScrollBar(), factor);
  171.  
  172. zoomInAct->setEnabled(scaleFactor < 3.0);
  173. zoomOutAct->setEnabled(scaleFactor > 0.333);
  174. }
  175.  
  176. void MainWindow::adjustScrollBar(QScrollBar *scrollBar, double factor)
  177. {
  178. int value = scrollBar->value();
  179. int step = scrollBar->pageStep();
  180. value = int(factor * value + ((factor - 1) * step/2));
  181. scrollBar->setValue(value);
  182. }

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()方法的处理为

  1. 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

  1. import QtQuick 2.0
  2. import QtQuick.Layouts 1.1
  3. import QtQuick.Controls 1.4
  4. import QtQuick.Controls.Styles 1.1
  5. import QtQuick.Dialogs 1.2
  6. import QtQml.Models 2.2
  7. import QtQuick.Window 2.2
  8. import Qt.labs.settings 1.0
  9. import "."
  10. import "./common"
  11. import "./common/platformutils.js" as PlatformUtils
  12. import "./value-editor"
  13. import "./connections-tree"
  14. import "./console"
  15. import "./server-info"
  16. import "./bulk-operations"
  17.  
  18. ApplicationWindow {
  19. id: approot
  20. visible: true
  21. objectName: "rdm_qml_root"
  22. title: "Redis Desktop Manager " + Qt.application.version
  23. width: 1100
  24. height: 800
  25.  
  26. property double wRatio : (width * 1.0) / (Screen.width * 1.0)
  27. property double hRatio : (height * 1.0) / (Screen.height * 1.0)
  28.  
  29. property var currentValueFormatter
  30.  
  31. Component.onCompleted: {
  32. if (hRatio > 1 || wRatio > 1) {
  33. console.log("Ratio > 1.0. Resize main window.")
  34. width = Screen.width * 0.9
  35. height = Screen.height * 0.8
  36. }
  37.  
  38. if (PlatformUtils.isOSXRetina(Screen)) {
  39. bottomTabView.implicitHeight = 100
  40. }
  41. }
  42.  
  43. Settings {
  44. category: "windows_settings"
  45. property alias x: approot.x
  46. property alias y: approot.y
  47. property alias width: approot.width
  48. property alias height: approot.height
  49. }
  50.  
  51. SystemPalette {
  52. id: sysPalette
  53. }
  54.  
  55. FontLoader {
  56. id: monospacedFont
  57. Component.onCompleted: {
  58. source = "qrc:/fonts/Inconsolata-Regular.ttf"
  59. }
  60. }
  61.  
  62. QuickStartDialog {
  63. id: quickStartDialog
  64. objectName: "rdm_qml_quick_start_dialog"
  65.  
  66. width: PlatformUtils.isOSX() ? 600 : approot.width * 0.8
  67. }
  68.  
  69. GlobalSettings {
  70. id: settingsDialog
  71. }
  72.  
  73. ConnectionSettignsDialog {
  74. id: connectionSettingsDialog
  75.  
  76. objectName: "rdm_connection_settings_dialog"
  77.  
  78. onTestConnection: {
  79. if (connectionsManager.testConnectionSettings(settings)) {
  80. hideLoader()
  81. showMsg(qsTr("Successful connection to redis-server"))
  82. } else {
  83. hideLoader()
  84. showError(qsTr("Can't connect to redis-server"))
  85. }
  86. }
  87.  
  88. onSaveConnection: connectionsManager.updateConnection(settings)
  89. }
  90.  
  91. MessageDialog {
  92. id: notification
  93. objectName: "rdm_qml_error_dialog"
  94. visible: false
  95. modality: Qt.WindowModal
  96. icon: StandardIcon.Warning
  97. standardButtons: StandardButton.Ok
  98.  
  99. function showError(msg) {
  100. icon = StandardIcon.Warning
  101. text = msg
  102. open()
  103. }
  104.  
  105. function showMsg(msg) {
  106. icon = StandardIcon.Information
  107. text = msg
  108. open()
  109. }
  110. }
  111.  
  112. BulkOperationsDialog {
  113. id: bulkOperationDialog
  114. }
  115.  
  116. Connections {
  117. target: bulkOperations
  118.  
  119. onOpenDialog: {
  120. bulkOperationDialog.operationName = operationName
  121. bulkOperationDialog.open()
  122. }
  123. }
  124.  
  125. Connections {
  126. target: connectionsManager
  127.  
  128. onEditConnection: {
  129. connectionSettingsDialog.settings = config
  130. connectionSettingsDialog.open()
  131. }
  132.  
  133. onError: {
  134. notification.showError(err)
  135. }
  136.  
  137. Component.onCompleted: {
  138. if (connectionsManager.size() == 0)
  139. quickStartDialog.open()
  140. }
  141. }
  142.  
  143. toolBar: AppToolBar {}
  144.  
  145. BetterSplitView {
  146. anchors.fill: parent
  147. orientation: Qt.Horizontal
  148.  
  149. BetterTreeView {
  150. id: connectionsTree
  151. Layout.fillHeight: true
  152. Layout.minimumWidth: 350
  153. Layout.minimumHeight: 500
  154. }
  155.  
  156. BetterSplitView {
  157. orientation: Qt.Vertical
  158.  
  159. BetterTabView {
  160. id: tabs
  161. objectName: "rdm_qml_tabs"
  162. currentIndex: 0
  163.  
  164. Layout.fillHeight: true
  165. Layout.fillWidth: true
  166. Layout.minimumWidth: 650
  167. Layout.minimumHeight: 30
  168.  
  169. onCurrentIndexChanged: {
  170.  
  171. if (tabs.getTab(currentIndex).tabType) {
  172. if (tabs.getTab(currentIndex).tabType == "value") {
  173.  
  174. var realIndex = currentIndex - serverStatsModel.tabsCount();
  175.  
  176. if (welcomeTab) {
  177. realIndex -= 1
  178. }
  179.  
  180. viewModel.setCurrentTab(realIndex);
  181. } else if (tabs.getTab(currentIndex).tabType == "server_info") {
  182. var realIndex = currentIndex;
  183.  
  184. if (welcomeTab) {
  185. realIndex -= 1
  186. }
  187.  
  188. serverStatsModel.setCurrentTab(index);
  189. }
  190. }
  191. }
  192.  
  193. WelcomeTab {
  194. id: welcomeTab
  195. clip: true
  196. objectName: "rdm_qml_welcome_tab"
  197.  
  198. property bool not_mapped: true
  199.  
  200. onClose: tabs.removeTab(index)
  201.  
  202. function closeIfOpened() {
  203. var welcomeTab = tabs.getTab(0)
  204.  
  205. if (welcomeTab && welcomeTab.not_mapped)
  206. tabs.removeTab(0)
  207. }
  208. }
  209.  
  210. ServerInfoTabs {
  211. model: serverStatsModel
  212. }
  213.  
  214. Connections {
  215. target: serverStatsModel
  216.  
  217. onRowsInserted: if (welcomeTab) welcomeTab.closeIfOpened()
  218. }
  219.  
  220. ValueTabs {
  221. objectName: "rdm_qml_value_tabs"
  222. model: valuesModel
  223. }
  224.  
  225. AddKeyDialog {
  226. id: addNewKeyDialog
  227. objectName: "rdm_qml_new_key_dialog"
  228.  
  229. width: approot.width * 0.7
  230. height: {
  231. if (approot.height > 500) {
  232. return approot.height * 0.7
  233. } else {
  234. return approot.height
  235. }
  236. }
  237. }
  238.  
  239. Connections {
  240. target: valuesModel
  241. onKeyError: {
  242. if (index != -1)
  243. tabs.currentIndex = index
  244.  
  245. notification.showError(error)
  246. }
  247.  
  248. onRowsInserted: {
  249. if (welcomeTab) welcomeTab.closeIfOpened()
  250. }
  251.  
  252. onNewKeyDialog: addNewKeyDialog.open()
  253. }
  254. }
  255.  
  256. BetterTabView {
  257. id: bottomTabView
  258. Layout.fillWidth: true
  259. Layout.minimumHeight: PlatformUtils.isOSXRetina()? 15 : 30
  260.  
  261. tabPosition: Qt.BottomEdge
  262.  
  263. Consoles {
  264. objectName: "rdm_qml_console_tabs"
  265. model: consoleModel
  266. }
  267.  
  268. Connections {
  269. target: consoleModel
  270.  
  271. onChangeCurrentTab: {
  272. bottomTabView.currentIndex = i + 1
  273. }
  274. }
  275.  
  276. BetterTab {
  277. closable: false
  278. title: "Log"
  279. icon: "qrc:/images/log.svg"
  280.  
  281. BaseConsole {
  282. id: logTab
  283. readOnly: true
  284. textColor: "#6D6D6E"
  285.  
  286. Connections {
  287. target: appLogger
  288. onEvent: logTab.append(msg)
  289. Component.onCompleted: appLogger.getMessages()
  290. }
  291. }
  292. }
  293. }
  294. }
  295. }
  296. }

Qt界面程序编写的步骤

1. 设计好全局的QAction, 这个可以被顶栏菜单QMenuBar, 工具栏QToolBar 以及 右键弹出菜单QMenu引用.

2. 设计好主窗体布局和主要widget

3. 开始制作各种slot, 并且将QAction connect到对应的slot上, 这一步需要开始设计各个弹出的QDialog

一个未完成的主窗口例子

rockredismain.h

  1. #ifndef ROCKREDISMAIN_H
  2. #define ROCKREDISMAIN_H
  3.  
  4. #include <QMainWindow>
  5. #include <QSplitter>
  6. #include <QTreeWidget>
  7. #include <QTableWidget>
  8. #include <QMenuBar>
  9. #include <QMenu>
  10. #include <QAction>
  11. #include <QToolBar>
  12. #include <QLabel>
  13. #include <QScrollArea>
  14. #include <QVBoxLayout>
  15.  
  16. namespace Ui {
  17. class RockRedisMain;
  18. }
  19.  
  20. class RockRedisMain : public QMainWindow
  21. {
  22. Q_OBJECT
  23.  
  24. public:
  25. explicit RockRedisMain(QWidget *parent = 0);
  26. ~RockRedisMain();
  27.  
  28. private slots:
  29. void onCustomContextMenuRequested(const QPoint& point);
  30. void showContextMenu(QTreeWidgetItem* item, const QPoint& globalPos);
  31. void onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
  32.  
  33. private:
  34. Ui::RockRedisMain *ui;
  35. QSplitter * splitter;
  36. QScrollArea *scrollArea;
  37. QTreeWidget *treeWidget;
  38. QTableWidget* tableWidget;
  39. QMenu *fileMenu;
  40. QMenu *helpMenu;
  41. QToolBar *fileToolBar;
  42. QAction *newConnectionAct;
  43. QAction *editConnectionAct;
  44. QAction *removeConnectionAct;
  45. QAction *exitAct;
  46. QAction *aboutAct;
  47.  
  48. void initMenu();
  49. QTreeWidgetItem* addTreeNode(QString name, QString description);
  50. QTreeWidgetItem* addChildNode(QTreeWidgetItem *parent, QString name, QString description);
  51. };
  52.  
  53. #endif // ROCKREDISMAIN_H

rockredismain.cpp

  1. #include "rockredismain.h"
  2. #include "ui_rockredismain.h"
  3.  
  4. RockRedisMain::RockRedisMain(QWidget *parent) : QMainWindow(parent), ui(new Ui::RockRedisMain) {
  5. ui->setupUi(this);
  6. initMenu();
  7.  
  8. splitter = new QSplitter(Qt::Horizontal, this);
  9. setCentralWidget(splitter);
  10.  
  11. treeWidget = new QTreeWidget();
  12. treeWidget->setHeaderHidden(true);
  13. //treeWidget->setHeaderLabels(QStringList() << "Connections");
  14. treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  15. QTreeWidgetItem* item = addTreeNode("Folder", "It's a folder");
  16. addChildNode(item, "A Connection Folder", "first");
  17. addChildNode(item, "B Connection File", "second");
  18.  
  19. item = addTreeNode("File", "it's a file");
  20. addChildNode(item, "A", "first");
  21. addChildNode(item, "B", "second");
  22.  
  23. addTreeNode("Folder", "It's a folder");
  24. addTreeNode("File", "it's a file");
  25. addTreeNode("Folder", "It's a folder");
  26. addTreeNode("File", "it's a file");
  27. addTreeNode("Folder", "It's a folder");
  28. addTreeNode("File", "it's a file");
  29.  
  30. treeWidget->resize(100, 100);
  31. splitter->addWidget(treeWidget);
  32.  
  33. tableWidget = new QTableWidget();
  34. splitter->addWidget(tableWidget);
  35. splitter->setStretchFactor(1,1);
  36.  
  37. setWindowTitle(tr("RockRedis"));
  38. resize(800, 600);
  39.  
  40. connect(treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint)));
  41. connect(treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(onCurrentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
  42. }
  43.  
  44. RockRedisMain::~RockRedisMain() {
  45. delete ui;
  46. }
  47.  
  48. void RockRedisMain::initMenu() {
  49. fileMenu = new QMenu(tr("&File"), this);
  50. helpMenu = new QMenu(tr("&Help"), this);
  51. menuBar()->addMenu(fileMenu);
  52. menuBar()->addMenu(helpMenu);
  53.  
  54. newConnectionAct = new QAction(tr("&New Connection"), this);
  55. newConnectionAct->setShortcut(tr("Ctrl+N"));
  56. newConnectionAct->setStatusTip(tr("Add a new connection"));
  57. //connect(newConnectionAct, SIGNAL(triggered()), this, SLOT(open()));
  58. fileMenu->addAction(newConnectionAct);
  59.  
  60. editConnectionAct = new QAction(tr("&Edit Connection"), this);
  61. editConnectionAct->setShortcut(tr("Alt+E"));
  62. editConnectionAct->setEnabled(false);
  63. //connect(editConnectionAct, SIGNAL(triggered()), this, SLOT(print()));
  64. fileMenu->addAction(editConnectionAct);
  65.  
  66. removeConnectionAct = new QAction(tr("Remove Connection"), this);
  67. editConnectionAct->setShortcut(tr("Del"));
  68. removeConnectionAct->setEnabled(false);
  69. //connect(newWindowAct, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
  70. fileMenu->addAction(removeConnectionAct);
  71.  
  72. exitAct = new QAction(tr("E&xit"), this);
  73. exitAct->setShortcut(tr("Ctrl+Q"));
  74. connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
  75. fileMenu->addAction(exitAct);
  76.  
  77. aboutAct = new QAction(tr("&About"), this);
  78. helpMenu->addAction(aboutAct);
  79. }
  80.  
  81. QTreeWidgetItem* RockRedisMain::addTreeNode(QString name, QString description) {
  82. QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget, 0);
  83. item->setText(0, name);
  84. item->setText(1, description);
  85. return item;
  86. }
  87.  
  88. QTreeWidgetItem* RockRedisMain::addChildNode(QTreeWidgetItem *parent, QString name, QString description) {
  89. QTreeWidgetItem *item = new QTreeWidgetItem(parent, 1);
  90. item->setText(0, name);
  91. item->setText(1, description);
  92. return item;
  93. }
  94.  
  95. void RockRedisMain::onCustomContextMenuRequested(const QPoint& point) {
  96. QTreeWidgetItem* item = treeWidget->itemAt(point);
  97.  
  98. if (item) {
  99. // Note: We must map the point to global from the viewport to
  100. // account for the header.
  101. showContextMenu(item, treeWidget->mapToGlobal(point));
  102. }
  103. }
  104.  
  105. void RockRedisMain::showContextMenu(QTreeWidgetItem* item, const QPoint& point) {
  106. QMenu menu;
  107. switch (item->type()) {
  108. case 0:
  109. menu.addAction(editConnectionAct);
  110. menu.addAction(removeConnectionAct);
  111. break;
  112. case 1:
  113. menu.addAction("This is a type 2");
  114. break;
  115. }
  116. menu.exec(point);
  117. }
  118.  
  119. void RockRedisMain::onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) {
  120. if (current == NULL) {
  121. editConnectionAct->setEnabled(false);
  122. removeConnectionAct->setEnabled(false);
  123. } else if (current->type() == 0) {
  124. editConnectionAct->setEnabled(true);
  125. removeConnectionAct->setEnabled(true);
  126. } else {
  127. editConnectionAct->setEnabled(false);
  128. removeConnectionAct->setEnabled(false);
  129. }
  130. }

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这个尺寸的图标是最完整的.

  1. /usr/share/icons/hicolor/16x16/apps/$ ll
  2. total 164
  3. -rw-r--r-- 1 root root 658 Mar 13 16:32 bluetooth.png
  4. -rw-r--r-- 1 root root 1418 Mar 24 22:05 display-im6.q16.png
  5. -rw-r--r-- 1 root root 526 Apr 15 22:27 evolution-mail.png
  6. -rw-r--r-- 1 root root 553 Apr 15 22:27 evolution-memos.png
  7. -rw-r--r-- 1 root root 714 Apr 15 22:27 evolution-tasks.png
  8. -rw-r--r-- 1 root root 459 Feb 8 18:59 filezilla.png
  9. -rw-r--r-- 1 root root 722 May 8 22:42 firefox.png
  10. -rw-r--r-- 1 root root 411 Mar 19 23:30 gcr-gnupg.png
  11. -rw-r--r-- 1 root root 693 Mar 19 23:30 gcr-key-pair.png
  12. -rw-r--r-- 1 root root 511 Mar 19 23:30 gcr-key.png
  13. -rw-r--r-- 1 root root 267 Mar 19 23:30 gcr-password.png
  14. -rw-r--r-- 1 root root 334 Mar 19 23:30 gcr-smart-card.png
  15. -rw-r--r-- 1 root root 528 May 1 15:10 gnome-power-manager.png
  16. -rw-r--r-- 1 root root 781 May 1 15:10 goa-panel.png
  17. -rw-r--r-- 1 root root 689 May 10 01:30 google-chrome.png
  18. -rw-r--r-- 1 root root 640 Apr 2 19:25 hook-notifier.png
  19. -rw-r--r-- 1 root root 508 Mar 30 17:50 ibus-keyboard.png
  20. -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文件作为例子

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

  

查看QT生成的AppImage文件

有两种类型,

Type 1:

  1. mkdir mountpoint
  2. sudo mount -o loop my.AppImage mountpoint/
  3.  
  4. # You can now inspect the contents
  5. # You can now also copy the contents to a writable location of your hard disk
  6.  
  7. sudo umount mountpoint/
  8. # Do not forget the umount step!
  9. # If you do forget it, your system may exhibit unwanted behavior.

Type 2:

  1. mkdir mountpoint
  2. my.AppImage --appimage-offset
  3. 123456 # This is just an example output
  4.  
  5. sudo mount my.AppImage mountpoint/ -o offset=123456
  6.  
  7. # you can now inspect the contents
  8.  
  9. sudo umount mountpoint/
  10. # Do not forget the umount step!
  11. # If you do forget it, your system may exhibit unwanted behavior.

  

Ubuntu18.04中配置QT5.11开发环境的更多相关文章

  1. Ubuntu18.04下配置深度学习开发环境

    在Ubuntu18.04下配置深度学习/机器学习开发环境 1.下载并安装Anaconda 下载地址:https://www.anaconda.com/distribution/#linux 安装步骤: ...

  2. 在VMware虚拟机中配置DOS汇编开发环境!!

    操作系统:win7 32位 DOS环境:DosBox  下载:http://www.dosbox.com/ 选择当前适合自己版本,下载就可以了. 汇编编译器:MASM 5.0 下载:http://do ...

  3. 在 Ubuntu16.04 中搭建 Spark 单机开发环境 (JDK + Scala + Spark)

    1.准备 本文主要讲述如何在Ubuntu 16.04 中搭建 Spark 2.11 单机开发环境,主要分为 3 部分:JDK 安装,Scala 安装和 Spark 安装. JDK 1.8:jdk-8u ...

  4. 在VS2008中配置WDK7600驱动开发环境

    网上这类资料多如牛毛,也许很多人都是转来转去,却很有人去真正的测试,有时候感觉确实对他人也是一种误导. 这里是我自己在VS2008 + WDK7600.16385.0 + DDKWizard配置自己的 ...

  5. 在Eclipse中配置安卓的开发环境 (踩过的坑)

    这个学期学校有门安卓程序设计课需要安装安卓开发环境. 一开始安装的是Andriod Studio,但是过程很坎坷很心酸,遇到各种各样的问题,最后还没有解决. 没办法决定用Eclipse配置安卓环境,虽 ...

  6. Ubuntu18.04中配置wxWidget3.0.4开发环境

    准备工作 在 https://www.wxwidgets.org/downloads/ 下载最新的稳定版 wxWidgets-3.0.4.tar.bz2 安装依赖 -dev build-essenti ...

  7. Ubuntu16.04 部署配置GO语言开发环境 & 注意事项

    1. 安装GO 安装go语言包: $ curl -O https://storage.googleapis.com/golang/go1.10.1.linux-amd64.tar.gz   下载完成后 ...

  8. Go语言系列:(1)在VsCode中配置Go的开发环境

    一.为什么选VSCode 这个系列的初宗是带领公司的PHPer转Go,在正式写这篇博文前,咱们先说说Go有哪些主流的IDE 1.GoLand(收费) JetBrains出品必属精品,除了贵没有其它缺点 ...

  9. 在Sublime Text 3中配置Python3的开发环境/Build System

    本文来源:https://www.cnblogs.com/zhangqinwei/p/6886600.html Sublime Text作为一款支持多种编程语言的文本编辑神器,深受广大开发者的喜爱.通 ...

随机推荐

  1. Intent 常用场景 FileProvider 拍照 裁剪 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. LSTM 文本情感分析/序列分类 Keras

    LSTM 文本情感分析/序列分类 Keras 请参考 http://spaces.ac.cn/archives/3414/   neg.xls是这样的 pos.xls是这样的neg=pd.read_e ...

  4. Kafka:ZK+Kafka+Spark Streaming集群环境搭建(五)针对hadoop2.9.0启动之后发现slave上正常启动了DataNode,DataManager,但是过了几秒后发现DataNode被关闭

    启动之后发现slave上正常启动了DataNode,DataManager,但是过了几秒后发现DataNode被关闭 以slave1上错误日期为例查看错误信息: /logs/hadoop-spark- ...

  5. Ubuntu14设置静态IP的地方

    sudo vim /etc/network/interfaces 修改如下部分: auto eth0iface eth0 inet staticaddress 192.168.0.117gateway ...

  6. Docker: Unknown – Unable to query docker version: x509: certificate is valid for

      I was playing around with Docker locally and somehow ended up with this error when I tried to list ...

  7. 【nodejs】FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    当使用大批量(>100)的SQL进行MySql数据库插值任务时,会发生以下错误: 总计将有371579条数据将被插入数据库 开始插入DB <--- Last few GCs ---> ...

  8. 获取sevlet response值

    调用: PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), "UTF-8 ...

  9. Oracle使用技巧及PL/SQL Developer配置

    Oracle使用技巧及PL/SQL Developer配置 摘自:http://livenzhao.spaces.live.com/blog/cns!6E368BE9F6DDD872!595.entr ...

  10. maven 上传包

    本地 mvn deploy 第三方jar包 mvn deploy:deploy-file -DgroupId=com.zendaimoney.uc -DartifactId=uc-client -Dv ...