2020年5月25日 星期一

TextEdit 的基本應用

TextEdit 的基本應用

前言

  在上一篇 Qt 的拖拉 widget 與事件 中,提及了 PushButton 的基本應用,這次來使用 TextEdit 來做為輸入,在此做個紀錄。

內容

  先到 [ GitLab ] HelloQt 下載範例,這次應用的專案路徑
(HelloQt' directory)/TextEdit/Basic,在 Qt Creator 開啟設計介面會看到以下
TextEdit 在 widget 工具箱的位置

圖中可以看到 TextEdit 在 widget 工具箱的位置,範例已經拖好了一個,可以依據需要自己新增。範例執行的結果如下
範例的執行結果

在範例按下"ShowMsg"後,會在 debug 訊息視窗顯示 TextEdit 裡所輸入的文字,在上圖可以看到 TextEdit 的內容是"Please input something" ,但在設計介面時看不到這段文字,這是因為範例在城市開啟時修改了 TextEdit 的內容,這段程式是可以在 MainWindow::MainWindow()裡找到,內容如下
ui->textEdit->setText(tr("Pleas input something") );

TextEdit 的內容用 setText() 來修改,但中間多了個 tr() ,這是因為 Qt 的字串型別為 QString ,而常用的 char* 無法自動轉成 QString ,所以透過 tr() 來轉型。

  TextEdit 的內容又是如何提取的呢?可以在 MainWindow::onPushButtonClicked() 裡找到,內容如下
qDebug("TextEdit:%s",ui->textEdit->toPlainText().toLocal8Bit().data() );

qDebug() 在 Qt 的拖拉 widget 與事件 時就用過了,就不再介紹了,內容的部分可以看到是透過 toPlainText() 來取得型別為 QString 的內容,但 QDebug() 需要的型別為 char* ,所以還要再轉型,透過 toLocal8Bit() 轉型為 QByteArray 後,透過 QByteArray::data() 來提取 char* ,這個過程有些冗長,但我查了一下並沒找到更好的轉型。

  如果要透過設計介面來輸入 TextEdit 的顯示內容的話,可以在 TextEdit 雙擊滑鼠,會跳出編輯介面,如下圖
在設計介面輸入 TextEdit 的預設文字

如圖,在欲編輯的 TextEdit 雙擊滑鼠,就會看到編輯介面,編輯介面可以到可以調整文字的對齊與粗體,甚至可以插圖片!這是由於 TextEdit 是用 HTML 的方式來儲存內容,而這次的範例只使用文字的部分而已,以後有機會在介紹更多的用法。

參考資料

[ doc.qt.io ] QString Class

相關文章

Qt 的拖拉 widget 與事件
[ GitLab ] HelloQt

2020年5月18日 星期一

Qt 的拖拉 widget 與事件

Qt 的拖拉 widget 與事件

前言

  在之前的 Qt Creator 入門 裡建置了基本的專案,這次會拖拉 widget 並增加事件,在此做個紀錄。

內容

  由於 Qt 的每個 widget 有各自的用法,所以我在  [ GitLab ] HelloQt 放了個專案來儲存這些用法的範例,這次會使用到 (HelloQt' directory)/PushButton/Basic 裡的專案來解說,用 Qt Creator 來開啟該資料夾的專案檔(HelloQt.pro),如下圖
開啟專案

接著會看到選擇組態的畫面,如下圖
選擇組態

這裡選擇需要的組態,組態會引響使用的編譯器與作業系統的位元數,圖中所選的編譯器是 MSVC 位元數為 64 位元,選擇完後按下右下角的"Configure project",專案的組態就完成了。

  在專案的組態完成後,在 UI 的檔案雙擊滑鼠到設計介面,如下圖
雙擊 UI 的檔案到設計介面

設計介面如下圖
設計介面

區塊1裡會有 Qt 提供的 Widget 的工具箱,用滑鼠拖曳到區塊2就可以新增 widget ,整個 UI 會被記錄成一個樹狀的結構,這個結構會顯示在區塊3,而區塊4會顯示目前所選的 widget 的屬性。這次我使用 PushButton 這個 widget ,其實就是很常見的按鈕,點選區塊2裡已經拖好的 widget 看它的屬性,如下圖
按鈕的屬性

按鈕的屬性裡有一個"objectName",這個是 widget 的名稱,所有的 widget 的名稱必須是唯一的,用 C++ 控制 widget 時會用這個名稱作為變數的名稱,待會會說明,而 "text"可以調整按鈕上所顯示的文字。

  接著先同步專案,如果有更動到 widget 的名稱,不論是新增、修改或移除時請先同步一次專案,如何同步呢?如下圖
同步專案

先清除專案後,再執行 qmake ,這樣就完成同步專案。

  接著來為按鈕增加事件,回到專案的 mainwindow.cpp 檔裡實驗一下專案是否同步

確認專案是否同步

在 MainWindow::MainWindow() 裡輸入"ui->"後,看 IDE跳出來的變數裡有沒有按鈕的名稱( pushButton ),如果沒有表示專案沒同步,請做一次同步的動作。

  接著來綁定按鈕的事件,我們需要先做一個當按鈕被按下後會啟動的 function ,可以在 mainwindow.h 裡找到它,如下
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  MainWindow(QWidget *parent = nullptr);
  ~MainWindow();
private slots:
  void onPushButton_Clicked(bool checked);
private:
  Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

紅色的部分是新增的部分,要注意有個 C++ 沒有的"slots",實作的內容如下
void MainWindow::onPushButton_Clicked(bool checked)
{
  qDebug("Hello Qt");
}

qDebug() 是 Qt 裡提供顯示 debug 訊息的 function ,用法類似 printf() ,接著綁定事件的部分在
 MainWindow::MainWindow() 裡,如下
connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::onPushButton_Clicked);

參數1就是按鈕的變數,參數2是按鈕的事件,參數3是當事件發生後要被通知的對象,範例的對象就是 MainWindow 自己,參數4 則是要被喚起的 funciton ,綁訂完後執行,按下"HelloQt"按鈕,如下圖
按下按鈕後顯示訊息

在應用程式輸出的分頁裡可以看到訊息。如果要解除事件綁定可以用以下
disconnect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::onPushButton_Clicked);

