Qt開發經驗小技巧166-170

語言: CN / TW / HK
  1. 有時候需要暫時停止某個控制元件發射訊號(比如下拉框combobox新增資料的時候會觸發當前元素改變訊號),有多種處理,推薦用 blockSignals 方法。
//方法1:先 disconnect 掉訊號,處理好以後再 connect 訊號,缺點很明顯,很傻,如果訊號很多,每個型號都要這麼來一次。
disconnect(ui->cbox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cbox_currentIndexChanged(int)));
for (int i = 0; i <= 100; i++) {
    ui->cbox->addItem(QString::number(i));
}
connect(ui->cbox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cbox_currentIndexChanged(int)));

//方法2:先呼叫 blockSignals(true) 阻塞訊號,處理號以後再呼叫 blockSignals(false) 恢復所有訊號。
//如果需要指定某個訊號進行斷開那就只能用 disconnect 來處理。
ui->cbox->blockSignals(true);
for (int i = 0; i <= 100; i++) {
    ui->cbox->addItem(QString::number(i));
}
ui->cbox->blockSignals(false);
  1. 專案程式碼檔案數量如果很多的話,全部包含在pro專案檔案中會顯得非常凌亂,甚至滾動條都要拉好久,有兩個方法可以處理的更好,推薦方法2。
//方法1:pro檔案直接全部引入,而不是每個都新增一次,省心省力。
HEADERS += *.h
SOURCES += *.cpp

//方法2:分模組資料夾存放,不同模組用pri包含程式碼檔案,比如介面可以放在ui資料夾,下面搞個ui.pri,然後pro專案檔案只需要引入這個pri檔案即可。
include($$PWD/ui/ui.pri)
//還可以加上一句包含路徑這樣可以省去在使用程式碼的時候不用寫資料夾
INCLUDEPATH += $$PWD/ui
//加上上面這行,在使用標頭檔案的時候可以直接 include "form.h",沒有加則需要 include "ui/form.h"。
  1. 在網路通訊中,無論是tcp客戶端還是udp客戶端,其實都是可以繫結網絡卡IP和埠的,很多人只知道服務端可以指定網絡卡監聽埠。客戶端如果沒有繫結通訊埠則由客戶端所在的作業系統隨機遞增分配的,這裡為啥這麼強調,因為無數人,甚至不乏一些多年經驗的新時代農名工,以為客戶端的埠是服務端分配的,因為他們看到在服務端建立連線後可以打印出不同的埠號。網路通訊的雙方自己決定自己要用什麼埠,伺服器端只能決定自己監聽的是哪個埠,不能決定客戶端的埠,同理客戶端也只能決定自己的埠。埠隨機分配一般是按照順序遞增的,比如先是45110埠,連線重新建立就用45111埠,只要埠沒被佔用就這樣遞增下去,所以很多人會問是否可以複用一些埠,不然埠一直這樣頻繁的分配下去不妥,甚至有些特定的場景和需求也是會要求客戶端繫結網絡卡和埠來和伺服器通訊的。
//tcp客戶端
QTcpSocket *socket = new QTcpSocket(this);
//斷開所有連線和操作
socket->abort();
//繫結網絡卡和埠
socket->bind(QHostAddress("192.168.1.2"), 6005);
//連線伺服器
socket->connectToHost("192.168.1.3", 6000);

//列印通訊用的本地繫結地址和埠
qDebug() << socket->localAddress() << socket->localPort();
//列印通訊伺服器對方的地址和埠
qDebug() << socket->peerAddress() << socket->peerPort() << socket->peerName();

//udp客戶端
QUdpSocket *socket = new QUdpSocket(this);
//繫結網絡卡和埠,沒有繫結過才需要繫結
//採用埠是否一樣來判斷是為了方便可以直接動態繫結切換埠
if (socket->localPort() != 6005) {
    socket->abort();
    socket->bind(QHostAddress("192.168.1.2"), 6005);
}
//指定地址和埠傳送資料
socket->writeDatagram(buffer, QHostAddress("192.168.1.3"), 6000);

