2020年10月27日 星期二

用 JavaScript 控制 Widget

 用 JavaScript 控制 Widget 

前言

  在之前的 在 Qt 環境使用 JavaScript 裡基本的使用 JavaScript ,這次要用 JavaScript 來控制 Widget 

,在此把學習的過程做個紀錄。


內容

  先到 [ GitLab ] HelloQt 下載範例,這次應用的專案路徑
(HelloQt' directory)/JSEngine/UseWidget,執行結果如下

範例的執行結果

上方會有預設的程式碼,按下右下的"Run"可以執行,執行完後可以看到左下的 PushButton 會改變顯示的字,按下後會列印偵錯訊息。


  看到 MainWindow::MainWindow() ,程式碼如下

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
  ui->setupUi(this);
  //
  connect( ui->runButton , &QPushButton::clicked , this , &MainWindow::onRunButtonClicked );
  //
  m_cJSEngine.installExtensions( QJSEngine::ConsoleExtension );
  //
  m_cJSEngine.globalObject().setProperty( "myButton" , m_cJSEngine.newQObject( ui->myButton ) );
  //Default code
  ui->textEdit->setText( tr(
    "myButton.text = 'Hello myButton.';\n"\
    "myButton.clicked.connect( function( clicked ){\n"\
    "  console.log('myButton clicked!');\n"\
    "});"
  ) );
}


程式開頭綁定"Run"的事件與初始化 QJSEngine ,接著就是在 JavaScript cript 產生對應的變數,變數名為"myButton",要產生這樣的變數要透過 QJSEngine::newQObject() 來產生,參數的型態是 QObject ,由於大部份的 Widget 都繼承 QObject ,所以直接輸入就可以,不需要轉型,變數會透過 QJSEngine::globalObject() 來取得 JavaScript 的 root object ,接著透過 QJSValue::setProperty() 來新增變數就完成新增 Widget 的變數。最後的部分是新增 JavaScript 的預設程式碼 ,程式碼直接透過"myButton"來控制,範例透過"text"來控制按鈕顯示的字串,接著是事件的綁定,跟 C++ 不太一樣,要透過 (事件名稱).connect() 來綁定事件,這裡單純就顯示偵錯訊息。


  這次的範例只是簡單的應用,重點放在如何產生變數讓 JavaScript 來控制,至於要如何控制的部分目前沒找到可以查找可以用的變數(像是"text"),似乎只能用猜的(跟在設計介面的參數同名),以後有機會再多多研究。


參考資料

[ doc.qt.io ] QJSEngine Class


相關文章與資料

[ GitLab ] HelloQt

在 Qt 環境使用 JavaScript

2020年10月20日 星期二

在 Python 中使用 Qt

在 Python 中使用 Qt

前言

  Qt 不只提供 C++ 使用,也提供 Python 來使用,不過使用需要一些前置作業,在此把學習的過程做個紀錄。


內容

  在 Python 裡使用 Qt 必須先安裝 Qt 相關套件,這些套件會安裝在 Python 的套件管理,透過以下命令來安裝

pip3 install PyQt5
pip3 install pyqt5-tools


pip3 是 Python 自帶的套件管理,在 Windows 時並不能直接執行,這個命令會在"(安裝目錄)/Scripts",安裝完後到 [ GitLab ] HelloQt 下載範例,這次應用的專案路徑