參數的形式和 connect() 一樣,範例中在 MainWindow::~MainWindow() 解除綁定,但其實這個動作是沒有必要的, Qt 在 widget 被刪除時會自動解除綁定,此處只是做個解除的示範而已。

相關文章

Qt Creator 入門
[ GitLab ] HelloQt

2020年5月11日 星期一

Qt 的專案檔

Qt 的專案檔

前言

  在之前的 Qt 的拖拉 widget 與事件 中介紹了基本的應用,但專案檔並沒有介紹到,在此把學習的過程做個紀錄。

內容

  Qt 的專案檔附檔名為"pro",就拿上次在 Qt 的拖拉 widget 與事件 裡所使用的專案來介紹,在 Qt Creator 打開它會看到以下
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked 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 it uses 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

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

內容其實就是個類似 Linux 的 makefile ,但並不一樣,所以 Qt 稱為 qmake 。這裡將常用的變數做個介紹
DEFINES:
這個變數是增加前置處理的定義用的,可以自己增加,範例如下
DEFINES += MY_DEFINE1
DEFINES += MY_DEFINE2

SOURCES:
這裡新增的是要編譯的 .cpp 或 .c 檔,要新增多個檔案的話可以用"\"來區隔,而且支援相對路徑,範例如下
SOURCES += \
    main.cpp \
    ../myLib.cpp

HEADERS:
這個變數類似 SOURCES ,但新增的是 .h 檔,一樣用"\"來區隔來新增多個檔案,一樣支援相對路徑。

