想了很久,我決定還是先從signal和slot(信號槽)開始講起。 signal和slot大家一定不陌生,先看一段示例(選自文檔): 使用signal和slot的類必須包含Q_OBJECT巨集,聲明slot需要使用public/private/protected slots:,signal則需要sig ...
想了很久,我決定還是先從signal和slot(信號槽)開始講起。
signal和slot大家一定不陌生,先看一段示例(選自文檔):
1 class Counter : public QObject 2 { 3 Q_OBJECT 4 5 public: 6 Counter() { m_value = 0; } 7 8 int value() const { return m_value; } 9 10 public slots: 11 void setValue(int value); 12 13 signals: 14 void valueChanged(int newValue); 15 16 private: 17 int m_value; 18 };
使用signal和slot的類必須包含Q_OBJECT巨集,聲明slot需要使用public/private/protected slots:,signal則需要signals:。
這些其實都是巨集,它們會指示moc做相應的代碼生成,這樣Qt程式才可以發送信號,並讓slot與signal相連接。
可是golang並沒有巨集,那麼在qt里我們要怎麼做呢?
信號----Signal
1. 信號----signal的定義
想要自定義signals,我們需要用到golang的一個簡單特性--struct tags。
tags被廣泛的用於golang的世界里,從標準庫encoding/json到廣泛使用的orm(xorm,gorm),tags的身影無處不在。tags之所以應用廣泛是因為它可以被reflect取到,
依賴於強大的reflect包,可以通過tags來實現各種各樣的功能,其中就包括Qt的moc擴展。
下麵我們看一下一個帶有兩個自定義signal的自定義組件:
1 import "github.com/therecipe/qt/core" 2 3 type MyWidget struct { 4 core.QObject 5 6 _ func() `signal:"dataChanged"` 7 _ func(int) `signal:"valueChanged"` 8 }
首先看到第四行,
core.QObject
所有需要自定義slot和signal的類都必須是core.QObject的派生類型,如果不是直接繼承自QObject,那麼直接繼承的類型必須要直接或間接的繼承自QObject。
同時要註意,不要用*core.QObject的形式,這會導致qtmoc忽略這個類,最終不能處理moc擴展引發問題。
_ func() `signal:"dataChanged"` _ func(int) `signal:"valueChanged"`
我們定義了兩個signal,第一個不帶任何參數,第二個帶有一個int類型參數。
qtmoc會把tags的內容用strings.Title做處理,也就是說dataChanged會變成DataChanged,這就是我們定義的信號的名字。
接著qtmoc會根據這個名字以及tags所在成員的類型生成自定義控制項類的三個成員方法:Connect[signal name],Disconnect[signal name],[signal name],
在本例中就是:ConnectDataChanged,DisconnectDataChanged和Datachanged。
2. 信號----signal的連接
想要和signal連接,需要用到前面提到的Connect[signal name]函數。
qtmoc會根據signal的類型來生成Connect函數,這裡的ConnectDataChanged的原型就是func ConnectDataChanged( f func() )。
需要連接這個signal時,調用它並把signal處理函數傳遞為參數即可
func sample() { fmt.Println("Data has been changed.") } widget := NewMyWidget(nil) // 這裡是創建我們的自定義組件,後面的文章我們會重點講解 // Qt5中與signal相連的可以是任何函數,在qt里也是一樣,所以我們用一個外部函數來處理signal,在實際開發中還是推薦用類的成員方法或者slot進行處理 widget.ConnectDataChanged(sample)
這裡我們把sample和信號DataChanged相連,每次觸發這個信號時都會列印出“Data has been changed.”這句信息。
如果想要取消和某個信號的連接,需要使用Disconnect[signal name]函數,它不帶參數,調用它意味著取消signal與上一次使用Connect[signal name]時作為參數的函數的連接。
widget.DisconnectDataChanged() // 我們取消了sample函數與DataChanged的連接
3. 信號----signal的觸發
在C++中要觸發一個信號,只需要如下代碼:
emit DataChanged()
emit ValueChanged(value)
emit?在C++和golang里都沒見過的語法。。。。。。沒錯,這也是Qt的moc擴展。
還記得我們說道qtmoc會根據signal tags生成三個成員方法嗎,ConnectDataChanged,DisconnectDataChanged,DataChanged
第三個函數就是我們用來觸發信號的。
信號觸發函數用來代替emit,它自身是一個根據signal tags前的類型生成的函數,所以MyWidget.DataChanged的類型是func f();而ValueChanged函數的類型就是func f(value int)。
註意,與Qt一樣,signal不可以擁有返回值。
下麵是觸發信號的示例:
// 觸發DataChanged信號 widget.DataChanged() value := 5 // 觸發ValueChanged信號並傳遞參數 widget.ValueChanged(1) widget.ValueChanged(100) widget.ValueChanged(value)
觸發信號之後,之前與之相連的函數就會被調用了。
4. 信號----signal的自動連接
如果自定義的signal比較多,那麼一個個的調用Connect[signal name]不僅麻煩低效,還會帶來維護上的困難,所以qt提供了自動連接的功能。
先看代碼:
import ( "github,com/therecipe/qt/widgets" ) type Auto struct { widgets.QLabel _ func() `signal:"dataChanged,auto"` _ func(string) `signal:"valueChanged,auto(this.QLabel.SetText)"` }
我們看到在signal的名字後面多了一個auto。
這個auto是告訴qtmoc這個信號需要connect一個和signal tags里名字相同的成員方法,在這裡成員函數的名字必須和tags里的相同,而不是經過strings.Title處理過的signal名字。
然後我們定義並實現這個和DataChanged連接的成員函數:
func (a *Auto) dataChanged() { fmt.Println("Data has been changed.") }
這樣你無需再顯示調用ConnectDataChanged,DataChanged將自動和成員函數dataChanged連接。
我們還看到有auto(this.QLabel.SetText)的寫法,這是在自定義類型繼承自其他QObject及其派生類時,可以自動連接基類的成員方法。
this是指當前的對象;
QLabel或是其他類型名錶示繼承的基類;
SetText是基類的成員函數,它將與DataChanged信號相關聯。這裡寫成setText也可以,因為在()里的函數名會被strings.Title處理。
每當我們觸發信號時:
widget := NewAuto(nil, 0) widget.DataChanged() widget.ValueChanged("signal & slot")
相應的成員函數就會被調用,上面的代碼會有如下反應:
// 因為DataChanged信號而被觸發 widget.dataChanged() // 因為ValueChanged信號而被觸發 widget.QLabel.SetText("signal & slot") // 等價於 widget.SetText("signal & slot")
有人會問,那可不可以用`signal:"dataChanged,auto(this.Myfunc)"`自己指定想要和信號connect的函數呢?
答案是暫時不可以。目前auto只有上面兩種用法,不過作者以及把實現自動連接成員變數的成員函數和自定義連接函數加入了開發計劃中,相信不久之後就能用上這些功能了。
以上就是qt中signal的具體用法,下一篇我們將會介紹slot的詳細用法。
如有疑問歡迎在評論中提出。
參考:
http://doc.qt.io/qt-5/signalsandslots.html