(HelloQt' directory)/PyQt/Basic ,這次的專案檔的附檔名為".pyproject",和 C++ 的".pro"不一樣。
如果要自己創建一個專案的話請依下圖操作
創建專案的操作


接著為專案命名,如下圖
專案的命名


這裡取好專案名後就下一步到下一步驟
設定預設專案的類別

這裡需要設定設定預設專案的名稱,這個名稱會關係到專案檔的名稱,這個預設的類別會用不到,所以不用在乎類別的名稱,要在乎只有專案名稱,接著到下一步驟
完成創建專案精靈

在完成專案後需要在創建一個名為"MainWindow"的類別,創建的步驟如下
創建類別精靈


依上圖操作後到下一步
設定類別名稱與相關設定

步驟2選擇"PyQt5",繼承類別選擇"QMainWindow",接著到下一步驟
完成創建類別精靈

在創建完類別後還要在創建一個".ui"的設計檔,有這個設計檔可以在 Qt Creator 裡直接編輯 UI,就像 C++ 使用 Qt 一樣,操作如下
創建設計檔的精靈

操作完到下一步
選擇設計檔的樣本

選擇"MainWindow"後就到下一步
設定設計檔的名稱

命好設計檔檔名後到下一步
完成新增設計檔精靈


完成後在專案的狀況如下
完成上述的新增後專案的狀況

 
雙擊畫面的"mainwindow.ui"可以直接到設計介面修改設計,範例的設計如下
範例的設計

範例只新增一個 PushButton ,接著看到"mainwindow.py",程式碼如下
# This Python file uses the following encoding: utf-8
from PyQt5 import QtCore , QtWidgets , uic
from PyQt5.QtCore import pyqtSlot , qDebug

class MainWindow( QtWidgets.QMainWindow ):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        super().__init__()
        uic.loadUi("mainwindow.ui", self)
        #BindEvent
        self.pushButton.clicked.connect( self.onPushButtonClicked )
    @pyqtSlot()
    def onPushButtonClicked(self):
        qDebug( 'Hello' )

在建構式裡會透過"uic.load()"來讀取設計檔,接著透過"connect()"來綁定事件,在要綁定的 Function 前要加"@pyqtSlot()",這個地方跟 C++ 的格式很像,事件的內容就單純列應偵錯訊息,接著看到"pyqt_basic.py",程式碼如下
# This Python file uses the following encoding: utf-8
import sys
from PyQt5 import QtWidgets
from MainWindow import MainWindow
#Follow code add by wizard,but it will not used
#class PyQt_Basic:
#    def __init__(self):
#        pass  # call __init__(self) of the custom base class here

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    #Follow code add by wizard,but it will not used
    # window = PyQt_Basic()
    ## window.show()
    #
    mainWin = MainWindow()
    mainWin.show()
    sys.exit(app.exec_())

開頭有個類別"PyQt_Basic",這是由精靈產生的類別,但由於用不到救註解掉。在主程式的開頭有精靈產生程式碼,由於用不到一樣註解掉,接著將"MainWindow"建出來,並喚起"show()"來顯示即可。

2020年10月13日 星期二

處理 TreeView 的 Drag 與 Drop

處理 TreeView 的 Drag 與 Drop

前言

  在先前的 TreeView 的基本應用 與 在 TreeView 使用右鍵選單 裡介紹了 TreeView 的常用功能,但少提到了一個常用的功能,就是 Drag 與 Drop ,節點的資料能夠直接在 Widget 上編輯是一個非常方便的功能,在此把學習的過程做個紀錄。


內容

  先到 [ GitLab ] HelloQt 下載範例,這次應用的專案路徑
(HelloQt' directory)/TreeView/DragAndDrop,執行結果如下

範例的執行結果


範例會給予預設的節點,並可以在 Widget 上透過拖曳(Drag)來編輯節點。這次雖然只是處理 Drag 與 Drop 的事件,但不幸的是 Qt 並沒有提供透過 Connect 事件的方式來處理,這次使用的方式是透過"繼承"的方式來處理 Drag 與 Drop 的事件,這種透過繼承的方式可以參考 特製 Widget ,兩者的差別是該篇繼承的是 QWidget ,而本篇繼承的是 QTreeView,在新增 Class 的精靈介面可以參考下圖

新增繼承 QTreeView 的類別


精靈介面加完後的宣告程式碼並不完整,像是 include 的相關程式碼並不會自動被加入,可以比對範例的宣告來看差異是什麼,接著就直接看到宣告的部分,程式碼如下

#include <QTreeView>
#include <QVector>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDragLeaveEvent>
#include <QDropEvent>
#include <QStandardItemModel>
class MyTreeView : public QTreeView
{
  Q_OBJECT
public:
  explicit MyTreeView(QWidget *parent = nullptr);
  //
  void CustomSetModel(QStandardItemModel* pModel);
  void HideColumnByList();
protected:
  void dragEnterEvent(QDragEnterEvent *event) override;
  void dragMoveEvent(QDragMoveEvent *event) override;
  void dragLeaveEvent(QDragLeaveEvent *event) override;
  void dropEvent(QDropEvent *event) override;
//
private:
  QVector<int> m_cHideColumnList;
};

處理 Drag 與 Drop 的事件有 dragEngterEvent() 、 dragMoveEvent() 、 dragLeaveEvent() 與 dropEvent() , CustomSetModel() 與 HideColumnByList() 是自己定義的 Function ,待會兒在實作的部分會進一步說明它們的功能,接下來就看到實作的部分,先看到自訂的 Function ,如下

void MyTreeView::CustomSetModel(QStandardItemModel *pModel)
{
  this->setModel( pModel );
  HideColumnByList();
}

void MyTreeView::HideColumnByList()
{
  for( auto i = 0 ; i < m_cHideColumnList.size() ; i++ )
  {
    this->hideColumn( m_cHideColumnList[i] );
  }
}


HideColumnByList() 的部分會依據成員"m_cHideColumn"裡所存的要隱藏的 Column 來喚起 QTreeView::hideColumn() ,為什麼要提供這個 Function 呢?因為 QTreeView 在處理某些事件後會把隱藏的 Column 再次變成顯示 ,所以需要這個 Function 來恢復我們期望隱藏的 Column ,CustomSetModel() 會在喚起 QTreeView::setModel() 後喚起 HideColumnByList() 隱藏指定的 Column ,接著看到主程式的 MainWindow::MainWindow() ,如下

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
  ui->setupUi(this);
  //
  //
  QStandardItemModel* pModel = new QStandardItemModel( ui->treeView );
  pModel->setHorizontalHeaderLabels( QStringList()<<QStringLiteral( "Name" )<<QStringLiteral( "Comment" ) );
  QStandardItem* pItemRoot1 = new QStandardItem( QStringLiteral( "root1" ) );
  //first
  pItemRoot1->setChild( 0 , 0 , new QStandardItem( QStringLiteral( "child" ) ) );
  pItemRoot1->setChild( 0 , 1 , new QStandardItem( QStringLiteral( "I'm child" ) ) );
  //second
  pItemRoot1->setChild( 1 , 0 , new QStandardItem( QStringLiteral( "child1" ) ) );
  //
  pModel->appendRow( pItemRoot1 );

  //
  QStandardItem* pItemRoot2 = new QStandardItem( QStringLiteral( "root2" ) );
  pItemRoot2->setChild( 0 , 0 , new QStandardItem( QStringLiteral( "child" ) ) );
  pItemRoot2->setChild( 0 , 1 , new QStandardItem( QStringLiteral( "I'm child" ) ) );
  //
  pModel->appendRow( pItemRoot2 );

  //
  ui->treeView->CustomSetModel( pModel );
}


前面就單純的加入預設節點不再贅述,可以參考 TreeView 的基本應用 ,重點在最後一行使用的是 MyTreeView::CustomSetModel() 而非 QTreeView::setModel() ,這是因為這樣可以隱藏指定的 Column 。接著看到 MyTreeView::MyTreeView() ,如下

MyTreeView::MyTreeView(QWidget *parent):
  QTreeView(parent)
{
  this->setDragEnabled( true );
  this->setDragDropMode( QAbstractItemView::InternalMove );
  //
  this->setHeaderHidden( true );
  this->setSelectionMode( QAbstractItemView::ExtendedSelection );
  //
  m_cHideColumnList.push_back( 1 );
}


由於 QTreeView 預設是把 Drag 與 Drop 的功能關閉,所以透過 setDragEnabled() 與 setDragDropMode() 來開啟,接著透過 setHeaderHidden() 來關閉上方的欄位,然後透過 setSelectionMode() 來開啟可多選,最後在成員"m_cHideColumnList"加入要隱藏的 Column ,最後就直接看到 Drag 和 Drop 事件的實作,如下

void MyTreeView::dragEnterEvent(QDragEnterEvent *event)
{
  qDebug( "dragEnterEvent" );
  //
  QTreeView::dragEnterEvent( event );
}

void MyTreeView::dragMoveEvent(QDragMoveEvent *event)
{
  qDebug( "dragMoveEvent" );
  //
  QTreeView::dragMoveEvent( event );
}

void MyTreeView::dragLeaveEvent(QDragLeaveEvent *event)
{
  qDebug( "dragLeaveEvent" );
  //
  QTreeView::dragLeaveEvent( event );
}
void MyTreeView::dropEvent(QDropEvent *event)
{
  qDebug( "dropEvent" );
  QModelIndex index = indexAt(event->pos() );
  qDebug( "dropEvent node:%s" , index.data().toString().toLocal8Bit().data() );

  DropIndicatorPosition dropIndicator = dropIndicatorPosition();
  switch ( dropIndicator ) {
  case DropIndicatorPosition::OnItem:
    qDebug("onItem");
    break;
  case DropIndicatorPosition::AboveItem:
    qDebug("AboveItem");
    break;
  case DropIndicatorPosition::BelowItem:
    qDebug("BelowItem");
    break;
  defadefault:
    qDebug("Unknown DropIndicatorPosition!");
    break;
  }
  //
  for( auto i = 0 ; i < this->selectedIndexes().size() ; i++ )
  {
    if( this->selectedIndexes().at(i).column() != 0 )
      continue;
    //
    qDebug( "source node:%s" , this->selectedIndexes().at(i).data().toString().toLocal8Bit().data() );
  }
  //
  HideColumnByList();
  //
  QTreeView::dropEvent(event);
}


 dragEngterEvent() 、 dragMoveEvent() 與 dragLeaveEvent() 由於這次用不到,所以只列印偵錯訊息,這樣方便了解事件觸發的時機,所以範例重點會放在 dropEvent() ,這裡說明一下,範例希望得到的資訊是把那些節點丟到某個節點 的相關資訊,但不幸的是 QDropEvent 裡所提供的資訊並不完整,必須搭配 QTreeView 的相關 Funciton 才能達到目的,要得到被丟到的節點資訊 可以透過 QTreeEvent::Pos() 與 QTreeView::indexAt() 來的到資料,但丟到某個節點的想法有點瑕疵,如果丟到 root 那這個節點會是無效的嗎?答案是不會,因為 Qt 的這裡設計得不是很直覺, QTreeView 提供 DropIndicatorPoisition 來說明"丟到這個節點的裡面"、"丟到這個節點的前面"與"丟到這個節點的後面",來解說要丟到哪個節點的詳細狀況,接著來接解決如何取得要丟的節點,這個資訊會存在 QTreeView::selectedIndexs() ,其實就是最後選擇的節點,因為之前有開啟多選,所以這裡可能會有多個節點,最後透過 HideColumnByList() 來隱藏指定的 Column ,最後的 QTreeView::dropEvent() 是執行 DropEvent 的預設行為,但這個預設行為並不包含把節點的資料變成丟完後的資料,整個 dropEvent() 會發生在資料被修改完之前,所以要注意這個事件是發生在丟完資料之前,也就是 QTreeView 的樹節點資料都還沒改變時。


參考資料

[ doc.qt.io ] QTreeView Class


相關文章與資料

[ GitLab ] HelloQt

特製 Widget

TreeView 的基本應用

在 TreeView 使用右鍵選單

2020年10月6日 星期二

在 Qt 方便顯示偵錯訊息的作法

 在 Qt 方便顯示偵錯訊息的作法 

前言

  在 [ GitLab ] HelloQt 的範例裡常常會用 qDebug() 來顯示,用起來類似 printf() ,但由於 Qt 有一些類別是自己定義的,像是 QString ,如果使用 qDebug() 來顯示會讓程式看起來很長,這次找到有變短的方法,在此做個紀錄。


內容

  在 [ GitLab ] HelloQt 的範例要顯示 QString 的內容在偵錯訊息會使用如下

QString str( "I'm a string" );
qDebug( "%s" , str.toLocal8Bit().data() );


可以看到要顯示 QString 需要取得字串指標時需要打很長的字,是否有比較簡單的方法呢?答案是肯定的,可以用以下

#include <QDebug>
//
QString str( "I'm a string" );
qDebug() << str;


這個方法需要 include <QDebug> ,接著就可以像 std::cout 一樣來顯示訊息,用起來相當方便。

 

參考資料

[ doc.qt.io ] QDebug Class


相關文章與資料

[ GitLab ] HelloQt