Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 12 (zh TW)

From KDE TechBase
Revision as of 20:43, 29 June 2011 by Neverendingo (talk | contribs) (Text replace - "<code ruby>" to "<syntaxhighlight lang="ruby">")

Template:I18n/Language Navigation Bar (zh TW)

Template:TutorialBrowser (zh TW)

Hanging in the Air the Way Bricks Don't

檔案:

概覽

在這個範例中,我們擴充我們的 LCDRange 類別來包含一個文字標籤。我們還提供一些用來射擊的目標。

一行一行的瀏覽

lcdrange.rb

<syntaxhighlight lang="ruby"> def initialize(s, parent = nil)

 super(parent)
 init()
 setText(s)

end

這個建構子先呼叫 init(),然後設定標籤文字。init() 是一個執行大部分初始化的獨立函式,因為在原來的C++版本中是函式重載。

<syntaxhighlight lang="ruby"> def init()

 lcd = Qt::LCDNumber.new(2)
 lcd.setSegmentStyle(Qt::LCDNumber::Filled)
 @slider = Qt::Slider.new(Qt::Horizontal)
 @slider.setRange(0, 99)
 @slider.setValue(0)
 @label = Qt::Label.new()
 @label.setAlignment(Qt::AlignHCenter.to_i | Qt::AlignTop.to_i)
 connect(@slider, SIGNAL('valueChanged(int)'),
 lcd, SLOT('display(int)'))
 connect(@slider, SIGNAL('valueChanged(int)'),
 self, SIGNAL('valueChanged(int)'))
 layout = Qt::VBoxLayout.new()
 layout.addWidget(lcd)
 layout.addWidget(@slider)
 layout.addWidget(@label)
 setLayout(layout)
 setFocusProxy(@slider)

end

lcd 的結構和前面章節的 slider 是一樣的。接下來,我們建立一個 Qt::Label,並告訴它以水平置中和與垂直向上的方式對齊內容。Qt::Object::connect() 呼叫也取自前面章節。

<syntaxhighlight lang="ruby"> def setText(s)

 @label.setText(s)

end

這個函式設定標籤文字。

cannon.rb

CannonField 現在有兩個新的訊號:hit()missed()。此外,它還多了一個目標。

<syntaxhighlight lang="ruby"> signals 'hit()', 'missed()' #...

一旦砲彈擊中了目標,hit() 訊號就會被發出。而當砲彈越過了 widget 右側或底部的邊緣,missed() 訊號就會被發出(換言之,可以肯定的是它不會命中目標)。

<syntaxhighlight lang="ruby"> newTarget()

這行已經加入到建構子中。它為目標建立了一個「隨機」的位置。事實上,newTarget() 函式將嘗試畫出目標。因為我們是在建構子中,CannonField widget 是不可見的。Qt 保證在一個隱藏的 widget 中呼叫Qt::Widget::update() 是無害。

<syntaxhighlight lang="ruby"> @@first_time = true

def newTarget()

 if @@first_time
   @@first_time = false
   midnight = Qt::Time.new(0, 0, 0)
   srand(midnight.secsTo(Qt::Time.currentTime()))
 end
 @target = Qt::Point.new(200 + rand(190), 10 + rand(255))
 update()

end

這個函式會在新的隨機位置建立一個目標中心點。

我們建立了 Qt::Time 物件 midnight,代表時間00:00:00。接下來,我們取出從午夜到目前為止經過的秒數,並使用它作為亂數子(random seed)。請參閱文件 Qt::DateQt::Time 和Qt::DateTime 以取得更多資訊。

最後,我們計算出目標的中心點。我們保持它在 widget 的底邊為 y 值0,讓 y 值向上增加,x 是正常的左側邊緣為0,以及右側增加x值的坐標系統中的矩形(x=200,y=35,寬=190,高=255。換句話說,可能的 x 和 y 值分別是200至389和35至289)。

通過實驗我們發現這樣砲彈都可以擊中。

<syntaxhighlight lang="ruby"> def moveShot()

 region = Qt::Region.new(shotRect())
 @timerCount += 1
 shotR = shotRect()

來自前面章節的這部分計時器事件並沒有改變。

<syntaxhighlight lang="ruby"> if shotR.intersects(targetRect())

 @autoShootTimer.stop()
 emit hit()

這個 if 敘述檢查砲彈矩形是否與目標矩形相交。如果是這樣,砲彈會擊中了目標(哎喲!)。我們停止射擊計時器,並發出 hit() 訊號來告訴外界目標被摧毀,並且返回。請注意,我們可以在這個點上建立一個新的目標,但是因為 CannonField 是一個組件,我們把這樣的決定留給組件的使用者。

<syntaxhighlight lang="ruby"> elsif shotR.x() > width() || shotR.y() > height()

 @autoShootTimer.stop()
 emit missed()

這與前面章節是一樣的,但它現在發出 missed() 訊號來告訴外界射擊失敗。

<syntaxhighlight lang="ruby">

 else
   region = region.unite(Qt::Region.new(shotR))
 end
   
 update(region)

end

而函式的剩下部分與之前一樣。

CannonField::paintEvent() 和之前相同,除了加入這個:

<syntaxhighlight lang="ruby">

   paintTarget(painter)

這行可以確保在必要時目標也會畫出。

<syntaxhighlight lang="ruby"> def paintTarget(painter)

 painter.setBrush(Qt::Brush.new(Qt::red))
 painter.setPen(Qt::Pen.new(Qt::Color.new(Qt::black)))
 painter.drawRect(targetRect())

end

這個函式繪製目標;一個填充了紅色和黑色外框的長方形。

<syntaxhighlight lang="ruby"> def targetRect()

 result = Qt::Rect.new(0, 0, 20, 10)
 result.moveCenter(Qt::Point.new(@target.x(), height() - 1 - @target.y()))
 return result

end

這個私有函式返回封裝目標的矩形。還記得取自 newTarget()target 點使用 widget 的底部為 y 坐標0。,我們在呼叫 Qt::Rect::moveCenter() 之前,在 widget 坐標中計算這個點。

我們之所以選擇這個坐標映射(coordinate mapping)是為了要固定目標和 widget 底部之間的距離。請記住,使用者或程式可以在任何時候改變 widget 大小。

t12.rb

MyWidget 類別中沒有新的成員。但我們現在稍微修改建構子來設定新的 LCDRange 文字標籤。

<syntaxhighlight lang="ruby">

   angle = LCDRange.new(tr('ANGLE'))

我們設定了角度文字標籤為「ANGLE」。

<syntaxhighlight lang="ruby">

   force  = LCDRange.new(tr('FORCE'))

我們設定了力量文字標籤為「FORCE」。

執行應用程式

LCDRange widgets 看起來有點怪:當 MyWidget 大小改變時,Qt::VBoxLayout 內建的佈局管理給標籤太多的空間了,其它的就不夠用;使得兩個 LCDRange widget 之間的空間改變大小。我們將在下一章修正。

練習

做一個作弊按鈕。當按下時,使 CannonField 顯示5秒的射擊軌跡。

如果你做了前一章的「圓形砲彈」練習,請嘗試把 shotRect() 更改為返回Qt::RegionshotRegion(),這樣你就可以有相當精確的碰撞檢測。

做一個移動的目標。

請確保目標建立時,總是完整的在螢幕上。

請確保這個 widget 不能調整大小,以免目標變成不可見的。[提示:Qt::Widget::setMinimumSize() 是你的好朋友。]

這不太容易。同一時間有多個砲彈在空中。[提示:建立 Shot 類別。]