Archive:Development/Tutorials/Qt4 Ruby Tutorial/Chapter 08 (zh CN)

From KDE TechBase

Template:I18n/Language Navigation Bar (zh CN)

Template:TutorialBrowser (zh CN)

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 的文字置中。