Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 08 (zh TW): Difference between revisions

From KDE TechBase
No edit summary
 
(6 intermediate revisions by 2 users not shown)
Line 9: Line 9:
pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_07_(zh_TW)|教學 7 - One Thing Leads to Another]]|
pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_07_(zh_TW)|教學 7 - One Thing Leads to Another]]|


next=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_09|教學 9 - With Cannon You Can]]
next=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_09_(zh_TW)|教學 9 - With Cannon You Can]]
}}
}}
== Preparing for Battle ==
== Preparing for Battle ==
Line 28: Line 28:
我們現在加入設定 '''<tt>LCDRange</tt>''' 範圍的可能性。到目前為止,它都被固定在0到99之間。
我們現在加入設定 '''<tt>LCDRange</tt>''' 範圍的可能性。到目前為止,它都被固定在0到99之間。


<code ruby>
<syntaxhighlight lang="ruby">
def setRange(minVal, maxVal)
def setRange(minVal, maxVal)
   if minVal < 0 || maxVal > 99 || minVal > maxVal
   if minVal < 0 || maxVal > 99 || minVal > maxVal
Line 39: Line 39:
   @slider.setRange(minVal, maxVal)
   @slider.setRange(minVal, maxVal)
end
end
</code>
</syntaxhighlight>