FORMS:
這個新增的是 UI 的設計檔。

INCLUDEPATH:
這個變數可以新增 include 的路徑,支援相對路徑,範例如下
INCLUDEPATH += ../include
INCLUDEPATH += ../../myLib

LIBS:
這個變數用來新增 .lib 檔,用法有點特別,因為要連命令參數一起輸入,而且可以新增路徑,範例如下
LIBS += -L../lib -lTestLib

"-L"後直接輸入要新增的路徑,無須空白,"-l"後也不須空白,且不加附檔名。

執行階段程式庫:
QMAKE_CFLAGS_DEBUG += /MTd
QMAKE_CXXFLAGS_DEBUG += /MTd

後面的參數有 4 種,MDd(Multi-threaded DLL Debug) 、 MTd(Multi-threaded Debug) 、 MD(Multi-threaded DLL) 與 MT(Multi-threaded),修改完後須重建才能生效。
 
  接著介紹一下實用的內建 function ,
message():
這個是拿來列印訊息用的,具體的用法如下
message("Hello qmake")
message( $${SOURCES} )

第一個範例會直接列印"Hello qmake",第二個範例是列印變數的內容,列印"SOURCES"這個變數的內容, 這個要注意的是格式為 $${ (變數名稱) } 。

CONFIG():
這個 function 用來對 Debug 與 Release 版做出差異,並注意 function 名稱為大寫,範例如下
CONFIG( debug , debug|release){
  DEFINES += BUILD_DEBUG
}
CONFIG( release , debug|release){
  DEFINES += BUILD_RELEASE
}

contains():
這個 function 用來檢查變數內是否含有某些內容,最基本的用法是用來判斷不同平台建置時給予不同的行為,範例如下
//Build for Windows
contains( QMAKE_HOST.os , Windows ){
  DEFINES += BUILD_WINDOWS
}
//Build for Linux
contains( QMAKE_HOST.os , Linux ){
  DEFINES += BUILD_LINUX
}
//Build for 64 bit
contains( QT_ARCH , x86_64 ){
  DEFINES += BUILD_OS64
}

function 的第一個變數是被尋找的變數,第二個則是要尋找的內容,要注意都有大小寫得分別,Qt 的預設變數可以在 [ Qt ] Variables 裡查詢。

  自訂變數的用法如下
MY_VAR = test
message( $${MY_VAR} )

變數的定義與 makefile 一樣,但如果要指名內容時要注意 qmake 特有的格式。

  本篇記錄了目前用的到的變數與 function ,如果有新的變數也就直接加在這篇,方便日後查詢。

參考資料

[ Qt ] Variables
[ Qt ] Test Functions

相關文章

Qt 的拖拉 widget 與事件

2020年5月4日 星期一

Qt Creator 入門

Qt Creator 入門

前言

  在上一篇 在 Ubuntu 上安裝 Qt 裡有稍微提到 Qt Creator ,它是一個 Qt 自帶的 IDE ,本來打算用 Visial studio 來使用 Qt ,但發現這樣不能跨平台,所以還是決定來學習 Qt Creator ,在此把學習的過程過個紀錄。

內容

  以下範例已將介面風格改為"Dark",可以參照 在 Ubuntu 上安裝 Qt 來修改。開啟 Qt Creator 後,創建新的專案,如下圖
創建新的專案

接著會到下圖
使用範本創建專案

選取"Qt Widgets Application"後,按下"Choose"按鍵,接著來到下圖
決定專案的名稱

這裡會決定專案的名稱與專案創建的位址,決定後"下一步"
選擇建置系統


這裡選擇預設的"qmake"後就下一步
設定預設的視窗類別名稱

這裡會設定視窗的類別名稱,照預設值直接"下一步"
設定語言

這裡設定語言,照預設值直接"下一步"
選擇需要的組態

這裡要選擇需要的組態,預設會全選,這裡我只選擇"MSVC2017 64bit",接著"下一步"
檢視創建專案的狀況