//上面是Qt5可以使用bind,Qt4中的QTcpSocket的對應介面是protected的沒法直接使用,需要繼承類重新實現把介面放出來。
//Qt4中的QUdpSocket有bind函式是開放的,奇怪了,為何Qt4中獨獨QTcpSocket不開放。
TcpSocket *socket = new TcpSocket(this);
socket->setLocalAddress(QHostAddress("192.168.1.2"));
socket->setLocalPort(6005);
  1. 關於網路通訊,tcp和udp是兩種不同的底層的網路通訊協議,兩者監聽和通訊的埠互不相干的,不同的協議或者不同的網絡卡IP地址可以用相同的埠。之前有個人說他的電腦居然可以監聽一樣的埠進行通訊,顛覆了他以前的認知,書上說的明明是不可以相同埠的,後面遠端一看原來選擇的不同的網絡卡IP地址,當然可以的咯。
  • tcp對網絡卡1監聽了埠6000,還可以對網絡卡2監聽埠6000。
  • tcp對網絡卡1監聽了埠6000,udp對網絡卡1還可以繼續監聽埠6000。
  • tcp對網絡卡1監聽了埠6000,在網絡卡1上其他tcp只能監聽6000以外的埠。
  • udp協議也是上面的邏輯。
  1. 開源的圖表控制元件QCustomPlot很經典,在曲線資料展示這塊效能彪悍,總結了一些容易忽略的經驗要點。
  • 可以將XY軸對調,然後形成橫向的效果,無論是曲線圖還是柱狀圖,分組圖、堆積圖等,都支援這個特性。
  • 不需要的提示圖例可以呼叫 legend->removeItem 進行移除。
  • 兩條曲線可以呼叫 setChannelFillGraph 設定合併為一個面積區域。
  • 可以關閉抗鋸齒 setAntialiased 加快繪製速度。
  • 可以設定不同的線條樣式(setLineStyle)、資料樣式(setScatterStyle)。
  • 座標軸的箭頭樣式可更換 setUpperEnding。
  • 可以用 QCPBarsGroup 實現柱狀分組圖,這個類在官方demo中沒有,所以非常容易忽略。
//對調XY軸,在最前面設定
QCPAxis *yAxis = customPlot->yAxis;
QCPAxis *xAxis = customPlot->xAxis;
customPlot->xAxis = yAxis;
customPlot->yAxis = xAxis;

//移除圖例
customPlot->legend->removeItem(1);

//合併兩個曲線畫布形成封閉區域
customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));

//關閉抗鋸齒以及設定拖動的時候不啟用抗鋸齒
customPlot->graph()->setAntialiased(false);
customPlot->setNoAntialiasingOnDrag(true);

//多種設定資料的方法
customPlot->graph(0)->setData();
customPlot->graph(0)->data()->set();

//設定不同的線條樣式、資料樣式
customPlot->graph()->setLineStyle(QCPGraph::lsLine);
customPlot->graph()->setScatterStyle(QCPScatterStyle::ssDot);
customPlot->graph()->setScatterStyle(QCPScatterStyle(shapes.at(i), 10));

//還可以設定為圖片或者自定義形狀
customPlot->graph()->setScatterStyle(QCPScatterStyle(QPixmap("./sun.png")));
QPainterPath customScatterPath;
for (int i = 0; i < 3; ++i) {
    customScatterPath.cubicTo(qCos(2 * M_PI * i / 3.0) * 9, qSin(2 * M_PI * i / 3.0) * 9, qCos(2 * M_PI * (i + 0.9) / 3.0) * 9, qSin(2 * M_PI * (i + 0.9) / 3.0) * 9, 0, 0);
}
customPlot->graph()->setScatterStyle(QCPScatterStyle(customScatterPath, QPen(Qt::black, 0), QColor(40, 70, 255, 50), 10));

//更換座標軸的箭頭樣式
customPlot->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
customPlot->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);

//設定背景圖片
customPlot->axisRect()->setBackground(QPixmap("./solarpanels.jpg"));
//畫布也可以設定背景圖片
customPlot->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg")));
//整體可以設定填充顏色或者圖片
customPlot->setBackground(QBrush(gradient));
//設定零點線條顏色
customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen);
//控制是否滑鼠滾輪縮放拖動等互動形式
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

//柱狀分組圖
QCPBarsGroup *group = new QCPBarsGroup(customPlot);
QList<QCPBars*> bars;
bars << fossil << nuclear << regen;
foreach (QCPBars *bar, bars) {
    //設定柱狀圖的寬度大小
    bar->setWidth(bar->width() / bars.size());
    group->append(bar);
}
//設定分組之間的間隔
group->setSpacing(2);