'''<tt>setRange()</tt>''' 槽設定了 '''<tt>LCDRange</tt>''' 中 slider 的範圍。因為我們已經設立了 [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] 是顯示兩位數。所以我們希望盡可能限制 '''<tt>minVal</tt>''' 和 '''<tt>maxVal</tt>''' 的範圍,避免[http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] 的溢位。(我們也可以允許值下限至-9,但我們沒有選擇這麼做。)如果參數是非法的,我們使用 Qt 的 [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] 函式對使用者發出警告並立即返回。[http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] 是一個類似 '''<tt>printf</tt>''' 的函式,預設情況下它會輸出至 '''<tt>$stderr</tt>'''。如果您願意,您可以使用 [http://doc.qt.nokia.com/latest/qtglobal.html#qInstallMsgHandler QtGlobal::qInstallMsgHandler()] 安裝自己的處理函式。
'''<tt>setRange()</tt>''' 槽設定了 '''<tt>LCDRange</tt>''' 中 slider 的範圍。因為我們已經設立了 [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] 是顯示兩位數。所以我們希望盡可能限制 '''<tt>minVal</tt>''' 和 '''<tt>maxVal</tt>''' 的範圍,避免[http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] 的溢位。(我們也可以允許值下限至-9,但我們沒有選擇這麼做。)如果參數是非法的,我們使用 Qt 的 [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] 函式對使用者發出警告並立即返回。[http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] 是一個類似 '''<tt>printf</tt>''' 的函式,預設情況下它會輸出至 '''<tt>$stderr</tt>'''。如果您願意,您可以使用 [http://doc.qt.nokia.com/latest/qtglobal.html#qInstallMsgHandler QtGlobal::qInstallMsgHandler()] 安裝自己的處理函式。


<code ruby>
<syntaxhighlight lang="ruby">
lcd.setSegmentStyle(Qt::LCDNumber::Filled)
lcd.setSegmentStyle(Qt::LCDNumber::Filled)
</code>
</syntaxhighlight>


這使得我們的 lcd 數字顯示方式更好。我不敢肯定,但我相信做到這一點是因為設定一個調色板(請見下一節)。我所知道的是,我在前面的章節嘗試時,這條線不會生效,但在這裡可以運作。
這使得我們的 lcd 數字顯示方式更好。我不敢肯定,但我相信做到這一點是因為設定一個調色板(請見下一節)。我所知道的是,我在前面的章節嘗試時,這條線不會生效,但在這裡可以運作。
Line 51: Line 51:
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/cannon.rb cannon.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/cannon.rb cannon.rb]'''


<code ruby>
<syntaxhighlight lang="ruby">
@currentAngle = 45
@currentAngle = 45
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
setAutoFillBackground(true)
setAutoFillBackground(true)
</code>
</syntaxhighlight>


建構子初始化角度為45度,並為這個 widget 設定一個自定調色板(palette)。
建構子初始化角度為45度,並為這個 widget 設定一個自定調色板(palette)。
Line 61: Line 61:
這個調色板使用指定的顏色作為背景,並挑選其他適當的顏色。(對這個 widget 而言,只有背景和文字顏色會真正用到。)然後,我們呼叫 setAutoFillBackground(true) 告訴 Qt 自動填充背景。
這個調色板使用指定的顏色作為背景,並挑選其他適當的顏色。(對這個 widget 而言,只有背景和文字顏色會真正用到。)然後,我們呼叫 setAutoFillBackground(true) 告訴 Qt 自動填充背景。


The [http://doc.qt.nokia.com/latest/qcolor.html Qt::Color] is specified as a RGB (red-green-blue) triplet, where each value is between 0 (dark) and 255 (bright). We could also have used a predefined color such as [http://doc.qt.nokia.com/latest/qt.html#GlobalColor-enum Qt::yellow] instead of specifying an RGB value.
[http://doc.qt.nokia.com/latest/qcolor.html Qt::Color] 用來指定一組 RGB(紅-綠-藍),每個值介於0(暗)和255(亮)之間。我們也可以使用一個預先定義的顏色,例如 [http://doc.qt.nokia.com/latest/qt.html#GlobalColor-enum Qt::yellow],來代替指定RGB值。


<code ruby>
<syntaxhighlight lang="ruby">
def setAngle(angle)
def setAngle(angle)
   if angle < 5
   if angle < 5
Line 79: Line 79:
   emit angleChanged(@currentAngle)
   emit angleChanged(@currentAngle)
end  def setAngle(degrees)
end  def setAngle(degrees)
</code>
</syntaxhighlight>


This function sets the angle value. We have chosen a legal range of 5 to 70 and adjust the given number of degrees accordingly. We have chosen not to issue a warning if the new angle is out of range.
這個函式設定角度值。我們選擇了5到70的合法範圍,並依此調整給予的角度數值。如果新的角度超出範圍,我們選擇了不發出警告。


If the new angle equals the old one, we return immediately. It is important to only emit the '''<tt>angleChanged()</tt>''' signal when the angle really has changed.
如果新的角度和舊的相等,我們立即返回。重要的是,只有當角度真的發生變化,才發出'''<tt>angleChanged()</tt>''' 訊號。


Then we set the new angle value and repaint our widget. The [http://doc.qt.nokia.com/latest/qwidget.html#update Qt::Widget::update()] function clears the widget (usually filling it with its background color) and sends a paint event to the widget. This results in a call to the paint event function of the widget.
然後我們設定新的角度值,並重繪我們的 widget。[http://doc.qt.nokia.com/latest/qwidget.html#update Qt::Widget::update()] 函式會清空這個 widget(通常用它的背景顏色填滿),並發送一個繪圖(paint)事件給這個 widget。結果就是呼叫了這個 widget 的繪圖事件函式。


Finally, we emit the '''<tt>angleChanged()</tt>''' signal to tell the outside world that the angle has changed. The '''<tt>emit</tt>''' keyword is unique to Qt and not regular Ruby syntax.
最後,我們發出 '''<tt>angleChanged()</tt>''' 訊號來告訴外界角度改變了。'''<tt>emit</tt>''' 關鍵字是 Qt 特有的,並不是標準的 Ruby 語法。


<code ruby>
<syntaxhighlight lang="ruby">
def paintEvent(event)
def paintEvent(event)
   painter = Qt::Painter.new(self)
   painter = Qt::Painter.new(self)
Line 95: Line 95:
   painter.end()
   painter.end()
end
end
</code>
</syntaxhighlight>


This is our first attempt to write a paint event handler. The event argument contains a description of the paint event. [http://doc.qt.nokia.com/latest/qpaintevent.html Qt::PaintEvent] contains the region in the widget that must be updated. For the time being, we will be lazy and just paint everything.
這是我們第一次嘗試寫繪圖事件處理器。事件參數包含了繪圖事件的描述。[http://doc.qt.nokia.com/latest/qpaintevent.html Qt::PaintEvent] 包含 widget 中必須更新的區域。暫時,讓我們懶惰的直接畫出所有東西。


Our code displays the angle value in the widget at a fixed position. We create a [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter] operating on this widget and use it to paint a string. We'll come back to [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter] later; it can do a great many things.
我們的程式碼會在 Widget 中一個固定的位置顯示角度值。我們建立了一個在這個 widget 運作的 [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter],並用它畫出字串。我們之後會再回到 [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter],它可以做很多事情。


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/t8.rb t8.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/t8.rb t8.rb]'''


<code ruby>
<syntaxhighlight lang="ruby">
angle = LCDRange.new()
angle = LCDRange.new()
angle.setRange(5, 70)
angle.setRange(5, 70)
</code>
</syntaxhighlight>


In the constructor, we create and set up the LCDRange widget. We set the LCDRange to accept angles from 5 to 70 degrees.
在建構子中,我們建立並設定 LCDRange widget。 我們設定 LCDRange 接受5到70度的角度。


<code ruby>
<syntaxhighlight lang="ruby">
  cannonField = CannonField.new()
  cannonField = CannonField.new()
</code>
</syntaxhighlight>


We create our CannonField widget.
我們建立了 CannonField widget。


<code ruby>
<syntaxhighlight lang="ruby">
connect(angle, SIGNAL('valueChanged(int)'),
connect(angle, SIGNAL('valueChanged(int)'),
         cannonField, SLOT('setAngle(int)'))
         cannonField, SLOT('setAngle(int)'))
connect(cannonField, SIGNAL('angleChanged(int)'),
connect(cannonField, SIGNAL('angleChanged(int)'),
         angle, SLOT('setValue(int)'))
         angle, SLOT('setValue(int)'))
</code>
</syntaxhighlight>


Here we connect the '''<tt>valueChanged()</tt>''' signal of the '''<tt>LCDRange</tt>''' to the '''<tt>setValue()</tt>''' slot of the '''<tt>CannonField</tt>'''. This will update '''<tt>CannonField</tt>''''s angle value whenever the user operates the '''<tt>LCDRange</tt>'''. We also make the reverse connection so that changing the angle in the '''<tt>CannonField</tt>''' will update the '''<tt>LCDRange</tt>''' value. In our example we never change the angle of the '''<tt>CannonField</tt>''' directly; but by doing the last connect() we ensure that no future changes will disrupt the synchronization between those two values.
在這裡,我們連接 '''<tt>LCDRange</tt>''' '''<tt>valueChanged()</tt>''' 訊號到  '''<tt>CannonField</tt>''' '''<tt>setValue()</tt>''' 槽。每當使用者操作 '''<tt>LCDRange</tt>''' 時,這將更新 '''<tt>CannonField</tt>''' 的角度值。我們也做出反向連接,以便 '''<tt>CannonField</tt>''' 改變角度,也會更新 '''<tt>LCDRange</tt>''' 的數值。在我們的例子中,我們不會直接改變 '''<tt>CannonField</tt>''' 的角度。但藉由最後的 connect(),我們可以確保沒有改變會破壞這兩個值之間的同步。


This illustrates the power of component programming and proper encapsulation.
這說明了組件程式設計和適當封裝的威力。


Notice how important it is to emit the '''<tt>angleChanged()</tt>''' signal only when the angle actually changes. If both the '''<tt>LCDRange</tt>''' and the '''<tt>CannonField</tt>''' had omitted this check, the program would have entered an infinite loop upon the first change of one of the values.
請注意,只有在角度實際改變時,發出 '''<tt>angleChanged()</tt>''' 訊號是多麼重要。如果 '''<tt>LCDRange</tt>''' '''<tt>CannonField</tt>''' 同時省略了這個檢查,在第一次改變其中一個值後,這支程式將進入一個無限循環。


<code ruby>
<syntaxhighlight lang="ruby">
gridLayout = Qt::GridLayout.new()
gridLayout = Qt::GridLayout.new()
</code>
</syntaxhighlight>


So far, we have used [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::VBoxLayout] for geometry management. Now, however, we want to have a little more control over the layout, and we switch to the more powerful [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] class. [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] isn't a widget; it is a different class that can manage the children of any widget.
到目前為止,我們都是使用 [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::VBoxLayout] 作為幾何管理。但現在,我們希望有更多一點的佈局控制,所以我們切換到更強大的 [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 類別。[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 不是一個 widget。它是另一個可以管理任何子 widget 的類別。


We don't need to specify any dimensions to the [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::GridLayout] constructor. The [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] will determine the number of rows and columns based on the grid cells we populate.
我們不需要在 [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::GridLayout] 的建構子裡指定任何尺寸。[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 會根據我們放入的網格決定列和行的數目。


[[Image:Qt4_Ruby_Tutorial_Screenshot_8-layout.png]][[Image:Qt4_Ruby_Tutorial_Screenshot_8-reallayout.png]]
[[Image:Qt4_Ruby_Tutorial_Screenshot_8-layout.png]][[Image:Qt4_Ruby_Tutorial_Screenshot_8-reallayout.png]]


The diagram above shows the layout we're trying to achieve. The left side shows a schematic view of the layout; the right side is an actual screenshot of the program. ''(These two images are copyrighted/owned by Nokia.)''
上圖展示了我們試著去實現的佈局。左邊展示的是佈局示意圖,右邊是一個程式的實際截圖。''(這兩張圖的版權為諾基亞所有。)''


<code ruby>
<syntaxhighlight lang="ruby">
     gridLayout.addWidget(quit, 0, 0)
     gridLayout.addWidget(quit, 0, 0)
</code>
</syntaxhighlight>


We add the <strong>Quit</strong> button in the top-left cell of the grid, i.e., the cell with coordinates (0, 0).
我們在網格的左上角,即坐標(0,0),加入 <strong>Quit</strong> 按鈕。


<code ruby>
<syntaxhighlight lang="ruby">
     gridLayout.addWidget(angle, 1, 0)
     gridLayout.addWidget(angle, 1, 0)
</code>
</syntaxhighlight>


We put the angle '''<tt>LCDRange</tt>''' cell (1, 0).
我們把角度 '''<tt>LCDRange</tt>''' 放在(1,0)。 We put the angle '''<tt>LCDRange</tt>''' cell (1, 0).


<code ruby>
<syntaxhighlight lang="ruby">
gridLayout.addWidget(cannonField, 1, 1, 2, 1)
gridLayout.addWidget(cannonField, 1, 1, 2, 1)
</code>
</syntaxhighlight>


We let the '''<tt>CannonField</tt>''' object occupy cells (1, 1) and (2, 1).
我們讓 '''<tt>CannonField</tt>''' 物件佔據(1,1)和(2,1)。


<code ruby>
<syntaxhighlight lang="ruby">
     gridLayout.setColumnStretch(1, 10)
     gridLayout.setColumnStretch(1, 10)
</code>
</syntaxhighlight>


We tell [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] that the right column (column 1) is stretchable, with a stretch factor of 10. Because the left column isn't (its stretch factor is 0, the default value), [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] will try to let the left-hand widgets' sizes be unchanged and will resize just the '''<tt>CannonField</tt>''' when the '''<tt>MyWidget</tt>''' is resized.
我們告訴 [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 右邊的行(行1)伸展係數(stretch factor)為10,是可伸展的。由於左行不是(它的伸展係數為0,即預設值),[http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] 會盡量讓左邊的 widgets 的大小不變。並在 '''<tt>MyWidget</tt>''' 大小改變時,只改變 '''<tt>CannonField</tt>''' 的大小。


In this particular example, any stretch factor greater than 0 for column 1 would have the same effect. In more complex layouts, you can use the stretch factors to tell that a particular column or row should stretch twice as fast as another by assigning appropriate stretch factors.
在這個特殊的例子中,第1行用任何大於0的伸展係數都有同樣的效果。在更複雜的佈局中,您可以分配適當的伸展係數告訴一個特定的行或列比另一個的伸展快兩倍。


<code ruby>
<syntaxhighlight lang="ruby">
     angle.setValue(60)
     angle.setValue(60)
</code>
</syntaxhighlight>


We set an initial angle value. Note that this will trigger the connection from '''<tt>LCDRange</tt>''' to '''<tt>CannonField</tt>'''.
我們設定一個初始角度值。請注意,這將觸發從 '''<tt>LCDRange</tt>''' '''<tt>CannonField</tt>''' 的連接 。


<code ruby>
<syntaxhighlight lang="ruby">
     angle.setFocus()
     angle.setFocus()
</code>
</syntaxhighlight>


Our last action is to set '''<tt>angle</tt>''' to have keyboard focus so that keyboard input will go to the '''<tt>LCDRange</tt>''' widget by default.
我們最後的動作是設定 '''<tt>angle</tt>''' 取得鍵盤焦點(focus),使鍵盤輸入預設將傳到 '''<tt>LCDRange</tt>''' widget。


===執行應用程式===
===執行應用程式===
When the slider is operated, the '''<tt>CannonField</tt>''' displays the new angle value. Upon resizing, '''<tt>CannonField</tt>''' is given as much space as possible.
slider 操作時,'''<tt>CannonField</tt>''' 會顯示新的角度值。若是調整大小,'''<tt>CannonField</tt>''' 會盡可能得到多的空間。


===練習===
===練習===
Try to resize the window. What happens if you make it really narrow or really squat?
嘗試調整視窗的大小。如果你使它變得非常窄或非常矮,會發生什麼事?


If you give the left-hand column a non-zero stretch factor, what happens when you resize the window?
如果你給左邊行一個非零的伸展係數。當視窗大小改變時,會發生什麼事?


Try to change "Quit" to "&Quit". How does the button's look change? ( Whether it does change or not depends on the platform.) What happens if you press <strong>Alt+Q</strong> while the program is running?
試著修改「Quit」為「&Quit」。 按鈕的外觀有什麼改變呢?(是否改變跟平台有關。)如果你在程式執行時,按下 <strong>Alt+Q</strong>,會發生什麼事?


Center the text in the '''<tt>CannonField</tt>'''.
'''<tt>CannonField</tt>''' 的文字置中。
 
[[Category:Ruby]]

Latest revision as of 15:44, 23 June 2013

Template:I18n/Language Navigation Bar (zh TW)

Template:TutorialBrowser (zh TW)

Preparing for Battle

檔案:

概覽

在這個範例中,我們介紹第一個可以描繪本身的自定 widget。我們還增加一個有用的鍵盤介面(用兩行程式碼)。

一行一行的瀏覽

lcdrange.rb

這個檔案非常類似第7章的 lcdrange.rb。但我們新增了一個槽:setRange()

我們現在加入設定 LCDRange 範圍的可能性。到目前為止,它都被固定在0到99之間。

def setRange(minVal, maxVal)
  if minVal < 0 || maxVal > 99 || minVal > maxVal
    qWarning("LCDRange::setRange(#{minVal}, #{maxVal})\n" +
               "\tRange must be 0..99\n" +
               "\tand minVal must not be greater than maxVal")
    return
  end

  @slider.setRange(minVal, maxVal)
end

setRange() 槽設定了 LCDRange 中 slider 的範圍。因為我們已經設立了 QLCDNumber 是顯示兩位數。所以我們希望盡可能限制 minValmaxVal 的範圍,避免QLCDNumber 的溢位。(我們也可以允許值下限至-9,但我們沒有選擇這麼做。)如果參數是非法的,我們使用 Qt 的 QtGlobal::qWarning() 函式對使用者發出警告並立即返回。QtGlobal::qWarning() 是一個類似 printf 的函式,預設情況下它會輸出至 $stderr。如果您願意,您可以使用 QtGlobal::qInstallMsgHandler() 安裝自己的處理函式。

lcd.setSegmentStyle(Qt::LCDNumber::Filled)

這使得我們的 lcd 數字顯示方式更好。我不敢肯定,但我相信做到這一點是因為設定一個調色板(請見下一節)。我所知道的是,我在前面的章節嘗試時,這條線不會生效,但在這裡可以運作。

cannon.rb

@currentAngle = 45
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
setAutoFillBackground(true)

建構子初始化角度為45度,並為這個 widget 設定一個自定調色板(palette)。

這個調色板使用指定的顏色作為背景,並挑選其他適當的顏色。(對這個 widget 而言,只有背景和文字顏色會真正用到。)然後,我們呼叫 setAutoFillBackground(true) 告訴 Qt 自動填充背景。

Qt::Color 用來指定一組 RGB(紅-綠-藍),每個值介於0(暗)和255(亮)之間。我們也可以使用一個預先定義的顏色,例如 Qt::yellow,來代替指定RGB值。

def setAngle(angle)
  if angle < 5
    angle = 5
  elsif angle > 70
    angle = 70
  end

  if @currentAngle == angle
    return
  end

  @currentAngle = angle
  update()
  emit angleChanged(@currentAngle)
end  def setAngle(degrees)

這個函式設定角度值。我們選擇了5到70的合法範圍,並依此調整給予的角度數值。如果新的角度超出範圍,我們選擇了不發出警告。

如果新的角度和舊的相等,我們立即返回。重要的是,只有當角度真的發生變化,才發出angleChanged() 訊號。

然後我們設定新的角度值,並重繪我們的 widget。Qt::Widget::update() 函式會清空這個 widget(通常用它的背景顏色填滿),並發送一個繪圖(paint)事件給這個 widget。結果就是呼叫了這個 widget 的繪圖事件函式。

最後,我們發出 angleChanged() 訊號來告訴外界角度改變了。emit 關鍵字是 Qt 特有的,並不是標準的 Ruby 語法。

def paintEvent(event)
  painter = Qt::Painter.new(self)
  painter.drawText(200, 200, tr("Angle = #{@currentAngle}"))
  painter.end()
end

這是我們第一次嘗試寫繪圖事件處理器。事件參數包含了繪圖事件的描述。Qt::PaintEvent 包含 widget 中必須更新的區域。暫時,讓我們懶惰的直接畫出所有東西。

我們的程式碼會在 Widget 中一個固定的位置顯示角度值。我們建立了一個在這個 widget 運作的 Qt::Painter,並用它畫出字串。我們之後會再回到 Qt::Painter,它可以做很多事情。

t8.rb

angle = LCDRange.new()
angle.setRange(5, 70)

在建構子中,我們建立並設定 LCDRange widget。 我們設定 LCDRange 接受5到70度的角度。

 cannonField = CannonField.new()

我們建立了 CannonField widget。

connect(angle, SIGNAL('valueChanged(int)'),
        cannonField, SLOT('setAngle(int)'))
connect(cannonField, SIGNAL('angleChanged(int)'),
        angle, SLOT('setValue(int)'))

在這裡,我們連接 LCDRangevalueChanged() 訊號到 CannonFieldsetValue() 槽。每當使用者操作 LCDRange 時,這將更新 CannonField 的角度值。我們也做出反向連接,以便 CannonField 改變角度,也會更新 LCDRange 的數值。在我們的例子中,我們不會直接改變 CannonField 的角度。但藉由最後的 connect(),我們可以確保沒有改變會破壞這兩個值之間的同步。

這說明了組件程式設計和適當封裝的威力。

請注意,只有在角度實際改變時,發出 angleChanged() 訊號是多麼重要。如果 LCDRangeCannonField 同時省略了這個檢查,在第一次改變其中一個值後,這支程式將進入一個無限循環。

gridLayout = Qt::GridLayout.new()

到目前為止,我們都是使用 Qt::VBoxLayout 作為幾何管理。但現在,我們希望有更多一點的佈局控制,所以我們切換到更強大的 Qt::GridLayout 類別。Qt::GridLayout 不是一個 widget。它是另一個可以管理任何子 widget 的類別。

我們不需要在 Qt::GridLayout 的建構子裡指定任何尺寸。Qt::GridLayout 會根據我們放入的網格決定列和行的數目。

上圖展示了我們試著去實現的佈局。左邊展示的是佈局示意圖,右邊是一個程式的實際截圖。(這兩張圖的版權為諾基亞所有。)

    gridLayout.addWidget(quit, 0, 0)

我們在網格的左上角,即坐標(0,0),加入 Quit 按鈕。

    gridLayout.addWidget(angle, 1, 0)

我們把角度 LCDRange 放在(1,0)。 We put the angle LCDRange cell (1, 0).

gridLayout.addWidget(cannonField, 1, 1, 2, 1)

我們讓 CannonField 物件佔據(1,1)和(2,1)。

    gridLayout.setColumnStretch(1, 10)

我們告訴 Qt::GridLayout 右邊的行(行1)伸展係數(stretch factor)為10,是可伸展的。由於左行不是(它的伸展係數為0,即預設值),Qt::GridLayout 會盡量讓左邊的 widgets 的大小不變。並在 MyWidget 大小改變時,只改變 CannonField 的大小。

在這個特殊的例子中,第1行用任何大於0的伸展係數都有同樣的效果。在更複雜的佈局中,您可以分配適當的伸展係數告訴一個特定的行或列比另一個的伸展快兩倍。

    angle.setValue(60)

我們設定一個初始角度值。請注意,這將觸發從 LCDRangeCannonField 的連接 。

    angle.setFocus()

我們最後的動作是設定 angle 取得鍵盤焦點(focus),使鍵盤輸入預設將傳到 LCDRange widget。

執行應用程式

當 slider 操作時,CannonField 會顯示新的角度值。若是調整大小,CannonField 會盡可能得到多的空間。

練習

嘗試調整視窗的大小。如果你使它變得非常窄或非常矮,會發生什麼事?

如果你給左邊行一個非零的伸展係數。當視窗大小改變時,會發生什麼事?

試著修改「Quit」為「&Quit」。 按鈕的外觀有什麼改變呢?(是否改變跟平台有關。)如果你在程式執行時,按下 Alt+Q,會發生什麼事?

CannonField 的文字置中。