最後檢視專案創建的狀況後按下"完成",完成後看到下圖
完成後的專案

按下"F5"來 Run ,可以看到下圖
執行結果

執行的結果就是個空視窗,如果要選擇 Debug 或 Release 版的建置的話,可以在 IDE 的左下做選擇
建置組態的選擇

  這次介紹了如何創建專案,但創建好的專案只有一個空視窗,下次會介紹如何增加 Widget 與事件的綁定。

相關文章

在 Ubuntu 上安裝 Qt
Qt 入門

2020年4月27日 星期一

關於 Virtual function 的成員存取控制

關於 Virtual function 的成員存取控制

前言

  最近使用 Qt 的時候發現到 Virtual function 在繼承後可以與基底類別的存取區域不一樣,這才發現到我對 Virtual function 的觀念有問題,在此把學習的過程做個紀錄。

內容

  我原本認為的 Virtaul function 如下
class CA
{
public:
  CA(){}
  virtual void Call(){
    printf("CA::Call()\n");
  }
};
//
class CB:public CA
{
public:
  CB(){}
  virtual void Call(){
    printf("CB::Call()\n");
  }
};
//
CB b;
b.Call();//CB::Call()

範例中的 CB::Call() 與 CA::Call() 的存取區域都必需是"public",但這個觀念其是錯的! CB::Call() 的存取區域可以自己定義所屬的存取區域,如下範例
class CA
{
public:
  CA(){}
  virtual void Call(){
    printf("CA::Call()\n");
  }
};
//
class CB:public CA
{
public:
  CB(){}
private:
  virtual void Call(){
    printf("CB::Call()\n");
  }
};
//
CB b;
b.Call();//Compile error!
CA* pA = (CA*)&b;
pA->Call();//CA::Call();

在範例中這次把 CB::Call() 改成"private",所以"b.Call()"會變成 Compile error ,但由於 CA::Call() 依舊是"public",所以透過轉型還是可以喚起 CA::Call() 。

2020年4月20日 星期一

在 Raspberry Pi 4 上安裝 Ubuntu

在 Raspberry Pi 4 上安裝 Ubuntu

前言

  Raspberry Pi 4 的官方作業系統為 Raspbian ,用了一陣子沒什麼問題,但發現他遽然是 32 位元,而且官方不提供 64 位元,實在無法理解官方的行為,因為 Raspberry Pi 4 的 CPU 是 64 位元,為什麼不提供 64 位元版本的 Raspbian ,但官方的 Ubuntu 卻提供 64 位元,所以我決定放棄使用 Raspbian ,改用 Ubuntu ,由於安裝的過程有些特別,在此做個紀錄。

內容

  首先到官方下載 Ubuntu Server 的官方映像檔,如下圖
下載 Ubuntu Server 的映像檔

這裡下載的是 Ubuntu 18 ,單純只是認為 18 比較穩定,要安裝 Ubuntu 19 也是可以的。
利用 balenaEtcher 燒錄到記憶卡後,先別急著放到 Raspberry Pi 4 上,先瀏覽記憶卡上面的檔案,如下圖
修改 usercfg.txt

在記憶卡裡會有個 usercfg.txt ,在裡面增加以下內容在最後
dtparam=audio=on

這個內容的目的是要開啟音效卡,如果不做這個步驟的話,安裝完後會發現沒聲音。

  接著,把記憶卡放入 Raspberry Pi 4  後開機,帳號/密碼為ubuntu/ubuntu,第一次登入會要求改密碼。由於環境的關係我需要設定無線網路來連線,如果是直接接網路線的話以下步驟可以省略。新增  /etc/netplan/wireless.yaml ,內容如下
network:
  version: 2
  wifis:
    wlan0:
      dhcp4: yes
      dhcp6: no
      access-points:
        "<your network ESSID>":
          password: "<your wifi password>"


要注意的是縮排的空白與冒號之後如果有內容一定要用空白隔開,紅字的部分是要設定無線網路的 SSID 與密碼,設定完候用以下命令啟動無線網路
sudo netplan apply

  在網路備妥後執行以下命令進行更新
sudo apt update
sudo apt full-upgrade

更新完後透過以下命令安裝桌面
sudo apt install ubuntu-desktop

這個安裝大概要一個小時,安裝完後如果有透過  /etc/netplan/wireless.yaml 來設定無線網路的話,請執行以下命令
sudo rm /etc/netplan/wireless.yaml

執行命令移除該檔,如果不移除的話,Ubuntu 的無線網路會抓不到。

  最後,重開機後就可以進入 Ubuntu 了。

參考資料

[ Edge ] 設定 Raspberry Pi 4 中的 Ubuntu Server 19.10 無線網路

2020年4月13日 星期一

在 Linux 上安裝 VNC Server

在 Linux 上安裝 VNC Server

前言

  最近發現同時使用 Raspberry Pi 4 與 PC 在同一個螢幕的時候會發生一些問題,但又希望兩者可以同時使用,所以想透過 VNC 服務來控制 Raspberry Pi 4 ,透過安裝 VNC Server 可以解決這個問題,在此把學習的過程做個紀錄。

內容

  在我的螢幕 Viewsonic VX2433wm 如果同時有兩個輸入的話,一個顯示會是 HDMI PC 模式,另一個會被強制轉成 HDMI AV模式,HDMI PC 模式就如同字面上的意思是給 PC ,另一個 HDMI AV 是給電視用的,雖然可以透過螢幕功能鍵強制調回來,但每次切換到 HDMI AV 模式的輸入都要再透過螢幕功能鍵調一次,如果不調整,畫面會被切邊。

  這個問題是螢幕的預設行為沒辦法透過軟體來修改,所以我想到用 VNC 的方式來解決,在 Raspberry Pi 4 上安裝 VNC Server 可以透過以下命令
sudo apt-get install tightvncserver

安裝完後可以透過以下命令來啟動
vncserver

第一次啟動時會要求設定連線密碼,可以依據喜好設定連線密碼, view-only 密碼可以不用設定。設定完後服務就啟動了,可以透過喜好的 VNC client 端來進行連線,預設的 Port 是 5901。

  雖然 VNC 可以解決切換螢幕的問題,但還是要"手動"來開啟服務,如果能開機就自動開啟服務的話就可以完全不切換螢幕來操控 Raspberry Pi 4 ,如何設定開機就啟動呢?新增以下位址新增檔案
/etc/init.d/tightvncserver

檔案的內容如下
#!/bin/bash
### BEGIN INIT INFO
# Provides:          tightvncserver
# Required-Start:    $syslog
# Required-Stop:     $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: vnc server
# Description:
### END INIT INFO

export USER='pi'

eval cd ~$USER

# Check the state of the command - this'll either be start or stop 
case "$1" in
  start)
    # if it's start, then start vncserver using the details below
    su $USER -c '/usr/bin/vncserver :1 -geometry 800x600 -depth 16 -pixelformat rgb565'
    echo "Starting vncserver for $USER "
    ;;
  stop)
    # if it's stop, then just kill the process
    pkill Xtightvnc
    echo "vncserver stopped"
    ;;
  *)
    echo "Usage: /etc/init.d/tightvncserver {start|stop}"
    exit 1
    ;;
esac
exit 0

內容中的 USER 是指作業系統的使用者,請依據狀況修改, geometry 的參數可設定解析度。
接著透過以下命令修改該檔權限
sudo chmod 755 /etc/init.d/tightvncserver

接著透過以下命令設定開機時啟動
sudo update-rc.d tightvncserver defaults


參考資料

[ Raspberry Pi台灣樹莓派 ] [基礎] 以 VNC 和 Raspberry Pi 連線