ZetCode-GUI-教程-二-
ZetCode GUI 教程(二)
原文:ZetCode
PyQt4 小部件 II
在这里,我们将继续介绍 PyQt4 小部件。 我们将介绍QtGui.QPixmap
,QtGui.QLineEdit
,QtGui.QSplitter
和QtGui.QComboBox
。
QtGui.QPixmap
QtGui.QPixmap
是用于处理图像的小部件之一。 它经过优化,可在屏幕上显示图像。 在我们的代码示例中,我们将使用QtGui.QPixmap
在窗口上显示图像。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example, we dispay an image
on the window.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
pixmap = QtGui.QPixmap("redrock.png")
lbl = QtGui.QLabel(self)
lbl.setPixmap(pixmap)
hbox.addWidget(lbl)
self.setLayout(hbox)
self.move(300, 200)
self.setWindowTitle('Red Rock')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们在窗口上显示图像。
pixmap = QtGui.QPixmap("redrock.png")
我们创建一个QtGui.QPixmap
对象。 它以文件名作为参数。
lbl = QtGui.QLabel(self)
lbl.setPixmap(pixmap)
我们将像素图放入QtGui.QLabel
小部件。
QtGui.QLineEdit
QtGui.QLineEdit
是一个小部件,允许输入和编辑单行纯文本。 该小部件具有撤消和重做,剪切和粘贴以及拖放功能。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This example shows text which
is entered in a QtGui.QLineEdit
in a QtGui.QLabel widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.lbl = QtGui.QLabel(self)
qle = QtGui.QLineEdit(self)
qle.move(60, 100)
self.lbl.move(60, 40)
qle.textChanged[str].connect(self.onChanged)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QtGui.QLineEdit')
self.show()
def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
此示例显示了行编辑小部件和标签。 我们在行编辑中键入的文本会立即显示在标签窗口小部件中。
qle = QtGui.QLineEdit(self)
QtGui.QLineEdit
小部件已创建。
qle.textChanged[str].connect(self.onChanged)
如果行编辑窗口小部件中的文本更改,我们将调用onChanged()
方法。
def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
在onChanged()
方法内部,我们将键入的文本设置为标签小部件。 我们调用adjustSize()
方法将标签的大小调整为文本的长度。
图:QtGui.QLineEdit
QtGui.QSplitter
QtGui.QSplitter
允许用户通过拖动子控件之间的边界来控制子控件的大小。 在我们的示例中,我们显示了由两个拆分器组成的三个QtGui.QFrame
小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This example shows
how to use QtGui.QSplitter widget.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
topleft = QtGui.QFrame(self)
topleft.setFrameShape(QtGui.QFrame.StyledPanel)
topright = QtGui.QFrame(self)
topright.setFrameShape(QtGui.QFrame.StyledPanel)
bottom = QtGui.QFrame(self)
bottom.setFrameShape(QtGui.QFrame.StyledPanel)
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)
splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QSplitter')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有三个框架小部件和两个拆分器。
topleft = QtGui.QFrame(self)
topleft.setFrameShape(QtGui.QFrame.StyledPanel)
我们使用样式化的框架以查看QtGui.QFrame
小部件之间的边界。
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)
我们创建一个QtGui.QSplitter
小部件,并在其中添加两个框架。
splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter2.addWidget(splitter1)
我们还可以将拆分器添加到另一个拆分器小部件。
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
我们使用Cleanlooks
风格。 在某些样式中,框架不可见。
图:QtGui.QSplitter
小部件
QtGui.QComboBox
QtGui.QComboBox
是一个小部件,允许用户从选项列表中进行选择。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This example shows
how to use QtGui.QComboBox widget.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.lbl = QtGui.QLabel("Ubuntu", self)
combo = QtGui.QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Red Hat")
combo.addItem("Gentoo")
combo.move(50, 50)
self.lbl.move(50, 150)
combo.activated[str].connect(self.onActivated)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QComboBox')
self.show()
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例显示了QtGui.QComboBox
和QtGui.QLabel
。 组合框具有五个选项的列表。 这些是 Linux 发行版的名称。 标签窗口小部件显示从组合框中选择的选项。
combo = QtGui.QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Red Hat")
combo.addItem("Gentoo")
我们创建具有五个选项的QtGui.QComboBox
小部件。
combo.activated[str].connect(self.onActivated)
选择项目后,我们调用onActivated()
方法。
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
在方法内部,我们将所选项目的文本设置为标签小部件。 我们调整标签的大小。
图:QtGui.QComboBox
在 PyQt4 教程的这一部分中,我们介绍了其他四个 PyQt4 小部件。
PyQt4 中的拖放
在 PyQt4 教程的这一部分中,我们将讨论拖放操作。
在计算机图形用户界面中,拖放是单击虚拟对象并将其拖动到其他位置或另一个虚拟对象上的动作(或支持以下动作)。 通常,它可用于调用多种动作,或在两个抽象对象之间创建各种类型的关联。
拖放是图形用户界面的一部分。 拖放操作使用户可以直观地执行复杂的操作。
通常,我们可以拖放两件事:数据或某些图形对象。 如果将图像从一个应用拖到另一个应用,则会拖放二进制数据。 如果我们在 Firefox 中拖动选项卡并将其移动到另一个位置,则将拖放图形组件。
简单的拖放
在第一个示例中,我们有一个QtGui.QLineEdit
和一个QtGui.QPushButton
。 我们将纯文本从行编辑小部件中拖放到按钮小部件上。 按钮的标签将更改。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This is a simple drag and
drop example.
author: Jan Bodnar
website: zetcode.com
last edited: January 2015
"""
import sys
from PyQt4 import QtGui
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
self.setText(e.mimeData().text())
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
edit = QtGui.QLineEdit('', self)
edit.setDragEnabled(True)
edit.move(30, 65)
button = Button("Button", self)
button.move(190, 65)
self.setWindowTitle('Simple drag & drop')
self.setGeometry(300, 300, 300, 150)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
if __name__ == '__main__':
main()
该示例展示了一个简单的拖动&放下操作。
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
为了在QtGui.QPushButton
小部件上放置文本,我们必须重新实现一些方法。 因此,我们创建了自己的Button
类,该类继承自QtGui.QPushButton
类。
self.setAcceptDrops(True)
我们为小部件启用放置事件。
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
首先,我们重新实现dragEnterEvent()
方法。 我们告知我们接受的数据类型。 在我们的情况下,它是纯文本。
def dropEvent(self, e):
self.setText(e.mimeData().text())
通过重新实现dropEvent()
方法,我们定义了对放置事件的处理方式。 在这里,我们更改按钮小部件的文本。
edit = QtGui.QLineEdit('', self)
edit.setDragEnabled(True)
QtGui.QLineEdit
小部件具有对拖动操作的内置支持。 我们需要做的就是调用setDragEnabled()
方法来激活它。
图:简单 drag & drop
拖动和放置一个按钮小部件
在下面的示例中,我们将演示如何拖放按钮小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this program, we can press on a button
with a left mouse click or drag and drop the
button with the right mouse click.
author: Jan Bodnar
website: zetcode.com
last edited: October 2013
"""
import sys
from PyQt4 import QtCore, QtGui
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
mimeData = QtCore.QMimeData()
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())
dropAction = drag.start(QtCore.Qt.MoveAction)
def mousePressEvent(self, e):
super(Button, self).mousePressEvent(e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.button = Button('Button', self)
self.button.move(100, 65)
self.setWindowTitle('Click or Move')
self.setGeometry(300, 300, 280, 150)
self.show()
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
position = e.pos()
self.button.move(position)
e.setDropAction(QtCore.Qt.MoveAction)
e.accept()
def main():
app = QtGui.QApplication([])
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的代码示例中,窗口上有一个QtGui.QPushButton
。 如果我们用鼠标左键单击该按钮,则会在控制台上显示'press'
消息。 通过右键单击并移动按钮,我们在按钮小部件上执行拖放操作。
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
我们创建一个Button
类,该类将从QtGui.QPushButton
派生。 我们还重新实现了QtGui.QPushButton
的两种方法:mouseMoveEvent()
和mousePressEvent()
。 mouseMoveEvent()
方法是拖放操作开始的地方。
if event.buttons() != QtCore.Qt.RightButton:
return
在这里,我们决定只能使用鼠标右键执行拖放操作。 鼠标左键保留用于单击该按钮。
mimeData = QtCore.QMimeData()
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(event.pos() - self.rect().topLeft())
QDrag
对象已创建。 该类提供对基于 MIME 的拖放数据传输的支持。
dropAction = drag.start(QtCore.Qt.MoveAction)
拖动对象的start()
方法开始拖放操作。
def mousePressEvent(self, e):
super(Button, self).mousePressEvent(e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
如果我们用鼠标左键单击按钮,我们将在控制台上打印'press'
。 注意,我们也在父对象上调用了mousePressEvent()
方法。 否则,我们将看不到按钮被按下。
position = e.pos()
self.button.move(position)
在dropEvent()
方法中,我们编码释放鼠标键并完成放置操作后发生的情况。 我们找出当前鼠标指针的位置并相应地移动按钮。
e.setDropAction(QtCore.Qt.MoveAction)
e.accept()
我们指定放置动作的类型。 在我们的情况下,这是一个动作。
PyQt4 教程的这一部分专门用于拖放操作。
PyQt4 中的绘图
当我们想要更改或增强现有的小部件,或者要从头开始创建自定义小部件时,绘图应用中需要绘图。 要进行绘制,我们使用 PyQt4 工具包提供的绘制 API。
绘图是在paintEvent()
方法中完成的。 绘图代码放置在QtGui.QPainter
对象的begin()
和end()
方法之间。 它在小部件和其他绘图设备上执行低级绘图。
绘制文字
我们首先在窗口的客户区域上绘制一些 Unicode 文本。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example, we draw text in Russian azbuka.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Draw text')
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
def drawText(self, event, qp):
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们以西里尔字母绘制一些文本。 文本在垂直和水平方向上对齐。
def paintEvent(self, event):
...
绘制是在绘画事件中完成的。
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
QtGui.QPainter
类负责所有低级绘图。 所有绘图方法都在begin()
和end()
方法之间。 实际绘图将委托给drawText()
方法。
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
在这里,我们定义了用于绘制文本的笔和字体。
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
drawText()
方法在窗口上绘制文本。 绘画事件的rect()
方法返回需要更新的矩形。
图:绘制文本
绘制点
点是可以绘制的最简单的图形对象。 这是窗口上的一个小地方。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In the example, we draw randomly 1000 red points
on the window.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys, random
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Points')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()
def drawPoints(self, qp):
qp.setPen(QtCore.Qt.red)
size = self.size()
for i in range(1000):
x = random.randint(1, size.width()-1)
y = random.randint(1, size.height()-1)
qp.drawPoint(x, y)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们在窗口的客户区域上随机绘制了 1000 个红点。
qp.setPen(QtCore.Qt.red)
我们将笔设置为红色。 我们使用预定义的QtCore.Qt.red
颜色常量。
size = self.size()
每次我们调整窗口大小时,都会生成一个绘制事件。 我们使用size()
方法获得窗口的当前大小。 我们使用窗口的大小将点分布在整个窗口的客户区域中。
qp.drawPoint(x, y)
我们用drawPoint()
方法画点。
图:点
色彩
颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 有效的 RGB 值在 0 到 255 之间。我们可以通过多种方式定义颜色。 最常见的是 RGB 十进制值或十六进制值。 我们还可以使用代表红色,绿色,蓝色和 Alpha 的 RGBA 值。 在这里,我们添加了一些有关透明度的额外信息。 Alpha 值为 255 表示完全不透明,0 表示完全透明,例如颜色是不可见的。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This example draws three rectangles in three
different colours.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 350, 100)
self.setWindowTitle('Colours')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawRectangles(qp)
qp.end()
def drawRectangles(self, qp):
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
qp.setPen(color)
qp.setBrush(QtGui.QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
qp.setBrush(QtGui.QColor(255, 80, 0, 160))
qp.drawRect(130, 15, 90, 60)
qp.setBrush(QtGui.QColor(25, 0, 90, 200))
qp.drawRect(250, 15, 90, 60)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们绘制了 3 个彩色矩形。
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
在这里,我们使用十六进制符号定义颜色。
qp.setBrush(QtGui.QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
在这里,我们定义了一个画笔并绘制了一个矩形。 笔刷是用于绘制形状背景的基本图形对象。 drawRect()
方法接受四个参数。 前两个是轴上的 x 和 y 值。 第三个和第四个参数是矩形的宽度和高度。 该方法使用当前的笔和画笔绘制矩形。
图:颜色
QtGui.QPen
QtGui.QPen
是基本图形对象。 它用于绘制矩形,椭圆形,多边形或其他形状的线,曲线和轮廓。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example we draw 6 lines using
different pen styles.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 270)
self.setWindowTitle('Pen styles')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()
def drawLines(self, qp):
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(20, 40, 250, 40)
pen.setStyle(QtCore.Qt.DashLine)
qp.setPen(pen)
qp.drawLine(20, 80, 250, 80)
pen.setStyle(QtCore.Qt.DashDotLine)
qp.setPen(pen)
qp.drawLine(20, 120, 250, 120)
pen.setStyle(QtCore.Qt.DotLine)
qp.setPen(pen)
qp.drawLine(20, 160, 250, 160)
pen.setStyle(QtCore.Qt.DashDotDotLine)
qp.setPen(pen)
qp.drawLine(20, 200, 250, 200)
pen.setStyle(QtCore.Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
qp.drawLine(20, 240, 250, 240)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们绘制了六条线。 线条以六种不同的笔样式绘制。 有五种预定义的笔样式。 我们还可以创建自定义笔样式。 最后一行是使用自定义笔样式绘制的。
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)
我们创建一个QtGui.QPen
对象。 颜色是黑色。 宽度设置为 2 像素,以便我们可以看到笔样式之间的差异。 QtCore.Qt.SolidLine
是预定义的笔样式之一。
pen.setStyle(QtCore.Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
在这里,我们定义了自定义笔样式。 我们设置QtCore.Qt.CustomDashLine
笔样式并调用setDashPattern()
方法。 数字列表定义样式。 数字必须是偶数。 奇数定义笔划线,偶数空格。 数字越大,空格或笔划线越大。 我们的模式是 1px 笔划线,4px 间隔,5px 笔划线,4px 间隔等。
图:笔的样式
QtGui.QBrush
QtGui.QBrush
是基本图形对象。 它用于绘制图形形状的背景,例如矩形,椭圆形或多边形。 笔刷可以具有三种不同类型:预定义笔刷,渐变或纹理图案。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This example draws 9 rectangles in different
brush styles.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 355, 280)
self.setWindowTitle('Brushes')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawBrushes(qp)
qp.end()
def drawBrushes(self, qp):
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense1Pattern)
qp.setBrush(brush)
qp.drawRect(130, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense2Pattern)
qp.setBrush(brush)
qp.drawRect(250, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense3Pattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)
brush.setStyle(QtCore.Qt.DiagCrossPattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)
brush.setStyle(QtCore.Qt.Dense5Pattern)
qp.setBrush(brush)
qp.drawRect(130, 105, 90, 60)
brush.setStyle(QtCore.Qt.Dense6Pattern)
qp.setBrush(brush)
qp.drawRect(250, 105, 90, 60)
brush.setStyle(QtCore.Qt.HorPattern)
qp.setBrush(brush)
qp.drawRect(10, 195, 90, 60)
brush.setStyle(QtCore.Qt.VerPattern)
qp.setBrush(brush)
qp.drawRect(130, 195, 90, 60)
brush.setStyle(QtCore.Qt.BDiagPattern)
qp.setBrush(brush)
qp.drawRect(250, 195, 90, 60)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们绘制了九个不同的矩形。
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
我们定义一个笔刷对象。 我们将其设置为绘画器对象,并通过调用drawRect()
方法绘制矩形。
图:笔刷
在 PyQt4 教程的这一部分中,我们做了一些基本的绘图。
PyQt4 中的自定义小部件
PyQt4 具有丰富的小部件集。 但是,没有工具包可以向程序员提供在其应用中可能需要的所有小部件。 工具箱通常仅提供最常见的窗口小部件,例如按钮,文本窗口小部件或滑块。 如果需要更专业的小部件,我们必须自己创建它。
使用工具箱提供的绘图工具创建自定义窗口小部件。 有两种基本的可能性:程序员可以修改或增强现有的小部件,或者可以从头开始创建自定义小部件。
刻录小部件
这是一个小部件,我们可以在 Nero,K3B 或其他 CD/DVD 刻录软件中看到。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example, we create a custom widget.
author: Jan Bodnar
website: zetcode.com
last edited: October 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Communicate(QtCore.QObject):
updateBW = QtCore.pyqtSignal(int)
class BurningWidget(QtGui.QWidget):
def __init__(self):
super(BurningWidget, self).__init__()
self.initUI()
def initUI(self):
self.setMinimumSize(1, 30)
self.value = 75
self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
def setValue(self, value):
self.value = value
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawWidget(qp)
qp.end()
def drawWidget(self, qp):
font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
qp.setFont(font)
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
if self.value >= 700:
qp.setPen(QtGui.QColor(255, 255, 255))
qp.setBrush(QtGui.QColor(255, 255, 184))
qp.drawRect(0, 0, full, h)
qp.setPen(QtGui.QColor(255, 175, 175))
qp.setBrush(QtGui.QColor(255, 175, 175))
qp.drawRect(full, 0, till-full, h)
else:
qp.setPen(QtGui.QColor(255, 255, 255))
qp.setBrush(QtGui.QColor(255, 255, 184))
qp.drawRect(0, 0, till, h)
pen = QtGui.QPen(QtGui.QColor(20, 20, 20), 1,
QtCore.Qt.SolidLine)
qp.setPen(pen)
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawRect(0, 0, w-1, h-1)
j = 0
for i in range(step, 10*step, step):
qp.drawLine(i, 0, i, 5)
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
j = j + 1
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
sld.setFocusPolicy(QtCore.Qt.NoFocus)
sld.setRange(1, 750)
sld.setValue(75)
sld.setGeometry(30, 40, 150, 30)
self.c = Communicate()
self.wid = BurningWidget()
self.c.updateBW[int].connect(self.wid.setValue)
sld.valueChanged[int].connect(self.changeValue)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.wid)
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.setGeometry(300, 300, 390, 210)
self.setWindowTitle('Burning widget')
self.show()
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有一个QtGui.QSlider
和一个自定义小部件。 滑块控制自定义窗口小部件。 此小部件以图形方式显示了介质的总容量和可供我们使用的可用空间。 我们的自定义窗口小部件的最小值为1
,最大值为750
。 如果达到700
值,我们将开始绘制红色。 这通常表示过度燃烧。
刻录小部件位于窗口的底部。 这可以通过使用一个QtGui.QHBoxLayout
和一个QtGui.QVBoxLayout
来实现。
class BurningWidget(QtGui.QWidget):
def __init__(self):
super(BurningWidget, self).__init__()
它基于QtGui.QWidget
小部件的刻录小部件。
self.setMinimumSize(1, 30)
我们更改小部件的最小大小(高度)。 默认值对我们来说有点小。
font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
qp.setFont(font)
我们使用的字体比默认字体小。 这更适合我们的需求。
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
我们动态绘制小部件。 窗口越大,刻录的窗口小部件越大,反之亦然。 这就是为什么我们必须计算在其上绘制自定义窗口小部件的窗口小部件的大小的原因。 till
参数确定要绘制的总大小。 该值来自滑块小部件。 它占整个面积的一部分。 full
参数确定我们开始用红色绘制的点。 请注意,使用浮点算法可以提高绘图精度。
实际图纸包括三个步骤。 我们绘制黄色或红色和黄色矩形。 然后,我们绘制垂直线,将小部件分为几个部分。 最后,我们画出表示介质容量的数字。
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
我们使用字体指标来绘制文本。 我们必须知道文本的宽度,以便使其围绕垂直线居中。
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
当我们移动滑块时,将调用changeValue()
方法。 在方法内部,我们发送带有参数的自定义updateBW
信号。 该参数是滑块的当前值。 该值随后用于计算刻录小部件的容量。 然后将自定义窗口小部件重新粉刷。
图:刻录小部件
在 PyQt4 教程的这一部分中,我们创建了一个自定义小部件。
PyQt4 中的俄罗斯方块游戏
在本章中,我们将创建一个俄罗斯方块游戏克隆。
俄罗斯方块
俄罗斯方块游戏是有史以来最受欢迎的计算机游戏之一。 原始游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。此后,几乎所有版本的几乎所有计算机平台上都可以使用俄罗斯方块。
俄罗斯方块被称为下降块益智游戏。 在这个游戏中,我们有七个不同的形状,称为 tetrominoes :S 形,Z 形,T 形,L 形,线形,MirroredL 形和正方形。 这些形状中的每一个都形成有四个正方形。 形状从板上掉下来。 俄罗斯方块游戏的目的是移动和旋转形状,以使其尽可能地适合。 如果我们设法形成一行,则该行将被破坏并得分。 我们玩俄罗斯方块游戏,直到达到顶峰。
图:Tetrominoes
PyQt4 是旨在创建应用的工具包。 还有其他一些旨在创建计算机游戏的库。 不过,PyQt4 和其他应用工具包可用于创建简单的游戏。
创建计算机游戏是增强编程技能的好方法。
开发
我们的俄罗斯方块游戏没有图像,我们使用 PyQt4 编程工具包中提供的绘图 API 绘制四面体。 每个计算机游戏的背后都有一个数学模型。 俄罗斯方块也是如此。
游戏背后的一些想法:
- 我们使用
QtCore.QBasicTimer()
创建游戏周期。 - 绘制四方块。
- 形状以正方形为单位移动(而不是逐个像素移动)。
- 从数学上讲,棋盘是一个简单的数字列表。
该代码包括四个类别:Tetris
,Board
,Tetrominoe
和Shape
。 Tetris
类设置游戏。 Board
是编写游戏逻辑的地方。 Tetrominoe
类包含所有俄罗斯方块的名称,Shape
类包含俄罗斯方块的代码。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
This is a Tetris game clone.
author: Jan Bodnar
website: zetcode.com
last edited: October 2013
"""
import sys, random
from PyQt4 import QtCore, QtGui
class Tetris(QtGui.QMainWindow):
def __init__(self):
super(Tetris, self).__init__()
self.initUI()
def initUI(self):
self.tboard = Board(self)
self.setCentralWidget(self.tboard)
self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
self.tboard.start()
self.resize(180, 380)
self.center()
self.setWindowTitle('Tetris')
self.show()
def center(self):
screen = QtGui.QDesktopWidget().screenGeometry()
size = self.geometry()
self.move((screen.width()-size.width())/2,
(screen.height()-size.height())/2)
class Board(QtGui.QFrame):
msg2Statusbar = QtCore.pyqtSignal(str)
BoardWidth = 10
BoardHeight = 22
Speed = 300
def __init__(self, parent):
super(Board, self).__init__(parent)
self.initBoard()
def initBoard(self):
self.timer = QtCore.QBasicTimer()
self.isWaitingAfterLine = False
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.isStarted = False
self.isPaused = False
self.clearBoard()
def shapeAt(self, x, y):
return self.board[(y * Board.BoardWidth) + x]
def setShapeAt(self, x, y, shape):
self.board[(y * Board.BoardWidth) + x] = shape
def squareWidth(self):
return self.contentsRect().width() / Board.BoardWidth
def squareHeight(self):
return self.contentsRect().height() / Board.BoardHeight
def start(self):
if self.isPaused:
return
self.isStarted = True
self.isWaitingAfterLine = False
self.numLinesRemoved = 0
self.clearBoard()
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.newPiece()
self.timer.start(Board.Speed, self)
def pause(self):
if not self.isStarted:
return
self.isPaused = not self.isPaused
if self.isPaused:
self.timer.stop()
self.msg2Statusbar.emit("paused")
else:
self.timer.start(Board.Speed, self)
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
for i in range(Board.BoardHeight):
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoe.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
if self.curPiece.shape() != Tetrominoe.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
def keyPressEvent(self, event):
if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
super(Board, self).keyPressEvent(event)
return
key = event.key()
if key == QtCore.Qt.Key_P:
self.pause()
return
if self.isPaused:
return
elif key == QtCore.Qt.Key_Left:
self.tryMove(self.curPiece, self.curX - 1, self.curY)
elif key == QtCore.Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
elif key == QtCore.Qt.Key_Down:
self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
elif key == QtCore.Qt.Key_Up:
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
elif key == QtCore.Qt.Key_Space:
self.dropDown()
elif key == QtCore.Qt.Key_D:
self.oneLineDown()
else:
super(Board, self).keyPressEvent(event)
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
super(Board, self).timerEvent(event)
def clearBoard(self):
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoe.NoShape)
def dropDown(self):
newY = self.curY
while newY > 0:
if not self.tryMove(self.curPiece, self.curX, newY - 1):
break
newY -= 1
self.pieceDropped()
def oneLineDown(self):
if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
self.pieceDropped()
def pieceDropped(self):
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.setShapeAt(x, y, self.curPiece.shape())
self.removeFullLines()
if not self.isWaitingAfterLine:
self.newPiece()
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
numFullLines = numFullLines + len(rowsToRemove)
if numFullLines > 0:
self.numLinesRemoved = self.numLinesRemoved + numFullLines
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.isWaitingAfterLine = True
self.curPiece.setShape(Tetrominoe.NoShape)
self.update()
def newPiece(self):
self.curPiece = Shape()
self.curPiece.setRandomShape()
self.curX = Board.BoardWidth / 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
#print self.curY
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoe.NoShape)
self.timer.stop()
self.isStarted = False
self.msg2Statusbar.emit("Game over")
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoe.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
def drawSquare(self, painter, x, y, shape):
colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
color = QtGui.QColor(colorTable[shape])
painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
self.squareHeight() - 2, color)
painter.setPen(color.light())
painter.drawLine(x, y + self.squareHeight() - 1, x, y)
painter.drawLine(x, y, x + self.squareWidth() - 1, y)
painter.setPen(color.dark())
painter.drawLine(x + 1, y + self.squareHeight() - 1,
x + self.squareWidth() - 1, y + self.squareHeight() - 1)
painter.drawLine(x + self.squareWidth() - 1,
y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
class Tetrominoe(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
class Shape(object):
coordsTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
((0, -1), (0, 0), (1, 0), (1, 1)),
((0, -1), (0, 0), (0, 1), (0, 2)),
((-1, 0), (0, 0), (1, 0), (0, 1)),
((0, 0), (1, 0), (0, 1), (1, 1)),
((-1, -1), (0, -1), (0, 0), (0, 1)),
((1, -1), (0, -1), (0, 0), (0, 1))
)
def __init__(self):
self.coords = [[0,0] for i in range(4)]
self.pieceShape = Tetrominoe.NoShape
self.setShape(Tetrominoe.NoShape)
def shape(self):
return self.pieceShape
def setShape(self, shape):
table = Shape.coordsTable[shape]
for i in range(4):
for j in range(2):
self.coords[i][j] = table[i][j]
self.pieceShape = shape
def setRandomShape(self):
self.setShape(random.randint(1, 7))
def x(self, index):
return self.coords[index][0]
def y(self, index):
return self.coords[index][1]
def setX(self, index, x):
self.coords[index][0] = x
def setY(self, index, y):
self.coords[index][1] = y
def minX(self):
m = self.coords[0][0]
for i in range(4):
m = min(m, self.coords[i][0])
return m
def maxX(self):
m = self.coords[0][0]
for i in range(4):
m = max(m, self.coords[i][0])
return m
def minY(self):
m = self.coords[0][1]
for i in range(4):
m = min(m, self.coords[i][1])
return m
def maxY(self):
m = self.coords[0][1]
for i in range(4):
m = max(m, self.coords[i][1])
return m
def rotateLeft(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
def rotateRight(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, -self.y(i))
result.setY(i, self.x(i))
return result
def main():
app = QtGui.QApplication([])
tetris = Tetris()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
游戏进行了简化,以便于理解。 游戏启动后立即开始。 我们可以通过按 p
键暂停游戏。 Space
键将使俄罗斯方块立即下降到底部。 游戏以恒定速度进行,没有实现加速。 分数是我们已删除的行数。
self.tboard = Board(self)
self.setCentralWidget(self.tboard)
创建Board
类的实例,并将其设置为应用的中央窗口小部件。
self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
我们创建一个状态栏,在其中显示消息。 我们将显示三种可能的消息:已删除的行数,暂停的消息或游戏结束消息。 msg2Statusbar
是在Board
类中实现的自定义信号。 showMessage()
是一种内置方法,可在状态栏上显示一条消息。
self.tboard.start()
这条线启动了游戏。
class Board(QtGui.QFrame):
msg2Statusbar = QtCore.pyqtSignal(str)
...
创建自定义信号。 msg2Statusbar
是当我们要向状态栏写消息或乐谱时发出的信号。
BoardWidth = 10
BoardHeight = 22
Speed = 300
这些是Board's
类变量。 BoardWidth
和BoardHeight
以块为单位定义电路板的大小。 Speed
定义游戏的速度。 每 300 毫秒将开始一个新的游戏周期。
...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...
在initBoard()
方法中,我们初始化了一些重要的变量。 变量self.board
是从 0 到 7 的数字的列表。它表示各种形状的位置以及板上形状的其余部分。
def shapeAt(self, x, y):
return self.board[(y * Board.BoardWidth) + x]
shapeAt()
方法确定给定块上的形状类型。
def squareWidth(self):
return self.contentsRect().width() / Board.BoardWidth
电路板可以动态调整大小。 结果,块的大小可能改变。 squareWidth()
计算单个正方形的宽度(以像素为单位)并将其返回。 Board.BoardWidth
是板的大小,以块为单位。
for i in range(Board.BoardHeight):
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoe.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
游戏的绘图分为两个步骤。 在第一步中,我们绘制所有形状或已放置到板底部的形状的其余部分。 所有正方形都记在self.board
列表变量中。 使用shapeAt()
方法访问该变量。
if self.curPiece.shape() != Tetrominoe.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
下一步是下落的实际零件的图纸。
elif key == QtCore.Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
在keyPressEvent()
方法中,我们检查按键是否按下。 如果按向右箭头键,我们将尝试将棋子向右移动。 我们说尝试,因为该部分可能无法移动。
elif key == QtCore.Qt.Key_Up:
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
向上
方向键将使下降片向左旋转。
elif key == QtCore.Qt.Key_Space:
self.dropDown()
空格键
将立即将下降的片段降到底部。
elif key == QtCore.Qt.Key_D:
self.oneLineDown()
按下 d
键,乐曲将向下移动一个格。 它可以用来加速一块的下落。
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoe.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
在tryMove()
方法中,我们尝试移动形状。 如果形状在板的边缘或与其他零件相邻,则返回False
。 否则,我们将当前的下降片放到新位置。
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
super(Board, self).timerEvent(event)
在计时器事件中,我们要么在上一个下降到底部之后创建一个新作品,要么将下降的一块向下移动一行。
def clearBoard(self):
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoe.NoShape)
clearBoard()
方法通过在板的每个块上设置Tetrominoe.NoShape
来清除板。
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
numFullLines = numFullLines + len(rowsToRemove)
...
如果片段触底,我们将调用removeFullLines()
方法。 我们找出所有实线并将其删除。 通过将所有行移动到当前全行上方来将其向下移动一行来实现。 请注意,我们颠倒了要删除的行的顺序。 否则,它将无法正常工作。 在我们的情况下,我们使用天真重力。 这意味着碎片可能会漂浮在空的间隙上方。
def newPiece(self):
self.curPiece = Shape()
self.curPiece.setRandomShape()
self.curX = Board.BoardWidth / 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoe.NoShape)
self.timer.stop()
self.isStarted = False
self.msg2Statusbar.emit("Game over")
newPiece()
方法随机创建一个新的俄罗斯方块。 如果棋子无法进入其初始位置,则游戏结束。
class Tetrominoe(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
Tetrominoe
类包含所有可能形状的名称。 我们还有一个NoShape
用于空白。
Shape
类保存有关俄罗斯方块的信息。
class Shape(object):
coordsTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
...
)
...
coordsTable
元组保存我们的零件的所有可能的坐标值。 这是一个模板,所有零件均从该模板获取其坐标值。
self.coords = [[0,0] for i in range(4)]
创建后,我们将创建一个空坐标列表。 该列表将保存俄罗斯方块的坐标。
图:坐标
上面的图片将帮助您更多地了解坐标值。 例如,元组(0,-1),(0,0),(-1,0),(-1,-1)表示 Z 形。 该图说明了形状。
def rotateLeft(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
rotateLeft()
方法将一块向左旋转。 正方形不必旋转。 这就是为什么我们只是将引用返回到当前对象。 将创建一个新零件,并将其坐标设置为旋转零件的坐标。
图:俄罗斯方块
这是 PyQt4 中的俄罗斯方块游戏。
Qt4 教程
这是 Qt4 教程。 在本教程中,您将学习使用 Qt4 和 C++ 进行 GUI 编程的基础。 Qt4 教程适合初学者和中级程序员。 Qt5 教程是本教程的后续版本。 请注意,本教程使用 C++ 11 的功能。
目录
Qt
Qt 是一个跨平台的应用开发框架。 用 Qt 开发的一些知名应用是 KDE,Opera,Google Earth 和 Skype。 Qt 于 1995 年 5 月首次公开发布。它具有双重许可。 这意味着,它可以用于创建开源应用以及商业应用。 Qt 工具箱是一个非常强大的工具箱。 它在开源社区中已经建立。
相关教程
ZetCode 提供了有关 Qt 库的其他语言绑定的教程: PyQt4 教程, PyQt5 教程和 Ruby Qt 教程。 Qt Quick 教程是 Qt Quick 的入门教程。
Qt4 工具包简介
在 Qt4 教程的这一部分中,我们将介绍 Qt4 库。 我们将安装 Qt4 库并创建我们的第一个小型 Qt4 应用。
Qt 最初是由挪威软件公司 Trolltech 开发的。 2008 年,该公司被诺基亚收购。 2012 年 8 月,一家芬兰开发公司 Digia 从诺基亚那里收购了 Qt 软件技术。 同时,创建了一个 Qt 项目,其中开源 Qt 的开发继续进行。 开源 Qt 工具包的网站可以在 qt.io 中找到。 目前,由 Digia 的子公司 Qt 公司和开放源代码治理下的 Qt 项目(包括个人开发者和公司)共同开发 Qt。
Qt
Qt 是一个跨平台的应用开发框架。 使用 Qt 开发的一些知名应用是 KDE,Opera,Google Earth,Skype,VLC,Maya 或 Mathematica。 Qt 于 1995 年 5 月首次公开发布。它具有双重许可。 它可以用于创建开源应用以及商业应用。 Qt 工具箱是一个非常强大的工具箱。 它在开源社区中已经建立。 全世界有成千上万的开源开发者在使用 Qt。
下载并解压缩
我们转到 download.qt.io/official_releases/qt/ 页面。 (由于下载链接过去经常更改,因此您可能需要用 Google 搜索当前链接。)我们选择最新的 Qt 4.x 来源。 在创建本教程时,最新的数据是 Qt 4.8.7。 接下来,我们将从源代码安装 Qt。
$ ls qt-everywhere-opensource-src-4.8.7.tar.gz
qt-everywhere-opensource-src-4.8.7.tar.gz
从下载页面,我们下载 Qt4 源。 使用 tar 文件。 (我们为自己省了一些麻烦。ZIP 文件具有 Windows 行尾。)
$ tar -xzvf qt-everywhere-opensource-src-4.8.7.tar.gz
该命令会将所有文件解压缩到目录qt-everywhere-opensource-src-4.8.7
。
$ du -hs qt-everywhere-opensource-src-4.8.7/
741M qt-everywhere-opensource-src-4.8.7/
现在目录的大小为 741 MB。
$ cd qt-everywhere-opensource-src-4.8.7/
我们转到创建的目录。 现在是时候仔细阅读README
和INSTALL
文件了。 在那里,我们将找到详细的安装说明。 安装简单明了。
从源安装
我们以经典方式安装库。 在 Unix 系统上,软件的安装分为三个步骤。
- 配置
- 构建
- 安装
$ ./configure -prefix /usr/local/qt4
Which edition of Qt do you want to use ?
Type 'c' if you want to use the Commercial Edition.
Type 'o' if you want to use the Open Source Edition.
首先,我们运行配置脚本。 该脚本将询问我们是否需要 Qt4 库的商业版或开源版。 该脚本将为我们的机器类型配置库。 默认情况下,Qt 将安装在/usr/local/Trolltech/Qt-4.8.7/
目录中。 这可以通过配置脚本的-prefix
参数进行更改。 我们将库安装到/usr/local/qt4/
目录中。 请注意,此处的安装字有两个含义。 这是整个过程,包括所有三个步骤。 这也意味着“将文件移动到特定目录”,这是最后第三步。
This is the Open Source Edition.
You are licensed to use this software under the terms of
the Lesser GNU General Public License (LGPL) versions 2.1.
You are also licensed to use this software under the terms of
the GNU General Public License (GPL) versions 3.
Type '3' to view the GNU General Public License version 3.
Type 'L' to view the Lesser GNU General Public License version 2.1.
Type 'yes' to accept this license offer.
Type 'no' to decline this license offer.
Do you accept the terms of either license? yes
确认许可协议。
Qt is now configured for building. Just run 'make'.
Once everything is built, you must run 'make install'.
Qt will be installed into /usr/local/qt4.
To reconfigure, run 'make confclean' and 'configure'.
配置脚本完成后,我们会收到此消息。
$ make
我们使用make
命令开始构建过程。 Qt 工具箱的构建可能要花费几个小时。 这取决于处理器的能力。
最后一步是安装文件或将文件移动到目录中。
$ sudo make install
此命令完成安装过程。 该库现在安装在/usr/local/qt4/
目录中。
$ du -sh /usr/local/qt4/
483M /usr/local/qt4/
目录的大小为 483 MB。 如我们所见,Qt 是一个巨大的库。
我们要做的最后一件事是将 Qt4 路径添加到PATH
系统变量。 bash 用户需要编辑.profile
文件或.bashrc
文件。
$ PATH=/usr/local/qt4/bin:$PATH
$ export PATH
我们已经将 Qt4 库的bin
目录的路径添加到PATH
环境变量。 再次登录后,更改将处于活动状态。
从包安装
从包安装 Qt 更加容易。 Linux 包通常不包含最新的 Qt 版本。
$ sudo apt-get install qt4-dev-tools
上面的命令在基于 Debian 的 Linux 上安装 Qt4。
版本
我们的第一个程序将打印 Qt4 库的版本。
version.cpp
#include <QtCore>
#include <iostream>
int main() {
std::cout << "Qt version: " << qVersion() << std::endl;
}
qVersion()
函数在运行时以字符串形式返回 Qt 的版本号。
$ g++ -o version version.cpp -I/usr/local/qt4/include/QtCore -I/usr/local/qt4/include -L/usr/local/qt4/lib -lQtCore
上面的命令将编译示例。 请注意,您的 Qt4 库可能安装在其他位置。
$ ./version
Qt version: 4.8.6
本教程中使用的 Qt4 库的版本是 4.8.6。
测试一个小的 GUI 示例
最后,我们编写一个小应用。 该应用包含一个普通窗口。
simple.cpp
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();
return app.exec();
}
要构建此示例,我们使用qmake
工具。
$ qmake -project
$ qmake
$ make
如果 Qt4 安装目录不是PATH
变量的一部分,我们可以提供qmake
工具的完整路径。
$ /usr/local/qt4/bin/qmake -project
$ /usr/local/qt4/bin/qmake
$ make
图:简单 example
安装完成确定。
本章是 Qt4 库的简介。
Windows API 控件 III
在本章中,我们将结束有关 Windows API 控件的讨论。 我们将提到单选按钮,单选框,组合框和进度条。
RadioButton
和分组框
在这里,我们介绍两个控件。分组框是围绕一组控件的矩形。 这些通常是单选按钮。 分组框具有描述控件的标签。 此控件的目的是对某种程度上相关的控件进行分组。单选按钮是一种特殊的按钮,用户可以选择但不能清除。 它允许用户从一组选项中选择一个独占选项。
radio_buttons.c
#include <windows.h>
#define ID_BLUE 1
#define ID_YELLOW 2
#define ID_ORANGE 3
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hinst;
COLORREF g_color;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"GroupBox";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
g_hinst = hInstance;
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"GroupBox",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 170, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
HBRUSH hBrush, holdBrush;
HPEN hPen, holdPen;
switch(msg) {
case WM_CREATE:
CreateWindowW(L"Button", L"Choose colour",
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
10, 10, 120, 110, hwnd, (HMENU) 0, g_hinst, NULL);
CreateWindowW(L"Button", L"Blue",
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 30, 100, 30, hwnd, (HMENU) ID_BLUE , g_hinst, NULL);
CreateWindowW(L"Button", L"Yellow",
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 55, 100, 30, hwnd, (HMENU) ID_YELLOW , g_hinst, NULL);
CreateWindowW(L"Button", L"Orange",
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 80, 100, 30, hwnd, (HMENU) ID_ORANGE , g_hinst, NULL);
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
switch (LOWORD(wParam)) {
case ID_BLUE:
g_color = RGB(0, 76, 255);
break;
case ID_YELLOW:
g_color = RGB(255, 255, 0);
break;
case ID_ORANGE:
g_color = RGB(255, 123, 0);
break;
}
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hBrush = CreateSolidBrush(g_color);
hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
holdPen = SelectObject(hdc, hPen);
holdBrush = (HBRUSH) SelectObject(hdc, hBrush);
Rectangle(hdc, 160, 20, 260, 120);
SelectObject(hdc, holdBrush);
SelectObject(hdc, holdPen);
DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
在我们的示例中,我们有一个带有三个单选按钮的分组框。 通过单击单选按钮,我们为右侧的矩形选择背景色。
CreateWindowW(L"Button", L"Choose colour",
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
10, 10, 120, 110, hwnd, (HMENU) 0, g_hinst, NULL);
分组框是一种特殊的按BS_GROUPBOX
样式创建的按钮。
CreateWindowW(L"Button", L"Blue",
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 30, 100, 30, hwnd, (HMENU) ID_BLUE , g_hinst, NULL);
单选按钮也是BS_AUTORADIOBUTTON
样式的特殊按钮。
case ID_BLUE:
g_color = RGB(0, 76, 255);
break;
如果单击单选按钮,则将使用选定的颜色填充全局变量。 此变量用于创建填充矩形的画笔。
InvalidateRect(hwnd, NULL, TRUE);
我们使矩形(在本例中为整个窗口)无效,这将导致重绘客户区。 这将启动WM_PAINT
消息。 在WM_PAINT
消息期间,我们绘制矩形。 在 GDI 一章中更详细地说明了绘图。
图:GroupBox
中的单选按钮
ComboBox
组合框是编辑框或静态文本与列表的组合。 当我们需要从可用选项列表中选择一个项目时,将使用一个组合框。
combobox.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hinst;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Application";
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc ;
wc.hCursor = LoadCursor(0,IDC_ARROW);
g_hinst = hInstance;
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Combo box",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 270, 170, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
static HWND hwndCombo, hwndStatic;
const wchar_t *items[] = { L"FreeBSD", L"OpenBSD",
L"NetBSD", L"Solaris", L"Arch" };
switch(msg) {
case WM_CREATE:
hwndCombo = CreateWindowW(L"Combobox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,
10, 10, 120, 110, hwnd, NULL, g_hinst, NULL);
CreateWindowW(L"Button", L"Drop down",
WS_CHILD | WS_VISIBLE,
150, 10, 90, 25, hwnd, (HMENU) 1, g_hinst, NULL);
hwndStatic = CreateWindowW(L"Static", L"",
WS_CHILD | WS_VISIBLE,
150, 80, 90, 25, hwnd, NULL, g_hinst, NULL);
for (int i = 0; i < 4; i++ ) {
SendMessageW(hwndCombo, CB_ADDSTRING, 0, (LPARAM) items[i]);
}
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
SendMessage(hwndCombo, CB_SHOWDROPDOWN, (WPARAM) TRUE, 0);
}
if (HIWORD(wParam) == CBN_SELCHANGE) {
LRESULT sel = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
SetWindowTextW(hwndStatic, items[sel]);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
在我们的示例中,我们在窗口上放置了三个控件:一个组合框,一个按钮和一个静态文本。 静态文本显示组合框中当前选择的项目。 用于演示CBN_SELCHANGE
组合框消息。 该按钮以编程方式打开组合框。
hwndCombo = CreateWindowW(L"Combobox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,
10, 10, 120, 110, hwnd, NULL, g_hinst, NULL);
要创建一个组合框,我们使用L"Combobox"
窗口类。 CBS_DROPDOWN
标志创建一个下拉列表。
for (int i = 0; i < 4; i++ ) {
SendMessageW(hwndCombo, CB_ADDSTRING, 0, (LPARAM) items[i]);
}
我们用项目填充组合框。 要向组合框添加字符串,我们发送CB_ADDSTRING
消息。
if (HIWORD(wParam) == BN_CLICKED) {
SendMessage(hwndCombo, CB_SHOWDROPDOWN, (WPARAM) TRUE, 0);
}
单击该按钮将导致发送CB_SHOWDROPDOWN
消息,该消息将以编程方式调用下拉框。
如果从组合框中选择一个项目,则窗口过程将接收WM_COMMAND
消息,并在wParam
参数的高位字中带有通知消息CBN_SELCHANGE
。
if (HIWORD(wParam) == CBN_SELCHANGE) {
LRESULT sel = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
SetWindowTextW(hwndStatic, items[sel]);
}
我们找出当前选择的项目。 我们向组合框发送CB_GETCURSEL
消息。 该函数返回当前所选项目的索引。 我们将静态文本设置为当前选择的字符串。
图:组合框
进度条
进度条是当我们处理冗长的任务时使用的控件。 它具有动画效果,以便用户知道我们的任务正在进行中。
progressbar.c
#include <windows.h>
#include <commctrl.h>
#define ID_BUTTON 1
#define ID_TIMER 2
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateControls(HWND);
HWND hwndPrgBar;
HWND hbtn;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Application";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Progress bar",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 260, 170, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
static int i = 0;
switch(msg) {
case WM_CREATE:
CreateControls(hwnd);
break;
case WM_TIMER:
SendMessage(hwndPrgBar, PBM_STEPIT, 0, 0);
i++;
if (i == 150) {
KillTimer(hwnd, ID_TIMER);
SendMessageW(hbtn, WM_SETTEXT, (WPARAM) NULL, (LPARAM) L"Start");
i = 0;
}
break;
case WM_COMMAND:
if (i == 0) {
i = 1;
SendMessage(hwndPrgBar, PBM_SETPOS, 0, 0);
SetTimer(hwnd, ID_TIMER, 5, NULL);
SendMessageW(hbtn, WM_SETTEXT, (WPARAM) NULL, (LPARAM) L"In progress");
}
break;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void CreateControls(HWND hwnd) {
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_PROGRESS_CLASS;
InitCommonControlsEx(&icex);
hwndPrgBar = CreateWindowEx(0, PROGRESS_CLASS, NULL,
WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
30, 20, 190, 25, hwnd, NULL, NULL, NULL);
hbtn = CreateWindowW(L"Button", L"Start",
WS_CHILD | WS_VISIBLE,
85, 90, 85, 25, hwnd, (HMENU) 1, NULL, NULL);
SendMessage(hwndPrgBar, PBM_SETRANGE, 0, MAKELPARAM(0, 150));
SendMessage(hwndPrgBar, PBM_SETSTEP, 1, 0);
}
在我们的示例中,我们有一个进度条和一个按钮。 该按钮启动一个计时器,该计时器更新进度条。
hwndPrgBar = CreateWindowEx(0, PROGRESS_CLASS, NULL,
WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
30, 20, 190, 25, hwnd, NULL, NULL, NULL);
我们使用PROGRESS_CLASS
类名称和PBS_SMOOTH
样式创建进度条控件。
SendMessage(hwndPrgBar, PBM_SETRANGE, 0, MAKELPARAM(0, 150));
SendMessage(hwndPrgBar, PBM_SETSTEP, 1, 0);
我们设置进度条的范围及其步骤。
i = 1;
SendMessage(hwndPrgBar, PBM_SETPOS, 0, 0);
SetTimer(hwnd, ID_TIMER, 5, NULL);
当按下开始按钮时,我们将i
值设置为 1,设置进度条的初始位置,然后启动计时器。 计时器将定期向窗口过程发送WM_TIMER
消息,直到被杀死。
SendMessageW(hbtn, WM_SETTEXT, (WPARAM) NULL, (LPARAM) L"In progress");
当计时器进行时,我们更改按钮的标签。
case WM_TIMER:
SendMessage(hwndPrgBar, PBM_STEPIT, 0, 0);
i++;
if (i == 150) {
KillTimer(hwnd, ID_TIMER);
SendMessageW(hbtn, WM_SETTEXT, (WPARAM) NULL, (LPARAM) L"Start");
i = 0;
}
break;
当我们收到WM_TIMER
消息时,我们通过发送PBM_STEPIT
消息的一步来更新进度条。 当i
变量达到进度条的上限时,计时器将被终止。
图:进度条
在 Windows API 教程的这一部分中,我们已经完成了 Windows 控件的介绍。
Qt4 工具类
在 Qt4 C++ 编程教程的这一部分中,我们将讨论 Qt4 库中可用的工具类。
Qt4 库由一大堆帮助程序类组成,它们可以帮助程序员完成工作。 这些包括用于处理字符串,文件,XML 文件,流,数据库或网络的类。 在这里,我们只显示了整个湖面的一小滴。
Qt4 库可用于创建控制台和 GUI 应用。 在本章中,我们将说明基于控制台的应用中的一些帮助程序类。
将文本打印到控制台
这是一个简单的控制台应用。 该应用将一些文本放入控制台窗口。
console.cpp
#include <iostream>
int main() {
std::cout << "console application\n";
}
在第一个示例中,我们使用 STL(标准模板库)库打印文本。
console2.cpp
#include <QTextStream>
int main() {
QTextStream out(stdout);
out << "console application\n";
}
第二个示例显示了如何使用 Qt4 编程库打印文本。
Output
$ ./console
console application
QFile
QFile
是用于读取和写入文件的类。
在第一个示例中,我们将一行写入文件中。
write_line.cpp
#include <QTextStream>
#include <QFile>
int main() {
QFile data("myfile");
if (data.open(QFile::WriteOnly)) {
QTextStream out(&data);
out << "You make me want to be a better man." << endl;
}
}
在只写模式下创建名为myfile
的文件。 QTextStream
类用于在文件中插入一行。
Output
$ cat myfile
You make me want to be a better man.
下一个示例将文件的内容打印到控制台。 文本将使用匈牙利语,因此我们必须设置正确的编解码器。
szerelem
S a régi szeretőmér
mit nem cselekednék,
tengerből a vizet
kanállal lemerném.
S a tenger fenekéről
apró gyöngyöt szednék,
s a régi szeretőmnek
gyöngykoszorút kötnék.
这些是szerelem
文件的内容。
szerelem.cpp
#include <QTextStream>
#include <QFile>
int main() {
QFile data("szerelem");
QString line;
if (data.open(QFile::ReadOnly)) {
QTextStream in(&data);
QTextStream out(stdout);
out.setCodec("UTF-8");
in.setCodec("UTF-8");
do {
line = in.readLine();
out << line << endl;
} while (!line.isNull());
}
}
该示例以只读模式打开文件,并逐行打印其内容。
out.setCodec("UTF-8");
in.setCodec("UTF-8");
由于匈牙利语言包含基本 Latin1 字符集以外的字符,因此我们将编解码器设置为 UTF-8,它能够显示所有可能的字符。
do {
line = in.readLine();
out << line << endl;
} while (!line.isNull());
在此循环中,我们逐行读取和打印文件的内容。 readLine()
方法从流中读取下一行。 如果流已读取到文件末尾,则readLine()
将返回空QString
。
Output
$ ./szerelem
S a régi szeretőmér
mit nem cselekednék,
tengerből a vizet
kanállal lemerném.
S a tenger fenekéről
apró gyöngyöt szednék,
s a régi szeretőmnek
gyöngykoszorút kötnék.
QList
QList
是通用 Qt4 的容器之一。 它用于存储值列表,并提供基于索引的快速访问以及快速插入和删除。
mlist.cpp
#include <QTextStream>
#include <QList>
int main() {
QTextStream out(stdout);
QList<QString> list;
list << "Balzac" << "Tolstoy" << "Guldbrassen"
<< "London" << "Galsworthy" << "Sienkiewicz";
qSort(list);
for (int i = 0; i < list.size(); ++i) {
out << list.at(i) << endl;
}
}
在代码示例中,我们创建了一个伟大的小说家列表。 我们按字母顺序对列表进行排序,然后将其打印到控制台。
Output
$ ./mlist
Balzac
Galsworthy
Guldbrassen
London
Sienkiewicz
Tolstoy
QDir
QDir
类提供对目录结构及其内容的访问。
home_dir.cpp
#include <QTextStream>
#include <QDir>
int main() {
QTextStream out(stdout);
QString home = QDir::homePath();
out << home << endl;
}
本示例确定主目录并将其打印到控制台。
Output
$ ./home_dir
/home/janbodnar
在下面的示例中,我们使用QDir
类
filters.cpp
#include <QTextStream>
#include <QDir>
int main() {
QTextStream out(stdout);
QDir dir;
QStringList filters;
filters << "*.c" << "*.c~";
dir.setNameFilters(filters);
QFileInfoList list = dir.entryInfoList();
for (int i=0; i<list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
out << QString("%1").arg(fileInfo.fileName());
out << endl;
}
}
该代码示例确定当前目录中的所有文件,并对文件应用特定的过滤器。
Output
$ ls -F
anim* anim.c anim.c~ filters* sun.png
$ ./filters
anim.c
anim.c~
QTime
QTime
类提供时钟时间功能。
在以下示例中,我们将当前本地时间打印到控制台。
local_time.cpp
#include <QTextStream>
#include <QTime>
int main() {
QTextStream out(stdout);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString(Qt::LocalDate);
out << stime << endl;
}
请注意,不得将文件命名为time.cpp
。
Output
$ ./local_time
10:30:33 PM
QString
QString
类用于处理字符串。 这可能是 Qt4 编程库中可用的最重要的工具类。
concat.cpp
#include <QTextStream>
int main() {
QString a = "Disziplin ";
QString b = "ist ";
QString c = "Macht.\n";
QTextStream out(stdout);
out << a + b + c;
}
第一个示例连接三个字符串。
Output
$ ./concat
Disziplin ist Macht.
第二个示例通过一个接一个的附加文本来构建字符串。
append.cpp
#include <QTextStream>
int main() {
QString string = "Whether I shall ";
string.append("turn out to be the hero of my own life, \n");
string.append("or whether that station will be held by anybody else, \n");
string.append("these pages must show.\n");
QTextStream out(stdout);
out << string;
}
QString
的append()
方法将给定字符串附加到该字符串的末尾。
Output
$ ./append
Whether I shall turn out to be the hero of my own life,
or whether that station will be held by anybody else,
these pages must show.
下一个示例显示了参数替换。
arg.cpp
#include <QTextStream>
int main() {
QString string = "What if I gave you %1 red roses?";
int num = 21;
QTextStream out(stdout);
out << string.arg(num) << endl;
}
arg()
方法返回字符串的副本,在该字符串中,它用提供的整数值替换%1
标记。
Output
$ ./arg
What if I gave you 21 red roses?
以下示例显示了如何确定字符串的大小。
size.cpp
#include <QTextStream>
int main() {
QString string = "The history of my life.";
QTextStream out(stdout);
out << "The string has " + QString::number(string.size())
+ " characters." << endl;
}
size()
方法返回此字符串中的字符数。 为了将数字与前面的字符串连接起来,我们使用number()
方法,该方法返回与给定数字等效的字符串。
Output
$ ./size
The string has 23 characters.
最后一个示例是关于使字符串大写或小写。
uplow.cpp
#include <QTextStream>
int main() {
QString string = "The history of my life.";
QTextStream out(stdout);
out << string.toLower() << endl;
out << string.toUpper() << endl;
}
toLower()
方法返回字符串的小写副本,toUpper()
方法返回字符串的大写副本。
Output
$ ./uplow
the history of my life.
THE HISTORY OF MY LIFE.
在本章中,我们描述了 Qt4 中的一些工具类。
Qt4 中的字符串
在本章中,我们将使用字符串。 Qt4 具有用于处理字符串的QString
类。 它非常强大,并且具有多种方法。
QString
类提供 Unicode 字符串。 它将字符串存储为 16 位QChars
。 每个QChar
对应一个 Unicode 4.0 字符。 与许多其他编程语言中的字符串不同,可以修改QString
。
第一个例子
在第一个示例中,我们将使用QString
类的一些基本方法。
basic.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString a = "love";
a.append(" chess");
a.prepend("I ");
out << a << endl;
out << "The a string has " << a.count()
<< " characters" << endl;
out << a.toUpper() << endl;
out << a.toLower() << endl;
return 0;
}
在代码示例中,我们启动QString
。 我们附加并添加一些其他文字。 我们打印字符串的长度。 最后,我们以大写和小写形式显示修改后的字符串。
QString a = "love";
启动QString
。
a.append(" chess");
a.prepend("I ");
我们将文本附加和添加到初始字符串之前。 该字符串就地修改。
out << a << endl;
终端上印有“我爱象棋”。
out << "The a string has " << a.count()
<< " characters" << endl;
count()
方法返回字符串中的字符数。 length()
和size()
方法是等效的。
out << a.toUpper() << endl;
out << a.toLower() << endl;
这两个方法返回字符串的大写和小写副本。 他们不修改字符串,而是返回新的修改后的字符串副本。
Output
$ ./basic
I love chess
The a string has 12 characters
I LOVE CHESS
i love chess
初始化字符串
QString
可以通过多种方式初始化。
init.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString str1 = "The night train";
out << str1 << endl;
QString str2("A yellow rose");
out << str2 << endl;
std::string s1 = "A blue sky";
QString str3 = s1.c_str();
out << str3 << endl;
std::string s2 = "A thick fog";
QString str4 = QString::fromAscii(s2.data(), s2.size());
out << str4 << endl;
char s3[] = "A deep forest";
QString str5(s3);
out << str5 << endl;
return 0;
}
我们介绍了五种初始化QString
的方法。
QString str1 = "The night train";
这是在计算机语言中初始化字符串的传统方式。
QString str2("A yellow rose");
这是启动QString
的对象方式。
std::string s1 = "A blue sky";
QString str3 = s1.c_str();
我们有一个来自 C++ 标准库的字符串对象。 我们使用其c_str()
方法来生成以空字符结尾的字符序列。 该字符数组(字符串的经典 C 表示形式)可以分配给QString
变量。
std::string s2 = "A thick fog";
QString str4 = QString::fromAscii(s2.data(), s2.size());
在这些代码行中,我们将标准 C++ 字符串转换为QString
。 我们利用fromAscii()
方法。 它需要一个指向字符数组的指针。 指针通过std::string
的data()
方法返回。 第二个参数是std::string
的大小。
char s3[] = "A deep forest";
QString str5(s3);
这是一个 C 字符串。 它是一个字符数组。 QString
构造器之一可以将char
数组作为参数。
Output
$ ./init
The night train
A yellow rose
A blue sky
A thick fog
A deep forest
访问字符串元素
QString
是QChars
的序列。 可以使用[]
运算符或at()
方法访问字符串的元素。
access.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString a = "Eagle";
out << a[0] << endl;
out << a[4] << endl;
out << a.at(0) << endl;
if (a.at(5).isNull()) {
out << "Outside the range of the string" << endl;
}
return 0;
}
我们从特定的QString
打印一些单独的字符。
out << a[0] << endl;
out << a[4] << endl;
我们打印字符串的第一个和第五个元素。
out << a.at(0) << endl;
使用at()
方法,我们检索字符串的第一个字符。
if (a.at(5).isNull()) {
out << "Outside the range of the string" << endl;
}
如果我们尝试访问字符串字符范围之外的字符,则at()
方法返回null
。
Output
$ ./access
E
e
E
Outside the range of the string
字符串长度
有三种获取字符串长度的方法。 size()
,count()
和length()
方法。 所有人都做同样的事情。 它们返回指定字符串中的字符数。
length.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString s1 = "Eagle";
QString s2 = "Eagle\n";
QString s3 = "Eagle ";
QString s4 = QString::fromUtf8("орел");
out << s1.length() << endl;
out << s2.length() << endl;
out << s3.length() << endl;
out << s4.length() << endl;
return 0;
}
我们得到四个字符串的大小。
QString s2 = "Eagle\n";
QString s3 = "Eagle ";
这两个字符串中的每个字符串都有一个白色字符。
QString s4 = QString::fromUtf8("орел");
此字符串由俄语字母组成。
Output
$ ./length
5
6
6
4
从输出中我们可以看到length()
方法也计算了白色字符。 最后一个字符串包含 Unicode 字母,其中每个字母都存储为两个字符。
字符串插值
字符串插值是字符串的动态构建。 它允许我们用实际值替换特定的控制字符。 我们使用arg()
方法进行插值。
interpolation.cpp
#include <QTextStream>
int main() {
QTextStream out(stdout);
QString s1 = "There are %1 white roses";
int n = 12;
out << s1.arg(n) << endl;
QString s2 = "The tree is %1 m high";
double h = 5.65;
out << s2.arg(h) << endl;
QString s3 = "We have %1 lemons and %2 oranges";
int ln = 12;
int on = 4;
out << s3.arg(ln).arg(on) << endl;
return 0;
}
将要替换的标记以%
字符开头。 下一个字符是指定自变量的数字。 一个字符串可以有多个参数。 arg()
方法已重载,它可以接受整数,长数字,字符和QChar
等。
QString s1 = "There are %1 white roses";
int n = 12;
%1
是我们计划替换的标记。 我们定义了一个整数。
out << s1.arg(n) << endl;
arg()
方法采用整数。 %1
标记被n
变量的值替换。
QString s2 = "The tree is %1 m high";
double h = 5.65;
out << s2.arg(h) << endl;
这三行对于一个双数执行相同的操作。 正确的arg()
方法将被自动调用。
QString s3 = "We have %1 lemons and %2 oranges";
int ln = 12;
int on = 4;
out << s3.arg(ln).arg(on) << endl;
我们可以有多个控制字符。 %1
引用第一个参数,%2
引用第二个参数。 arg()
方法在连续的链中调用。
Output
$ ./interpolation
There are 12 white roses
The tree is 5.65 m high
We have 12 lemons and 4 oranges
子串
在进行文本处理时,我们需要找到普通字符串的子字符串。 我们有left()
,mid()
和right()
方法可供我们使用。
substrings.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString str = "The night train";
out << str.right(5) << endl;
out << str.left(9) << endl;
out << str.mid(4, 5) << endl;
QString str2("The big apple");
QStringRef sub(&str2, 0, 7);
out << sub.toString() << endl;
return 0;
}
我们将使用这三种方法查找给定字符串的一些子字符串。
out << str.right(5) << endl;
通过right()
方法,我们获得str
字符串的最右边五个字符。 将打印"train"
。
out << str.left(9) << endl;
使用left()
方法,我们获得str
字符串的最左边九个字符。 将打印"night"
。
out << str.mid(4, 5) << endl;
使用mid()
方法,我们从第 4 个位置开始得到 5 个字符。 打印"night"
。
QString str2("The big apple");
QStringRef sub(&str2, 0, 7);
QStringRef
类是QString
的只读版本。 在这里,我们创建str2
字符串一部分的QStringRef
。 第二个参数是位置,第三个参数是子字符串的长度。
Output
$ ./substrings
train
The night
night
The big
遍历字符串
QString
由QChars
组成。 我们可以遍历QString
以访问字符串的每个元素。
looping.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString str = "There are many stars.";
foreach (QChar qc, str) {
out << qc << " ";
}
out << endl;
for (QChar *it=str.begin(); it!=str.end(); ++it) {
out << *it << " " ;
}
out << endl;
for (int i = 0; i < str.size(); ++i) {
out << str.at(i) << " ";
}
out << endl;
return 0;
}
我们展示了通过QString
的三种方法。 在将字母打印到终端时,我们在字母之间添加空格字符。
foreach (QChar qc, str) {
out << qc << " ";
}
foreach
关键字是 C++ 语言的 Qt 扩展。 关键字的第一个参数是字符串元素,第二个参数是字符串。
for (QChar *it=str.begin(); it!=str.end(); ++it) {
out << *it << " " ;
}
在此代码中,我们使用迭代器遍历字符串。
for (int i = 0; i < str.size(); ++i) {
out << str.at(i) << " ";
}
我们计算字符串的大小,并使用at()
方法访问字符串元素。
Output
$ ./looping
T h e r e a r e m a n y s t a r s .
T h e r e a r e m a n y s t a r s .
T h e r e a r e m a n y s t a r s .
字符串比较
QString::compare()
静态方法用于比较两个字符串。 该方法返回一个整数。 如果返回的值小于零,则第一个字符串小于第二个字符串。 如果返回零,则两个字符串相等。 最后,如果返回的值大于零,则第一个字符串大于第二个字符串。 “较少”是指字符串的特定字符在字符表中位于另一个字符之前。 比较字符串的方法如下:比较两个字符串的第一个字符; 如果它们相等,则比较以下两个字符,直到找到一些不同的字符或发现所有字符都匹配为止。
comparing.cpp
#include <QTextStream>
#define STR_EQUAL 0
int main(void) {
QTextStream out(stdout);
QString a = "Rain";
QString b = "rain";
QString c = "rain\n";
if (QString::compare(a, b) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}
out << "In case insensitive comparison:" << endl;
if (QString::compare(a, b, Qt::CaseInsensitive) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}
if (QString::compare(b, c) == STR_EQUAL) {
out << "b, c are equal" << endl;
} else {
out << "b, c are not equal" << endl;
}
c.chop(1);
out << "After removing the new line character" << endl;
if (QString::compare(b, c) == STR_EQUAL) {
out << "b, c are equal" << endl;
} else {
out << "b, c are not equal" << endl;
}
return 0;
}
我们将使用compare()
方法进行区分大小写和不区分大小写的比较。
#define STR_EQUAL 0
为了更好的代码清晰度,我们定义STR_EQUAL
常量。
QString a = "Rain";
QString b = "rain";
QString c = "rain\n";
我们将比较这三个字符串。
if (QString::compare(a, b) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}
我们比较a
和b
字符串,它们不相等。 它们的第一个字符不同。
if (QString::compare(a, b, Qt::CaseInsensitive) == STR_EQUAL) {
out << "a, b are equal" << endl;
} else {
out << "a, b are not equal" << endl;
}
如果不区分大小写,则字符串相等。 Qt::CaseInsensitive
使比较大小写不敏感。
c.chop(1);
chop()
方法从c
字符串中删除最后一个字符。 现在b
和c
字符串相等。
Output
$ ./comparing
a, b are not equal
In case insensitive comparison:
a, b are equal
b, c are not equal
After removing the new line character
b, c are equal
转换字符串
字符串通常需要转换为其他数据类型,反之亦然。 toInt()
,toFloat()
和toLong()
是将字符串转换为整数,浮点数和长整数的三种QString
方法。 (还有更多这样的方法。)setNum()
方法将各种数字数据类型转换为字符串。 该方法已重载,并且自动调用了正确的方法。
Output
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString s1 = "12";
QString s2 = "15";
QString s3, s4;
out << s1.toInt() + s2.toInt() << endl;
int n1 = 30;
int n2 = 40;
out << s3.setNum(n1) + s4.setNum(n2) << endl;
return 0;
}
在示例中,我们将两个字符串转换为整数并将其相加。 然后,我们将两个整数转换为字符串并将其连接起来。
out << s1.toInt() + s2.toInt() << endl;
toInt()
方法将字符串转换为整数。 我们添加两个转换为字符串的数字。
out << s3.setNum(n1) + s4.setNum(n2) << endl;
在这种情况下,setNum()
方法将整数转换为字符串。 我们连接两个字符串。
Output
$ ./converts
27
3040
字符
字符分为各种类别:数字,字母,空格和标点符号。 每个QString
都由QChar
组成。 QChar
具有isDigit()
,isLetter()
,isSpace()
和isPunct()
方法来执行作业。
letters.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
int digits = 0;
int letters = 0;
int spaces = 0;
int puncts = 0;
QString str = "7 white, 3 red roses.";
foreach(QChar s, str) {
if (s.isDigit()) {
digits++;
} else if (s.isLetter()) {
letters++;
} else if (s.isSpace()) {
spaces++;
} else if (s.isPunct()) {
puncts++;
}
}
out << QString("There are %1 characters").arg(str.count()) << endl;
out << QString("There are %1 letters").arg(letters) << endl;
out << QString("There are %1 digits").arg(digits) << endl;
out << QString("There are %1 spaces").arg(spaces) << endl;
out << QString("There are %1 punctuation characters").arg(puncts) << endl;
return 0;
}
在示例中,我们定义了一个简单的句子。 我们将计算句子中的数字,字母,空格和标点符号的数量。
int digits = 0;
int letters = 0;
int spaces = 0;
int puncts = 0;
我们为每个字符类别定义一个整数变量。
QString str = "7 white, 3 red roses.";
这是要检查的句子。
foreach(QChar s, str) {
if (s.isDigit()) {
digits++;
} else if (s.isLetter()) {
letters++;
} else if (s.isSpace()) {
spaces++;
} else if (s.isPunct()) {
puncts++;
}
}
我们使用foreach
关键字来浏览QString
。 每个元素都是QChar
。 我们使用QChar
类的方法来确定字符的类别。
out << QString("There are %1 characters").arg(str.count()) << endl;
out << QString("There are %1 letters").arg(letters) << endl;
out << QString("There are %1 digits").arg(digits) << endl;
out << QString("There are %1 spaces").arg(spaces) << endl;
out << QString("There are %1 punctuation characters").arg(puncts) << endl;
使用字符串插值,我们将数字打印到终端。
Output
$ ./letters
There are 21 characters
There are 13 letters
There are 2 digits
There are 4 spaces
There are 2 punctuation characters
修改字符串
某些方法(例如toLower()
方法)返回原始字符串的新修改副本。 其他方法可就地修改字符串。 我们将介绍其中的一些。
modify.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString str = "Lovely";
str.append(" season");
out << str << endl;
str.remove(10, 3);
out << str << endl;
str.replace(7, 3, "girl");
out << str << endl;
str.clear();
if (str.isEmpty()) {
out << "The string is empty" << endl;
}
return 0;
}
我们描述了四种就地修改字符串的方法。
str.append(" season");
append()
方法在字符串的末尾添加一个新字符串。
str.remove(10, 3);
remove()
方法从位置 10 开始从字符串中删除 3 个字符。
str.replace(7, 3, "girl");
replace()
方法用指定的字符串替换从7
位置开始的3
字符。
str.clear();
clear()
方法清除字符串。
Output
$ ./modify
Lovely season
Lovely sea
Lovely girl
The string is empty
对齐字符串
拥有整洁的输出是常见的要求。 我们可以使用leftJustified()
和rightJustified()
方法来对齐字符串。
right_align.cpp
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QString field1 = "Name: ";
QString field2 = "Occupation: ";
QString field3 = "Residence: ";
QString field4 = "Marital status: ";
int width = field4.size();
out << field1.rightJustified(width, ' ') << "Robert\n";
out << field2.rightJustified(width, ' ') << "programmer\n";
out << field3.rightJustified(width, ' ') << "New York\n";
out << field4.rightJustified(width, ' ') << "single\n";
return 0;
}
该示例将字段字符串向右对齐。
int width = field4.size();
我们计算最宽字符串的大小。
out << field1.rightJustified(width, ' ') << "Robert\n";
rightJustified()
方法返回具有width
字符的字符串。 如果字符串较短,则其余部分将使用提供的字符填充。 在我们的情况下,它是一个空格字符。
Output
$ ./rightalign
Name: Robert
Occupation: programmer
Residence: New York
Marital status: single
在本章中,我们使用了 Qt4 中的字符串。
Qt4 中的日期和时间
在 Qt4 C++ 编程教程的这一部分中,我们将讨论时间和日期。
Qt4 具有QDate
,QTime
和QDateTime
类以与日期和时间一起使用。 QDate
是用于使用公历中的日历日期的类。 它具有确定日期,比较或操纵日期的方法。 QTime
类使用时钟时间。 它提供了比较时间,确定时间的方法以及其他各种时间操纵方法。 QDateTime
是将QDate
和QTime
对象结合为一个对象的类。
初始化日期&时间对象
日期和时间对象可以通过两种基本方式进行初始化。 我们可以在对象构造器中对其进行初始化,也可以创建空对象并在以后用数据填充它们。
init.cpp
#include <QTextStream>
#include <QDate>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QDate dt1(2015, 4, 12);
out << "The date is " << dt1.toString() << endl;
QDate dt2;
dt2.setDate(2015, 3, 3);
out << "The date is " << dt2.toString() << endl;
QTime tm1(17, 30, 12, 55);
out << "The time is " << tm1.toString("hh:mm:ss.zzz") << endl;
QTime tm2;
tm2.setHMS(13, 52, 45, 155);
out << "The time is " << tm2.toString("hh:mm:ss.zzz") << endl;
}
我们以两种方式初始化日期和时间对象。
QDate dt1(2015, 4, 12);
QDate
对象构造器采用三个参数:年,月和日。
out << "The date is " << dt1.toString() << endl;
日期将打印到控制台。 我们使用toString()
方法将日期对象转换为字符串。
QTime tm2;
tm2.setHMS(13, 52, 45, 155);
空的QTime
对象已创建。 我们使用setHMS()
方法用数据填充对象。 参数是小时,分钟,秒和毫秒。
out << "The time is " << tm2.toString("hh:mm:ss.zzz") << endl;
我们将QTime
对象打印到控制台。 我们使用的特定格式还包括毫秒,默认情况下会省略毫秒。
Output
$ ./init
The date is Sun Apr 12 2015
The date is Tue Mar 3 2015
The time is 17:30:12.055
The time is 13:52:45.155
当前日期&时间
在以下示例中,我们将当前本地时间和日期打印到控制台。
curdatetime.cpp
#include <QTextStream>
#include <QTime>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
QTime ct = QTime::currentTime();
out << "Current date is: " << cd.toString() << endl;
out << "Current time is: " << ct.toString() << endl;
}
请注意,不得将文件命名为time.cpp
。
QDate cd = QDate::currentDate();
QDate::currentDate()
静态函数返回当前日期。
QTime ct = QTime::currentTime();
QTime::currentTime()
静态函数返回当前时间。
out << "Current date is: " << cd.toString() << endl;
out << "Current time is: " << ct.toString() << endl;
我们使用toString()
方法将日期和时间对象转换为字符串。
Output
$ ./curdatetime
Current date is: Wed Oct 14 2015
Current time is: 13:40:04
比较日期
关系运算符可用于比较日期。 我们可以比较他们在日历中的位置。
comparedates.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate dt1(2015, 4, 5);
QDate dt2(2014, 4, 5);
if (dt1 < dt2) {
out << dt1.toString() << " comes before "
<< dt2.toString() << endl;
} else {
out << dt1.toString() << " comes after "
<< dt2.toString() << endl;
}
}
该示例比较两个日期。
QDate dt1(2015, 4, 5);
QDate dt2(2014, 4, 5);
我们有两个不同的日期。
if (dt1 < dt2) {
out << dt1.toString() << " comes before "
<< dt2.toString() << endl;
} else {
out << dt1.toString() << " comes after "
<< dt2.toString() << endl;
}
我们使用小于(<
)的比较运算符比较日期,并确定其中哪个位于日历的更早位置。
Output
$ ./comparedates
Sun Apr 5 2015 comes after Sat Apr 5 2014
比较运算符也可以轻松用于QTime
和QDateTime
对象。
确定闰年
闰年是包含另一天的年份。 日历中额外一天的原因是天文日历年与日历年之间的差异。 日历年正好是 365 天,而天文学年(地球绕太阳公转的时间)是 365.25 天。 相差 6 个小时,这意味着在四年的时间里,我们一天中都没有。 因为我们希望日历与季节同步,所以每四年将 2 月增加一天。 (有例外。)在公历中,闰年的 2 月有 29 天,而不是通常的 28 天。该年持续 366 天,而不是通常的 365 天。
QDate::isLeapYear()
静态方法确定年份是否为闰年。
leapyear.cpp
#include <QTextStream>
#include <QDate>
int main() {
QTextStream out(stdout);
QList<int> years({2010, 2011, 2012, 2013, 2014, 2015, 2016});
foreach (int year, years) {
if (QDate::isLeapYear(year)) {
out << year << " is a leap year" << endl;
} else {
out << year << " is not a leap year" << endl;
}
}
}
在示例中,我们有一个年份列表。 我们每年检查是否是闰年。
QList<int> years({2010, 2011, 2012, 2013, 2014, 2015, 2016});
我们初始化一个年份列表。 这是 C++ 11 构造,因此,我们需要启用 C++ 11。 我们需要将CONFIG += c++11
,QMAKE_CXXFLAGS += -std=c++11
或QMAKE_CXXFLAGS += -std=c++0x
添加到.pro
文件。
foreach (int year, years) {
if (QDate::isLeapYear(year)) {
out << year << " is a leap year" << endl;
} else {
out << year << " is not a leap year" << endl;
}
}
我们遍历列表,确定给定的年份是否为闰年。 QDate::isLeapYear()
返回布尔值 true 或 false。
Output
$ ./leapyear
2010 is not a leap year
2011 is not a leap year
2012 is a leap year
2013 is not a leap year
2014 is not a leap year
2015 is not a leap year
2016 is a leap year
预定义的日期格式
Qt4 具有一些内置的日期格式。 QDate
对象的toString()
方法采用日期格式作为参数。 Qt4 使用的默认日期格式为Qt::TextDate
。
dateformats.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Today is " << cd.toString(Qt::TextDate) << endl;
out << "Today is " << cd.toString(Qt::ISODate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleDate) << endl;
out << "Today is " << cd.toString(Qt::LocaleDate) << endl;
}
在示例中,我们显示了当前日期的八种不同日期格式。
out << "Today is " << cd.toString(Qt::ISODate) << endl;
在这里,我们以Qt::ISODate
格式打印当前日期,这是用于显示日期的国际标准。
Output
$ ./dateformats
Today is Wed Oct 14 2015
Today is 2015-10-14
Today is 10/14/15
Today is Wednesday, October 14, 2015
Today is 10/14/15
Today is Wednesday, October 14, 2015
Today is 10/14/15
Today is 10/14/15
自定义日期格式
日期可以用多种其他格式表示。 在 Qt4 中,我们也可以创建自定义日期格式。 toString()
方法的另一个版本采用格式字符串,我们可以在其中使用各种格式说明符。 例如,d
指示符代表一天,而不是前导零。 dd
指示符代表一天,前导零。 下表列出了可用的日期格式表达式:
表达式 | 输出 |
---|---|
d |
不带前导零(1 到 31)的日期 |
dd |
带前导零(01 到 31)的日期 |
ddd |
本地化日期的缩写(例如,Mon 到Sun )。 使用QDate::shortDayName() 。 |
dddd |
本地化的长名称(例如,Monday 到Sunday )。 使用QDate::longDayName() 。 |
M |
不带前导零(1 到 12)的月份 |
MM |
带前导零(01 到 12)的月份 |
MMM |
本地化月份的缩写名称(例如,"Jan" 到"Dec" )。 使用QDate::shortMonthName() 。 |
MMMM |
本地化的长月份名称(例如,January 到December )。 使用QDate::longMonthName() 。 |
y |
两位数年份(00 到 99) |
yyyy |
四位数年份。 如果年份为负数,则还会附加一个减号。 |
Table: Date format specifiers
customdateformats.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Today is " << cd.toString("yyyy-MM-dd") << endl;
out << "Today is " << cd.toString("yy/M/dd") << endl;
out << "Today is " << cd.toString("d.M.yyyy") << endl;
out << "Today is " << cd.toString("d-MMMM-yyyy") << endl;
}
我们有四种自定义日期格式。
out << "Today is " << cd.toString("yyyy-MM-dd") << endl;
这是国际日期格式。 日期的各部分用笔划线分隔。 yyyy
是具有四个数字的年份。 MM
是月份,前导零(01 到 12)。 dd
是带前导零(01 到 31)的数字的日子。
out << "Today is " << cd.toString("yy/M/dd") << endl;
这是另一种常见的日期格式。 零件之间用斜杠(/
)字符分隔。 M
指示符代表一个月,不带前导零(1 到 12)。
out << "Today is " << cd.toString("d.M.yyyy") << endl;
此日期格式在斯洛伐克使用。 零件由点字符分隔。 日和月没有前导零。 首先是日期,然后是月份,最后是年份。
Output
$ ./customdateformats
Today is 2015-10-14
Today is 15/10/14
Today is 14.10.2015
Today is 14-October-2015
预定义的时间格式
时间具有一些预定义的格式。 标准格式说明符与日期格式中使用的说明符相同。 Qt4 使用的默认时间格式为Qt::TextDate
。
timeformats.cpp
#include <QTextStream>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QTime ct = QTime::currentTime();
out << "The time is " << ct.toString(Qt::TextDate) << endl;
out << "The time is " << ct.toString(Qt::ISODate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleDate) << endl;
out << "The time is " << ct.toString(Qt::LocaleDate) << endl;
}
在示例中,我们显示了当前时间的八种不同时间格式。
out << "The time is " << ct.toString(Qt::ISODate) << endl;
在这里,我们以Qt::ISODate
格式打印当前时间,这是用于显示时间的国际标准。
Output
$ ./timeformats
The time is 16:14:08
The time is 16:14:08
The time is 4:14 PM
The time is 4:14:08 PM CEST
The time is 4:14 PM
The time is 4:14:08 PM CEST
The time is 4:14 PM
The time is 4:14 PM
自定义时间格式
我们可以创建其他时间格式。 我们在使用时间格式说明符的地方构建自定义时间格式。 下表列出了可用的格式表达式。
表达式 | 输出 |
---|---|
h |
没有前导零的小时(如果显示 AM/PM,则为 0 到 23 或 1 到 12) |
hh |
带前导零的小时(如果显示 AM/PM,则为 00 到 23 或 01 到 12) |
H |
没有前导零的小时(0 到 23,即使有 AM/PM 显示) |
HH |
带前导零的小时(00 到 23,即使有 AM/PM 显示) |
m |
没有前导零(0 到 59)的分钟 |
mm |
分钟,前导零(00 到 59) |
s |
秒,不带前导零(0 到 59) |
ss |
秒,带有前导零(00 到 59) |
z |
不带前导零的毫秒数(0 到 999) |
zz |
以零开头的毫秒数(000 到 999) |
AP 或 A |
使用 AM/PM 显示。 AP 将被"AM" 或"PM" 替换。 |
ap 或 a |
使用 am/pm 显示。 ap 将被"am" 或"pm" 替换。 |
t |
时区(例如"CEST" ) |
Table: Time format specifiers
customtimeformats.cpp
#include <QTextStream>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QTime ct = QTime::currentTime();
out << "The time is " << ct.toString("hh:mm:ss.zzz") << endl;
out << "The time is " << ct.toString("h:m:s a") << endl;
out << "The time is " << ct.toString("H:m:s A") << endl;
out << "The time is " << ct.toString("h:m AP") << endl;
out << "The version of Qt4 is " << qVersion() << endl;
}
我们有四种自定义时间格式。
out << "The time is " << ct.toString("hh:mm:ss.zzz") << endl;
在这种格式下,我们具有小时,分钟和秒,前导零。 我们还以毫秒为单位加上前导零。
out << "The time is " << ct.toString("h:m:s a") << endl;
此时间格式说明符使用小时,分钟和秒(不带前导零),并添加 AM/PM 周期标识符。
Output
$ ./customtimeformats
The time is 16:15:07.690
The time is 4:15:7 pm
The time is 16:15:7 PM t
The time is 4:15 PM
检索工作日
dayOfWeek()
方法返回一个代表星期几的数字,其中 1 是星期一,7 是星期日。
weekday.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
int wd = cd.dayOfWeek();
out << "Today is " << QDate::shortDayName(wd) << endl;
out << "Today is " << QDate::longDayName(wd) << endl;
}
在示例中,我们打印当前工作日的简称和长名称。
QDate cd = QDate::currentDate();
我们得到当前日期。
int wd = cd.dayOfWeek();
从当前日期开始,我们得到星期几。
out << "Today is " << QDate::shortDayName(wd) << endl;
使用QDate::shortDayName()
静态方法,我们得到工作日的简称。
out << "Today is " << QDate::longDayName(wd) << endl;
使用QDate::longDayName()
静态方法,我们获得了工作日的长名称。
Output
$ ./weekday
Today is Wed
Today is Wednesday
天数
我们可以使用daysInMonth()
方法计算特定月份中的天数,并使用daysInYear()
方法计算一年中的天数。
nofdays.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QList<QString> months;
months.append("January");
months.append("February");
months.append("March");
months.append("April");
months.append("May");
months.append("June");
months.append("July");
months.append("August");
months.append("September");
months.append("October");
months.append("November");
months.append("December");
QDate dt1(2015, 9, 18);
QDate dt2(2015, 2, 11);
QDate dt3(2015, 5, 1);
QDate dt4(2015, 12, 11);
QDate dt5(2015, 1, 21);
out << "There are " << dt1.daysInMonth() << " days in "
<< months.at(dt1.month()-1) << endl;
out << "There are " << dt2.daysInMonth() << " days in "
<< months.at(dt2.month()-1) << endl;
out << "There are " << dt3.daysInMonth() << " days in "
<< months.at(dt3.month()-1) << endl;
out << "There are " << dt4.daysInMonth() << " days in "
<< months.at(dt4.month()-1) << endl;
out << "There are " << dt5.daysInMonth() << " days in "
<< months.at(dt5.month()-1) << endl;
out << "There are " << dt1.daysInYear() << " days in year "
<< QString::number(dt1.year()) << endl;
}
创建了五个日期对象。 我们计算这些月份和特定年份的天数。
QDate dt1(2015, 9, 18);
QDate dt2(2015, 2, 11);
QDate dt3(2015, 5, 1);
QDate dt4(2015, 12, 11);
QDate dt5(2015, 1, 21);
创建了五个QDate
对象。 每个代表不同的日期。
out << "There are " << dt1.daysInMonth() << " days in "
<< months.at(dt1.month()-1) << endl;
我们使用daysInMonth()
方法获取日期对象中的天数。
out << "There are " << dt1.daysInYear() << " days in year "
<< QString::number(dt1.year()) << endl;
在这里,我们使用日期对象的daysInYear()
方法获得一年中的天数。
Output
$ ./nofdays
There are 30 days in September
There are 28 days in February
There are 31 days in May
There are 31 days in December
There are 31 days in January
There are 365 days in year 2015
检查日期的有效性
有isValid()
方法可以检查日期是否有效。
isvalid.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QList<QDate> dates({QDate(2015, 5, 11), QDate(2015, 8, 1),
QDate(2015, 2, 30)});
for (int i=0; i < dates.size(); i++) {
if (dates.at(i).isValid()) {
out << "Date " << i+1 << " is a valid date" << endl;
} else {
out << "Date " << i+1 << " is not a valid date" << endl;
}
}
}
在示例中,我们检查了三天的有效性。
QList<QDate> dates({QDate(2015, 5, 11), QDate(2015, 8, 1),
QDate(2015, 2, 30)});
前两天有效。 第三个无效。 2 月有 28 或 29 天。
if (dates.at(i).isValid()) {
out << "Date " << i+1 << " is a valid date" << endl;
} else {
out << "Date " << i+1 << " is not a valid date" << endl;
}
根据isValid()
方法的结果,我们将有关日期有效性的消息打印到控制台。
Output
$ ./isvalid
Date 1 is a valid date
Date 2 is a valid date
Date 3 is not a valid date
到...的天数
我们可以轻松地从特定日期算起 n 天的日期。 我们使用addDays()
方法。 daysTo()
方法返回到选定日期的天数。
daystofrom.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate dt(2015, 5, 11);
QDate nd = dt.addDays(55);
QDate xmas(2015, 12, 24);
out << "55 days from " << dt.toString() << " is "
<< nd.toString() << endl;
out << "There are " << QDate::currentDate().daysTo(xmas)
<< " days till Christmas" << endl;
}
从 2015 年 5 月 11 日起,我们将在 55 天后获得日期。我们还将获得圣诞节前的天数。
QDate dt(2015, 5, 11);
QDate nd = dt.addDays(55);
addDays()
方法返回给定日期之后 55 天的QDate
。
QDate xmas(2015, 12, 24);
...
out << "There are " << QDate::currentDate().daysTo(xmas)
<< " days till Christmas" << endl;
我们使用daysTo()
方法来计算直到圣诞节的天数。
Output
$ ./daystofrom
55 days from Mon May 11 2015 is Sun Jul 5 2015
There are 71 days till Christmas
QDateTime
类
QDateTime
对象包含日历日期和时钟时间。 它是QDate
和QTime
类的组合。 它有许多相似的方法,用法与这两类相同。
datetime.cpp
#include <QTextStream>
#include <QDateTime>
int main(void) {
QTextStream out(stdout);
QDateTime cdt = QDateTime::currentDateTime();
out << "The current datetime is " << cdt.toString() << endl;
out << "The current date is " << cdt.date().toString() << endl;
out << "The current time is " << cdt.time().toString() << endl;
}
该示例检索当前日期时间。
out << "The current datetime is " << cdt.toString() << endl;
这行代码将当前日期时间打印到终端。
out << "The current date is " << cdt.date().toString() << endl;
此行使用date()
方法检索日期时间对象的日期部分。
Output
$ ./datetime
The current datetime is Wed Oct 14 17:02:52 2015
The current date is Wed Oct 14 2015
The current time is 17:02:52
朱利安日
儒略日是指儒略时期开始以来的连续天数。 它主要由天文学家使用。 不应将其与朱利安历法相混淆。 朱利安纪元开始于公元前 4713 年。 朱利安天数 0 被指定为从公元前 4713 年 1 月 1 日中午开始的日期。 朱利安天数(JDN)是自此时间段开始以来经过的天数。 任意时刻的儒略日期(JD)是前一个正午的儒略日编号加上该时刻以来的那一天的分数。 (Qt4 不会计算此分数。)除天文学外,军事和大型机程序经常使用儒略日期。
julianday.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Gregorian date for today: " << cd.toString(Qt::ISODate) << endl;
out << "Julian day for today: " << cd.toJulianDay() << endl;
}
在示例中,我们计算了今天的公历日期和儒略日。
out << "Julian day for today: " << cd.toJulianDay() << endl;
使用toJulianDay()
方法返回儒略日。
Output
$ ./julianday
Gregorian date for today: 2015-10-14
Julian day for today: 2457310
使用儒略日期,很容易进行计算。
battles.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate bordate(1812, 9, 7);
QDate slavdate(1805, 12, 2);
QDate cd = QDate::currentDate();
int j_today = cd.toJulianDay();
int j_borodino = bordate.toJulianDay();
int j_slavkov = slavdate.toJulianDay();
out << "Days since Slavkov battle: " << j_today - j_slavkov << endl;
out << "Days since Borodino battle: " << j_today - j_borodino << endl;
}
该示例计算自两次历史事件以来经过的天数。
QDate bordate(1812, 9, 7);
QDate slavdate(1805, 12, 2);
我们有拿破仑纪元的两次战斗。
int j_today = cd.toJulianDay();
int j_borodino = bordate.toJulianDay();
int j_slavkov = slavdate.toJulianDay();
我们计算了今天以及斯拉夫科夫和波罗底诺战役的儒略日。
out << "Days since Slavkov battle: " << j_today - j_slavkov << endl;
out << "Days since Borodino battle: " << j_today - j_borodino << endl;
我们计算了两次战斗以来经过的天数。
Output
$ date
Wed Oct 14 17:35:40 CEST 2015
$ ./battles
Days since Slavkov battle: 76652
Days since Borodino battle: 74181
自 2015 年 10 月 14 日起,斯拉夫科夫战役已经过去了 76652 天,而波罗底诺战役已经过去了 74181 天。
UTC 时间
我们的星球是一个球体。 它绕其轴旋转。 地球向东旋转。 因此,太阳在不同位置的不同时间升起。 地球大约每 24 小时旋转一次。 因此,世界被划分为 24 个时区。 在每个时区,都有一个不同的本地时间。 夏令时通常会进一步修改此本地时间。
实际需要一个全球时间。 全球时间可以避免时区和夏令时的混淆。 UTC(世界标准时间)被选为主要时间标准。 UTC 用于航空,天气预报,飞行计划,空中交通管制通关和地图。 与当地时间不同,UTC 不会随季节变化而变化。
utclocal.cpp
#include <QTextStream>
#include <QDateTime>
int main(void) {
QTextStream out(stdout);
QDateTime cdt = QDateTime::currentDateTime();
out << "Universal datetime: " << cdt.toUTC().toString() << endl;
out << "Local datetime: " << cdt.toLocalTime().toString() << endl;
}
在示例中,我们计算当前日期时间。 我们用 UTC 日期时间和本地日期时间表示日期时间。
out << "Universal datetime" << cdt.toUTC().toString() << endl;
toUTC()
方法用于获取 UTC 日期时间。
out << "Local datetime" << cdt.toLocalTime().toString() << endl;
toLocalTime()
用于获取本地日期时间。
Output
$ ./localutc
Universal datetime: Wed Oct 14 15:50:30 2015
Local datetime: Wed Oct 14 17:50:30 2015
该示例在布拉迪斯拉发运行,该站点具有中欧夏令时(CEST)-UTC + 2 小时。
Unix 纪元
纪元是选择作为特定纪元起源的时间瞬间。 例如,在西方基督教国家,时间从耶稣出生的第 0 天开始。 另一个例子是法国共和党日历,使用了十二年。 这个时期是 1792 年 9 月 22 日宣布的共和纪元的开始,即宣布成立第一共和国并废除君主制的那一天。 电脑也有自己的纪元。 最受欢迎的时间之一是 Unix 时间。 Unix 纪元是 1970 年 1 月 1 日 UTC 时间 00:00:00(或1970-01-01T00:00:00Z ISO8601
)。 计算机中的日期和时间是根据自该计算机或平台的定义时期以来经过的秒数或时钟滴答数确定的。
$ date +%s
1444838231
Unix date 命令可用于获取 Unix 时间。 在这个特定时刻,自 Unix 纪元以来已经过去了 1444838231 秒。
unixepoch.cpp
#include <QTextStream>
#include <QDateTime>
#include <ctime>
int main(void) {
QTextStream out(stdout);
time_t t = time(0);
out << t << endl;
QDateTime dt;
dt.setTime_t(t);
out << dt.toString() << endl;
QDateTime cd = QDateTime::currentDateTime();
out << cd.toTime_t() << endl;
}
在示例中,我们使用两个 Qt4 函数获取 Unix 时间并将其转换为人类可读的形式。
#include <ctime>
我们包括标准的 C++ 时间头文件。
time_t t = time(0);
out << t << endl;
使用标准的 C++ time()
命令,我们可以获得 Unix 时间。
QDateTime dt;
dt.setTime_t(t);
out << dt.toString() << endl;
setTime_t()
方法用于将 Unix 时间转换为日期时间,并将其格式化为易于阅读的格式。
QDateTime cd = QDateTime::currentDateTime();
out << cd.toTime_t() << endl;
Qt4 的toTime_t()
方法也可以用来获取 Unix 时间。
Output
$ ./unixepoch
1444838314
Wed Oct 14 17:58:34 2015
1444838314
在本章中,我们处理了时间和日期。
在 Qt4 中使用文件和目录
在 Qt4 C++ 编程教程的这一部分中,我们处理文件和目录。
QFile
,QDir
和QFileInfo
是在 Qt4 中处理文件的基本类。 QFile
提供了用于读取和写入文件的接口。 QDir
提供对目录结构及其内容的访问。 QFileInfo
提供与系统无关的文件信息,包括文件名和在文件系统中的位置,访问时间和修改时间,权限或文件所有权。
文件大小
在下一个示例中,我们确定文件的大小。
file_size.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: file_size file");
return 1;
}
QString filename = argv[1];
if (!QFile(filename).exists()) {
qWarning("The file does not exist");
return 1;
}
QFileInfo fileinfo(filename);
qint64 size = fileinfo.size();
QString str = "The size is: %1 bytes";
out << str.arg(size) << endl;
}
文件的大小由QFileInfo
的size()
方法确定。
QString filename = argv[1];
文件名作为参数传递给程序。
if (!QFile(filename).exists()) {
qWarning("The file does not exist");
return 1;
}
使用QFile
类的exists()
方法检查文件的存在。 如果不存在,我们将发出警告并终止程序。
QFileInfo fileinfo(filename);
创建QFileInfo
的实例。
qint64 size = fileinfo.size();
文件大小由size()
方法确定。 qint64
是在 Qt 支持的所有平台上保证为 64 位的类型。
QString str = "The size is: %1 bytes";
out << str.arg(size) << endl;
结果将打印到控制台。
$ ./file_size Makefile
The size is: 7483 bytes
这是示例的输出。
读取文件内容
为了读取文件的内容,我们必须首先打开文件进行读取。 然后创建一个输入文件流; 从该流中读取数据。
read_file.cpp
#include <QTextStream>
#include <QFile>
int main(void) {
QTextStream out(stdout);
QFile file("colours");
if (!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open file for reading");
return 1;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
out << line << endl;
}
file.close();
}
该示例从colours
文件读取数据。 该文件包含八种颜色的名称。
QFile file("colours");
创建QFile
对象的实例。
if (!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open file for reading");
return 1;
}
QFile
的open()
方法以只读模式打开文件。 如果该方法失败,我们将发出警告并终止程序。
QTextStream in(&file);
创建输入流。 QTextStream
接收文件句柄。 将从该流中读取数据。
while (!in.atEnd()) {
QString line = in.readLine();
out << line << endl;
}
在while
循环中,我们逐行读取文件,直到文件结束。 如果没有更多数据要从流中读取,则atEnd()
方法返回true
。 readLine()
方法从流中读取一行。
file.close();
close()
方法刷新数据并关闭文件句柄。
$ ./read_file colours
red
green
blue
yellow
brown
white
black
orange
这是示例的输出。
写入文件
为了写入文件,我们在写入模式下打开文件,创建定向到该文件的输出流,并使用写入运算符写入该流。
write2file.cpp
#include <QTextStream>
#include <QFile>
int main(void) {
QTextStream out(stdout);
QString filename = "distros";
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file);
out << "Xubuntu" << endl;
out << "Arch" << endl;
out << "Debian" << endl;
out << "Redhat" << endl;
out << "Slackware" << endl;
} else {
qWarning("Could not open file");
}
file.close();
}
该示例将五个 Linux 发行版的名称写入名为distros
的文件名。
QString filename = "distros";
QFile file(filename);
使用提供的文件名创建QFile
对象。
if (file.open(QIODevice::WriteOnly)) {
使用open()
方法,我们以只写方法打开文件。
QTextStream out(&file);
该行创建一个在文件句柄上运行的QTextStream
。 换句话说,要写入的数据流被定向到文件。
out << "Xubuntu" << endl;
out << "Arch" << endl;
out << "Debian" << endl;
out << "Redhat" << endl;
out << "Slackware" << endl;
数据通过<<
运算符写入。
file.close();
最后,文件句柄被关闭。
$ ./write2file
$ cat distros
Xubuntu
Arch
Debian
Redhat
Slackware
这是示例输出。
复制文件
复制文件时,我们将使用文件名或文件系统的不同位置来精确复制该文件。
copy_file.cpp
#include <QTextStream>
#include <QFile>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 3) {
qWarning("Usage: copyfile source destination");
return 1;
}
QString source = argv[1];
if (!QFile(source).exists()) {
qWarning("The source file does not exist");
return 1;
}
QString destin(argv[2]);
QFile::copy(source, destin);
}
该示例使用QFile::copy()
方法创建提供的文件的副本。
if (argc != 3) {
qWarning("Usage: copyfile source destination");
return 1;
}
该程序有两个参数。 如果未给出,则以警告消息结尾。
QString source = argv[1];
从程序的命令行参数中,我们获得源文件的名称。
if (!QFile(source).exists()) {
qWarning("The source file does not exist");
return 1;
}
我们使用QFile
的exists()
方法检查源文件是否存在。 如果它不存在,我们将以警告消息终止该程序。
QString destin(argv[2]);
我们得到目标文件名。
QFile::copy(source, destin);
使用QFile::copy()
方法复制源文件。 第一个参数是源文件名,第二个参数是目标文件名。
文件所有者和组
每个文件都有一个拥有者的用户。 文件也属于一组用户,以更好地管理和保护文件。
owner.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: owner file");
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QString group = fileinfo.group();
QString owner = fileinfo.owner();
out << "Group: " << group << endl;
out << "Owner: " << owner << endl;
}
该示例打印给定文件的所有者和主要组。
QFileInfo fileinfo(filename);
创建QFileInfo
类的实例。 它的参数是作为命令行参数给出的文件名。
QString group = fileinfo.group();
文件的主要组是通过QFileInfo
的group()
方法确定的。
QString owner = fileinfo.owner();
文件的所有者通过QFileInfo
的owner()
方法确定。
$ touch myfile
$ ./owner myfile
Group: janbodnar
Owner: janbodnar
系统会自动为新创建的文件提供用户的默认组。
上次读取,上次修改
文件存储有关上次读取或修改它们的信息。 要获取此信息,我们使用QFileInfo
类。
file_times.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDateTime>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: file_times file");
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QDateTime last_rea = fileinfo.lastRead();
QDateTime last_mod = fileinfo.lastModified();
out << "Last read: " << last_rea.toString() << endl;
out << "Last modified: " << last_mod.toString() << endl;
}
该示例打印给定文件的最后读取时间和最后修改时间。
QFileInfo fileinfo(filename);
QFileInfo
对象已创建。
QDateTime last_rea = fileinfo.lastRead();
lastRead()
方法返回上次读取(访问)文件的日期和时间。
QDateTime last_mod = fileinfo.lastModified();
lastModified()
方法返回上次修改文件的日期和时间。
$ ./file_times Makefile
Last read: Mon Oct 19 10:23:54 2015
Last modified: Mon Oct 19 10:23:33 2015
时间可以相同也可以不相同。
处理目录
QDir
类具有用于处理目录的方法。
dirs.cpp
#include <QTextStream>
#include <QDir>
int main(void) {
QTextStream out(stdout);
QDir dir;
if (dir.mkdir("mydir")) {
out << "mydir successfully created" << endl;
}
dir.mkdir("mydir2");
if (dir.exists("mydir2")) {
dir.rename("mydir2", "newdir");
}
dir.mkpath("temp/newdir");
}
在示例中,我们提供了四种使用目录的方法。
if (dir.mkdir("mydir")) {
out << "mydir successfully created" << endl;
}
mkdir()
方法创建一个目录。 如果目录创建成功,则返回true
。
if (dir.exists("mydir2")) {
dir.rename("mydir2", "newdir");
}
exists()
检查目录是否存在。 rename()
方法重命名目录。
dir.mkpath("temp/newdir");
mkpath()
一键创建一个新目录和所有必要的父目录。
特殊路径
文件系统中有一些特殊的路径。 例如主目录或根目录。 QDir
类用于获取系统中的特殊路径。
special_paths.cpp
#include <QTextStream>
#include <QDir>
int main(void) {
QTextStream out(stdout);
out << "Current path:" << QDir::currentPath() << endl;
out << "Home path:" << QDir::homePath() << endl;
out << "Temporary path:" << QDir::tempPath() << endl;
out << "Rooth path:" << QDir::rootPath() << endl;
}
该示例打印四个特殊路径。
out << "Current path:" << QDir::currentPath() << endl;
当前的工作目录使用QDir::currentPath()
方法检索。
out << "Home path:" << QDir::homePath() << endl;
使用QDir::homePath()
方法返回主目录。
out << "Temporary path:" << QDir::tempPath() << endl;
使用QDir::tempPath()
方法检索临时目录。
out << "Rooth path:" << QDir::rootPath() << endl;
根目录通过QDir::rootPath()
方法返回。
$ ./special_paths
Current path:/home/janbodnar/prog/qt4/files/special_paths
Home path:/home/janbodnar
Temporary path:/tmp
Rooth path:/
这是一个示例输出。
文件路径
文件由文件名和路径标识。 路径由文件名,基本名和后缀组成。
file_path.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
out << "Usage: file_times file" << endl;
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QString absPath = fileinfo.absoluteFilePath();
QString baseName = fileinfo.baseName();
QString compBaseName = fileinfo.completeBaseName();
QString fileName = fileinfo.fileName();
QString suffix = fileinfo.suffix();
QString compSuffix = fileinfo.completeSuffix();
out << "Absolute file path: " << absPath << endl;
out << "Base name: " << baseName << endl;
out << "Complete base name: " << compBaseName << endl;
out << "File name: " << fileName << endl;
out << "Suffix: " << suffix << endl;
out << "Whole suffix: " << compSuffix << endl;
}
在示例中,我们使用几种方法来打印文件路径及其给定文件名的一部分。
QFileInfo fileinfo(filename);
文件路径是使用QFileInfo
类标识的。
QString absPath = fileinfo.absoluteFilePath();
absoluteFilePath()
方法返回包含文件名的绝对路径。
QString baseName = fileinfo.baseName();
baseName()
方法返回基本名称-没有路径的文件名称。
QString compBaseName = fileinfo.completeBaseName();
completeBaseName()
方法返回完整的基本名称-文件中的所有字符,直到(但不包括)最后一个点字符。
QString fileName = fileinfo.fileName();
fileName()
方法返回文件名,该文件名是基本名称和扩展名。
QString suffix = fileinfo.suffix();
suffix()
方法返回文件结尾,该结尾由文件中所有字符组成,该文件之后(但不包括)最后一个点字符。
QString compSuffix = fileinfo.completeSuffix();
文件结尾可能由几部分组成。 completeSuffix()
方法返回第一个点字符之后(但不包括)后的文件中的所有字符。
$ ./file_path ~/Downloads/qt-everywhere-opensource-src-4.8.7.tar.gz
Absolute file path: /home/janbodnar/Downloads/qt-everywhere-opensource-src-4.8.7.tar.gz
Base name: qt-everywhere-opensource-src-4
Complete base name: qt-everywhere-opensource-src-4.8.7.tar
File name: qt-everywhere-opensource-src-4.8.7.tar.gz
Suffix: gz
Whole suffix: 8.7.tar.gz
这是程序的输出。
权限
文件系统中的文件具有保护系统。 文件带有标志,这些标志确定谁可以访问和修改它们。 QFile::permissions()
方法返回有关文件的 OR-ED 标志的枚举。
permissions.cpp
#include <QTextStream>
#include <QFile>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
out << "Usage: permissions file" << endl;
return 1;
}
QString filename = argv[1];
QFile::Permissions ps = QFile::permissions(filename);
QString fper;
if (ps & QFile::ReadOwner) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteOwner) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeOwner) {
fper.append('x');
} else {
fper.append('-');
}
if (ps & QFile::ReadGroup) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteGroup) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeGroup) {
fper.append('x');
} else {
fper.append('-');
}
if (ps & QFile::ReadOther) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteOther) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeOther) {
fper.append('x');
} else {
fper.append('-');
}
out << fper << endl;
}
该示例为给定文件生成类似 Unix 的权限列表。 有几种可能的用户类型:所有者,文件所属的组以及其余的称为其他用户。 前三个位置属于文件的所有者,后三个位置属于文件的组,后三个字符属于其他字符。 权限共有四种:读取(r
),写入或修改(w
),执行(x
)和无权限(-
)。
QFile::Permissions ps = QFile::permissions(filename);
通过QFile::permissions()
方法,我们获得了权限标志的枚举。
QString fper;
该字符串是根据给定的权限动态构建的。
if (ps & QFile::ReadOwner) {
fper.append('r');
} else {
fper.append('-');
}
我们使用&运算符确定返回的枚举是否包含QFile::ReadOwner
标志。
$ ./permissions Makefile
rw-rw-r--
文件所属的所有者和用户组有权读取和修改文件。 其他用户有权读取该文件。 由于该文件不是可执行文件,因此没有执行该文件的权限。
列出目录内容
在下面的示例中,我们显示给定目录的内容。
list_dir.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDir>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: list_dir directory");
return 1;
}
QString directory = argv[1];
QDir dir(directory);
if (!dir.exists()) {
qWarning("The directory does not exist");
return 1;
}
dir.setFilter(QDir::Files | QDir::AllDirs);
dir.setSorting(QDir::Size | QDir::Reversed);
QFileInfoList list = dir.entryInfoList();
int max_size = 0;
foreach (QFileInfo finfo, list) {
QString name = finfo.fileName();
int size = name.size();
if (size > max_size) {
max_size = size;
}
}
int len = max_size + 2;
out << QString("Filename").leftJustified(len).append("Bytes") << endl;
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString str = fileInfo.fileName().leftJustified(len);
str.append(QString("%1").arg(fileInfo.size()));
out << str << endl;
}
return 0;
}
要列出目录的内容,我们使用QDir
类及其entryInfoList()
方法。 文件列表按其大小反向排序,并且排列整齐。 有两列; 第一列包含文件名,第二列包含文件大小。
QDir dir(directory);
创建具有给定目录名称的QDir
对象。
dir.setFilter(QDir::Files | QDir::AllDirs);
setFilter()
方法指定entryInfoList()
方法应返回的文件类型。
dir.setSorting(QDir::Size | QDir::Reversed);
setSorting()
方法指定entryInfoList()
方法使用的排序顺序。
QFileInfoList list = dir.entryInfoList();
entryInfoList()
方法返回目录中所有文件和目录的QFileInfo
对象的列表,并通过过滤和排序方法进行过滤和排序。 QFileInfoList
是QList<QFileInfo>
的同义词。
foreach (QFileInfo finfo, list) {
QString name = finfo.fileName();
int size = name.size();
if (size > max_size) {
max_size = size;
}
}
我们遍历列表并确定最大文件名大小。 需要此信息来整齐地组织输出。
int len = max_size + 2;
我们在列的长度上再加上两个空格。
out << QString("Filename").leftJustified(len).append("Bytes") << endl;
在这里,我们打印列名。 leftJustified()
方法返回给定大小的字符串,该字符串左对齐并在其右边用填充字符(默认为空格)填充。
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString str = fileInfo.fileName().leftJustified(len);
str.append(QString("%1").arg(fileInfo.size()));
out << str << endl;
}
我们浏览文件列表并打印它们的名称和大小。 第一列保持对齐,并在必要时用空格填充; 仅将第二列添加到该行的末尾。
$ ./list_dir .
Filename Bytes
list_dir.pro 302
list_dir.cpp 1092
.. 4096
. 4096
Makefile 7456
list_dir.o 8848
list_dir 14687
这是示例的示例输出。
在本章中,我们使用文件和目录。
Qt4 中的第一个程序
在 Qt4 C++ 编程教程的这一部分中,我们创建了第一个程序。
我们显示一个应用图标,一个工具提示和各种鼠标光标。 我们在屏幕上居中放置一个窗口,并介绍信号和槽机制。
简单的例子
我们从一个非常简单的例子开始。
simple.cpp
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();
return app.exec();
}
该示例在屏幕上显示一个基本窗口。
#include <QApplication>
#include <QWidget>
我们包括必要的头文件。
QApplication app(argc, argv);
这是应用对象。 每个 Qt4 应用都必须创建此对象。 (控制台应用除外。)
QWidget window;
这是我们的主要小部件。
window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();
在这里,我们调整窗口小部件的大小并为我们的主窗口设置标题。 在这种情况下,QWidget
是我们的主窗口。 最后,我们在屏幕上显示小部件。
return app.exec();
exec()
方法启动应用的主循环。
图:简单 example
使窗口居中
如果我们不自己定位窗口,则窗口管理器将为我们定位。 在下一个示例中,我们将窗口居中。
center.cpp
#include <QApplication>
#include <QDesktopWidget>
#include <QWidget>
int main(int argc, char *argv[]) {
int WIDTH = 250;
int HEIGHT = 150;
int screenWidth;
int screenHeight;
int x, y;
QApplication app(argc, argv);
QWidget window;
QDesktopWidget *desktop = QApplication::desktop();
screenWidth = desktop->width();
screenHeight = desktop->height();
x = (screenWidth - WIDTH) / 2;
y = (screenHeight - HEIGHT) / 2;
window.resize(WIDTH, HEIGHT);
window.move( x, y );
window.setWindowTitle("Center");
window.show();
return app.exec();
}
监视器大小和分辨率类型很多。 为了使窗口居中,我们必须确定桌面的宽度和高度。 为此,我们使用QDesktopWidget
类。
QDesktopWidget *desktop = QApplication::desktop();
screenWidth = desktop->width();
screenHeight = desktop->height();
在这里,我们确定屏幕的宽度和高度。
x = (screenWidth - WIDTH) / 2;
y = (screenHeight - HEIGHT) / 2;
在这里,我们计算中心窗口的左上点。
window.resize(WIDTH, HEIGHT);
window.move( x, y );
我们调整窗口小部件的大小并将其移动到计算的位置。 请注意,我们必须首先调整窗口小部件的大小。 之后我们将其移动。
工具提示
工具提示是有关应用中项目的特定提示。 以下示例将演示如何在 Qt4 编程库中创建工具提示。
tooltip.cpp
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.move(300, 300);
window.setWindowTitle("ToolTip");
window.setToolTip("QWidget");
window.show();
return app.exec();
}
该示例显示了主QWidget
的工具提示。
window.setWindowTitle("ToolTip");
我们使用setToolTip()
方法为QWidget
小部件设置了工具提示。
图:工具提示
应用图标
在下一个示例中,我们显示应用图标。 大多数窗口管理器在标题栏的左上角以及任务栏上都显示图标。
icon.cpp
#include <QApplication>
#include <QWidget>
#include <QIcon>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.setWindowTitle("Icon");
window.setWindowIcon(QIcon("web.png"));
window.show();
return app.exec();
}
窗口左上角显示一个图标。
window.setWindowIcon(QIcon("web.png"));
为了显示图标,我们使用setWindowIcon()
方法和QIcon
类。 该图标是位于当前工作目录中的一个小 PNG 文件。
图:图标
游标
光标是一个小图标,指示鼠标指针的位置。 在下一个示例中,将显示我们可以在程序中使用的各种游标。
cursors.cpp
#include <QApplication>
#include <QWidget>
#include <QFrame>
#include <QGridLayout>
class Cursors : public QWidget
{
public:
Cursors(QWidget *parent = 0);
};
Cursors::Cursors(QWidget *parent)
: QWidget(parent) {
QFrame *frame1 = new QFrame(this);
frame1->setFrameStyle(QFrame::Box);
frame1->setCursor(Qt::SizeAllCursor);
QFrame *frame2 = new QFrame(this);
frame2->setFrameStyle(QFrame::Box);
frame2->setCursor(Qt::WaitCursor);
QFrame *frame3 = new QFrame(this);
frame3->setFrameStyle(QFrame::Box);
frame3->setCursor(Qt::PointingHandCursor);
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(frame1, 0, 0);
grid->addWidget(frame2, 0, 1);
grid->addWidget(frame3, 0, 2);
setLayout(grid);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Cursors window;
window.resize(350, 150);
window.setWindowTitle("Cursors");
window.show();
return app.exec();
}
在此示例中,我们使用三个框架。 每个帧都有一个不同的光标集。
QFrame *frame1 = new QFrame(this);
QFrame
小部件已创建。
frame1->setFrameStyle(QFrame::Box);
我们使用setFrameStyle()
方法设置框架样式。 这样我们可以看到框架的边界。
frame1->setCursor(Qt::SizeAllCursor);
使用setCursor()
方法将光标设置到该帧。
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(frame1, 0, 0);
grid->addWidget(frame2, 0, 1);
grid->addWidget(frame3, 0, 2);
setLayout(grid);
这会将所有帧分组为一行。 我们将在布局管理一章中详细讨论这一点。
QButton
在下一个代码示例中,我们在窗口上显示一个按钮。 通过单击按钮,我们关闭应用。
pushbutton.cpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
class MyButton : public QWidget {
public:
MyButton(QWidget *parent = 0);
};
MyButton::MyButton(QWidget *parent)
: QWidget(parent) {
QPushButton *quitBtn = new QPushButton("Quit", this);
quitBtn->setGeometry(50, 40, 75, 30);
connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyButton window;
window.resize(250, 150);
window.setWindowTitle("QPushButton");
window.show();
return app.exec();
}
在此代码示例中,我们首次使用信号和槽的概念。
QPushButton *quitBtn = new QPushButton("Quit", this);
quitBtn->setGeometry(50, 40, 75, 30);
我们创建一个新的QPushButton
。 我们手动调整其大小,然后使用setGeometry()
方法将其放置在窗口中。
connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
当我们点击按钮时,会产生一个clicked()
信号。 slot
是对信号做出反应的方法。 在我们的情况下,它是主应用对象的quit()
槽。 qApp
是指向应用对象的全局指针。 它在QApplication
头文件中定义。
图:QPushButton
PlusMinus
我们将在本节完成,展示小部件如何进行通信。 该代码分为三个文件。
plusminus.h
#pragma once
#include <QWidget>
#include <QApplication>
#include <QPushButton>
#include <QLabel>
class PlusMinus : public QWidget {
Q_OBJECT
public:
PlusMinus(QWidget *parent = 0);
private slots:
void OnPlus();
void OnMinus();
private:
QLabel *lbl;
};
这是示例的头文件。 在此文件中,我们定义了两个槽和一个标签小部件。
class PlusMinus : public QWidget {
Q_OBJECT
...
Q_OBJECT
宏必须包含在声明自己的信号和槽的类中。
plusminus.cpp
#include "plusminus.h"
#include <QGridLayout>
PlusMinus::PlusMinus(QWidget *parent)
: QWidget(parent) {
QPushButton *plsBtn = new QPushButton("+", this);
QPushButton *minBtn = new QPushButton("-", this);
lbl = new QLabel("0", this);
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(plsBtn, 0, 0);
grid->addWidget(minBtn, 0, 1);
grid->addWidget(lbl, 1, 1);
setLayout(grid);
connect(plsBtn, SIGNAL(clicked()), this, SLOT(OnPlus()));
connect(minBtn, SIGNAL(clicked()), this, SLOT(OnMinus()));
}
void PlusMinus::OnPlus() {
int val = lbl->text().toInt();
val++;
lbl->setText(QString::number(val));
}
void PlusMinus::OnMinus() {
int val = lbl->text().toInt();
val--;
lbl->setText(QString::number(val));
}
我们有两个按钮和一个标签小部件。 我们使用按钮增加或减少标签上显示的数字。
connect(plsBtn, SIGNAL(clicked()), this, SLOT(OnPlus()));
connect(minBtn, SIGNAL(clicked()), this, SLOT(OnMinus()));
在这里,我们将clicked()
信号连接到槽。
void PlusMinus::OnPlus() {
int val = lbl->text().toInt();
val++;
lbl->setText(QString::number(val));
}
在OnPlus()
方法中,我们确定标签的当前值。 标签窗口小部件显示一个字符串值,因此我们必须将其转换为整数。 我们增加数量并为标签设置新的文本。 我们将数字转换为字符串值。
main.cpp
#include "plusminus.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
PlusMinus window;
window.resize(300, 190);
window.setWindowTitle("Plus minus");
window.show();
return app.exec();
}
这是代码示例的主文件。
图:正负
在本章中,我们在 Qt4 中创建了第一个程序。
Qt4 中的菜单和工具栏
在 Qt4 C++ 编程教程的这一部分中,我们将讨论 Qt4 应用中的菜单和工具栏。
菜单栏是 GUI 应用的常见部分。 它是位于各个位置(称为菜单)的一组命令。 菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。
简单菜单
第一个示例显示了一个简单的菜单。
simplemenu.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class SimpleMenu : public QMainWindow {
public:
SimpleMenu(QWidget *parent = 0);
};
这是我们的代码示例的头文件。
simplemenu.cpp
#include "simplemenu.h"
#include <QMenu>
#include <QMenuBar>
SimpleMenu::SimpleMenu(QWidget *parent)
: QMainWindow(parent) {
QAction *quit = new QAction("&Quit", this);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(quit);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
}
我们有一个菜单栏,一个菜单和一个动作。 为了使用菜单,我们必须从QMainWindow
小部件继承。
QAction *quit = new QAction("&Quit", this);
此代码行创建一个QAction
。 每个QMenu
具有一个或多个动作对象。
QMenu *file;
file = menuBar()->addMenu("&File");
我们创建一个QMenu
对象。
file->addAction(quit);
我们使用addAction()
方法在菜单中放置一个动作。
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
当我们从菜单中选择此选项时,应用退出。
main.cpp
#include "simplemenu.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
SimpleMenu window;
window.resize(250, 150);
window.setWindowTitle("Simple menu");
window.show();
return app.exec();
}
主文件。
图:简单菜单
图标,快捷方式和分隔符
在以下示例中,我们将进一步增强以前的应用。 我们将图标添加到菜单,使用快捷方式和分隔符。
anothermenu.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class AnotherMenu : public QMainWindow {
public:
AnotherMenu(QWidget *parent = 0);
};
该示例的头文件。
anothermenu.cpp
#include "anothermenu.h"
#include <QMenu>
#include <QMenuBar>
AnotherMenu::AnotherMenu(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QAction *newa = new QAction(newpix, "&New", this);
QAction *open = new QAction(openpix, "&Open", this);
QAction *quit = new QAction(quitpix, "&Quit", this);
quit->setShortcut(tr("CTRL+Q"));
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(newa);
file->addAction(open);
file->addSeparator();
file->addAction(quit);
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, false);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
}
在我们的示例中,我们有一个包含三个动作的菜单。 如果我们选择退出操作,则实际上只有退出操作才可以执行某些操作。 我们还创建一个分隔符和CTRL+Q
快捷方式,以终止应用。
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
这些是我们在菜单中使用的图像。 请注意,某些桌面环境可能不会在菜单中显示图像。
QAction *newa = new QAction(newpix, "&New", this);
QAction *open = new QAction(openpix, "&Open", this);
QAction *quit = new QAction(quitpix, "&Quit", this);
在此代码中,我们将QAction
构造器与像素映射用作第一个参数。
quit->setShortcut(tr("CTRL+Q"));
在这里,我们创建键盘快捷键。 通过按下此快捷方式,我们将运行退出操作,该操作将退出应用。
file->addSeparator();
我们创建一个分隔符。 分隔符是一条水平线,使我们能够将菜单操作分组为一些逻辑组。
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, false);
在某些环境中,默认情况下不显示菜单图标。 在这种情况下,我们可以禁用Qt::AA_DontShowIconsInMenus
属性。
main.cpp
#include "anothermenu.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
AnotherMenu window;
window.resize(350, 200);
window.move(300, 300);
window.setWindowTitle("Another menu");
window.show();
return app.exec();
}
这是主文件。
图:另一个菜单示例
复选菜单
在下一个示例中,我们创建一个复选菜单。 这将是带有复选框的操作。 该选项可切换状态栏的可见性。
checkable.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Checkable : public QMainWindow {
Q_OBJECT
public:
Checkable(QWidget *parent = 0);
private slots:
void toggleStatusbar();
private:
QAction *viewst;
};
该示例的头文件。
checkable.cpp
#include "checkable.h"
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
Checkable::Checkable(QWidget *parent)
: QMainWindow(parent) {
viewst = new QAction("&View statusbar", this);
viewst->setCheckable(true);
viewst->setChecked(true);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(viewst);
statusBar();
connect(viewst, SIGNAL(triggered()), this, SLOT(toggleStatusbar()));
}
void Checkable::toggleStatusbar() {
if (viewst->isChecked()) {
statusBar()->show();
} else {
statusBar()->hide();
}
}
复选菜单项切换状态栏的可见性。
viewst = new QAction("&View statusbar", this);
viewst->setCheckable(true);
viewst->setChecked(true);
我们创建一个动作,并使用setCheckable()
方法对其进行检查。 setChecked()
方法进行检查。
if (viewst->isChecked()) {
statusBar()->show();
} else {
statusBar()->hide();
}
在toggleStatusbar()
方法内部,我们确定菜单项是否已选中,并相应地隐藏或显示状态栏。
main.cpp
#include "checkable.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Checkable window;
window.resize(250, 150);
window.setWindowTitle("Checkable menu");
window.show();
return app.exec();
}
这是主文件。
图:可选菜单
QToolBar
QToolBar
类提供了一个可移动面板,其中包含一组控件,这些控件可快速访问应用动作。
toolbar.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Toolbar : public QMainWindow {
Q_OBJECT
public:
Toolbar(QWidget *parent = 0);
};
该示例的头文件。
toolbar.cpp
#include "toolbar.h"
#include <QToolBar>
#include <QIcon>
#include <QAction>
Toolbar::Toolbar(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QToolBar *toolbar = addToolBar("main toolbar");
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
QAction *quit = toolbar->addAction(QIcon(quitpix),
"Quit Application");
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
}
要创建工具栏,我们从QMainWindow
小部件继承。
QToolBar *toolbar = addToolBar("main toolbar");
addToolBar()
方法创建一个工具栏并返回指向它的指针。
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
在这里,我们向工具栏添加了两个动作和一个分隔符。
main.cpp
#include "toolbar.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Toolbar window;
window.resize(300, 200);
window.setWindowTitle("QToolBar");
window.show();
return app.exec();
}
这是主文件。
图:QToolBar
应用框架
在 C++ Qt4 教程的这一部分的最后,我们将创建一个应用框架。 该示例主要基于QMainWindow
小部件。
skeleton.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Skeleton : public QMainWindow {
Q_OBJECT
public:
Skeleton(QWidget *parent = 0);
};
该示例的头文件。
skeleton.cpp
#include "skeleton.h"
#include <QToolBar>
#include <QIcon>
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QTextEdit>
Skeleton::Skeleton(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QAction *quit = new QAction("&Quit", this);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(quit);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
QToolBar *toolbar = addToolBar("main toolbar");
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
QAction *quit2 = toolbar->addAction(QIcon(quitpix),
"Quit Application");
connect(quit2, SIGNAL(triggered()), qApp, SLOT(quit()));
QTextEdit *edit = new QTextEdit(this);
setCentralWidget(edit);
statusBar()->showMessage("Ready");
}
在这里,我们创建一个菜单,一个工具栏和一个状态栏。
QTextEdit *edit = new QTextEdit(this);
setCentralWidget(edit);
我们创建一个QTextEdit
小部件,并将其放入QMainWindow
小部件的中央部分。
main.cpp
#include "skeleton.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Skeleton window;
window.resize(350, 250);
window.setWindowTitle("Application skeleton");
window.show();
return app.exec();
}
这是主文件。
图:应用骨架
在 Qt4 教程的这一部分中,我们介绍了菜单和工具栏。
Qt4 中的布局管理
在 Qt4 编程教程的这一部分中,我们将讨论小部件的布局管理。
典型的应用由各种小部件组成。 这些小部件放置在布局内。 程序员必须管理应用的布局。 在 Qt4 中,我们有两个选择:
- 绝对定位
- 布局管理器
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。 当使用绝对定位时,我们必须了解几件事。
- 如果我们调整窗口大小,则小部件的大小和位置不会改变。
- 在各种平台上,应用看起来有所不同(通常很差)。
- 在我们的应用中更改字体可能会破坏布局。
- 如果决定更改布局,则必须完全重做布局,这既繁琐又耗时。
在某些情况下,我们可能会使用绝对定位。 但是大多数情况下,在实际程序中,程序员使用布局管理器。
absolute.cpp
#include <QApplication>
#include <QDesktopWidget>
#include <QTextEdit>
class Absolute : public QWidget {
public:
Absolute(QWidget *parent = 0);
};
Absolute::Absolute(QWidget *parent)
: QWidget(parent) {
QTextEdit *ledit = new QTextEdit(this);
ledit->setGeometry(5, 5, 200, 150);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Absolute window;
window.setWindowTitle("Absolute");
window.show();
return app.exec();
}
setGeometry()
方法用于以绝对坐标将窗口小部件放置在窗口上。
QTextEdit *edit = new QTextEdit(this);
edit->setGeometry(5, 5, 200, 150);
我们创建一个QTextEdit
小部件并手动定位。 setGeometry()
方法有两件事:将窗口小部件定位到绝对坐标并调整窗口小部件的大小。
图:调整大小前
图:调整大小后
QVBoxLayout
QVBoxLayout
类垂直排列小部件。 使用addWidget()
方法将小部件添加到布局。
verticalbox.h
#pragma once
#include <QWidget>
class VerticalBox : public QWidget {
public:
VerticalBox(QWidget *parent = 0);
};
头文件。
verticalbox.cpp
#include "verticalbox.h"
#include <QVBoxLayout>
#include <QPushButton>
VerticalBox::VerticalBox(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);
QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *accounts = new QPushButton("Accounts", this);
accounts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *loans = new QPushButton("Loans", this);
loans->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *cash = new QPushButton("Cash", this);
cash->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *debts = new QPushButton("Debts", this);
debts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
vbox->addWidget(settings);
vbox->addWidget(accounts);
vbox->addWidget(loans);
vbox->addWidget(cash);
vbox->addWidget(debts);
setLayout(vbox);
}
在我们的示例中,我们有一个垂直布局管理器。 我们在其中放入了五个按钮。 我们使所有按钮都可以在两个方向上展开。
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);
我们创建QVBoxLayout
并在子窗口小部件之间设置 1px 的间距。
QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
我们创建一个按钮并为其设置大小策略。 子窗口小部件由布局管理器管理。 默认情况下,按钮水平扩展,垂直方向固定大小。 如果要更改它,我们将设置一个新的大小策略。 在我们的例子中,按钮可以向两个方向扩展。
vbox->addWidget(settings);
vbox->addWidget(accounts);
...
我们使用addWidget()
方法将子窗口小部件添加到布局管理器。
setLayout(vbox);
我们为窗口设置QVBoxLayout
管理器。
main.cpp
#include "verticalbox.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
VerticalBox window;
window.resize(240, 230);
window.setWindowTitle("VerticalBox");
window.show();
return app.exec();
}
主文件。
图:QVBoxLayout
按钮
在下面的示例中,我们在窗口的客户区域上显示两个按钮。 它们将位于窗口的右下角。
buttons.h
#pragma once
#include <QWidget>
#include <QPushButton>
class Buttons : public QWidget {
public:
Buttons(QWidget *parent = 0);
private:
QPushButton *okBtn;
QPushButton *applyBtn;
};
头文件。
buttons.cpp
#include "buttons.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
Buttons::Buttons(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
okBtn = new QPushButton("OK", this);
applyBtn = new QPushButton("Apply", this);
hbox->addWidget(okBtn, 1, Qt::AlignRight);
hbox->addWidget(applyBtn, 0);
vbox->addStretch(1);
vbox->addLayout(hbox);
}
假设我们想在窗口的右下角有两个按钮。
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
我们创建了两个框布局管理器:一个垂直框布局管理器和一个水平框布局管理器。
okBtn = new QPushButton("OK", this);
applyBtn = new QPushButton("Apply", this);
我们创建两个按钮。
hbox->addWidget(okBtn, 1, Qt::AlignRight);
hbox->addWidget(applyBtn, 0);
这些按钮位于水平布局管理器中。 使用addWidget()
方法。 这些按钮右对齐。 第一个参数是子窗口小部件。 第二个参数是拉伸因子,最后一个参数是对齐。 通过将“确定”按钮的拉伸因子设置为 1,我们在窗口的左侧到右侧留出一定的空间。 窗口小部件不会扩展到分配给它的所有空间。 最后,Qt::AlignRight
常数将小部件对齐到分配空间的右侧。
vbox->addStretch(1);
vbox->addLayout(hbox);
通过调用addStretch()
方法,我们在垂直框中放入了一个可扩展的空白区域。 然后,将水平框布局添加到垂直框布局。
main.cpp
#include <QApplication>
#include "buttons.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Buttons window;
window.resize(290, 170);
window.setWindowTitle("Buttons");
window.show();
return app.exec();
}
主文件。
图:按钮
嵌套布局
以下示例的目的是说明可以合并布局管理器。 通过甚至简单布局的组合,我们可以创建复杂的对话框或窗口。 要嵌套布局,我们利用addLayout()
方法。
layouts.h
#pragma once
#include <QWidget>
class Layouts : public QWidget {
public:
Layouts(QWidget *parent = 0);
};
头文件。
layouts.cpp
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include "layouts.h"
Layouts::Layouts(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout();
QHBoxLayout *hbox = new QHBoxLayout(this);
QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
setLayout(hbox);
}
在示例中,我们创建一个窗口,该窗口由四个按钮和一个列表小部件组成。 这些按钮被分组在一个垂直列中,并位于列表小部件的右侧。 如果我们调整窗口的大小,列表小部件也将被调整大小。
QVBoxLayout *vbox = new QVBoxLayout();
QVBoxLayout
将是按钮的列。
QHBoxLayout *hbox = new QHBoxLayout(this);
QHBoxLayout
将是小部件的基本布局。
QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
QListWidget
已创建。
QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);
在这里,我们创建四个按钮。
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);
创建带有四个按钮的垂直框。 我们在按钮之间留了一些空间。 注意,我们在垂直框的顶部和底部添加了一个拉伸因子。 这样,按钮可以垂直居中。
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
列表小部件和按钮的垂直框放置在水平框布局中。 addLayout()
方法用于将一个布局添加到另一个布局。
setLayout(hbox);
我们为父窗口设置基本布局。
main.cpp
#include <QApplication>
#include "layouts.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Layouts window;
window.setWindowTitle("Layouts");
window.show();
return app.exec();
}
主文件。
图:布局
QFormLayout
QFormLayout
是一个简单的布局管理器,用于管理输入窗口小部件及其相关标签的形式。 它以两列的形式布置其子项。 左列包含标签,右列包含输入窗口小部件,例如QLineEdit
或QSpinBox
。
form.h
#pragma once
#include <QWidget>
class FormEx : public QWidget {
public:
FormEx(QWidget *parent = 0);
};
这是标题文件管理器。
form.cpp
#include "form.h"
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
FormEx::FormEx(QWidget *parent)
: QWidget(parent) {
QLineEdit *nameEdit = new QLineEdit(this);
QLineEdit *addrEdit = new QLineEdit(this);
QLineEdit *occpEdit = new QLineEdit(this);
QFormLayout *formLayout = new QFormLayout;
formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
formLayout->addRow("Name:", nameEdit);
formLayout->addRow("Email:", addrEdit);
formLayout->addRow("Age:", occpEdit);
setLayout(formLayout);
}
该示例创建一个包含三个标签和三个行编辑的表单。
QFormLayout *formLayout = new QFormLayout;
创建QFormLayout
的实例。
formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
使用setLabelAlignment()
方法,我们设置标签小部件的对齐方式。
formLayout->addRow("Name:", nameEdit);
addRow()
方法将新行添加到表单布局的底部,并带有给定的标签和输入小部件。
main.cpp
#include <QApplication>
#include "form.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
FormEx window;
window.setWindowTitle("Form example");
window.show();
return app.exec();
}
主文件。
图:简单 form
QGridLayout
QGridLayout
将其小部件放置在网格中。 它是一个功能强大的布局管理器。
calculator.h
#pragma once
#include <QWidget>
class Calculator : public QWidget {
public:
Calculator(QWidget *parent = 0);
};
这是头文件。
calculator.cpp
#include <QGridLayout>
#include <QPushButton>
#include "calculator.h"
Calculator::Calculator(QWidget *parent)
: QWidget(parent) {
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);
QList<QString> values({ "7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"
});
int pos = 0;
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
QPushButton *btn = new QPushButton(values[pos], this);
btn->setFixedSize(40, 40);
grid->addWidget(btn, i, j);
pos++;
}
}
setLayout(grid);
}
我们创建计算器的骨架。
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);
我们创建网格布局,并在子小部件之间设置 2px 的空间。
QList<QString> values({ "7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"
});
这些是按钮上显示的字符。
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
QPushButton *btn = new QPushButton(values[pos], this);
btn->setFixedSize(40, 40);
grid->addWidget(btn, i, j);
pos++;
}
}
我们将十六个小部件放置到网格布局中。 每个按钮将具有固定的大小。
main.cpp
#include <QApplication>
#include "calculator.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Calculator window;
window.move(300, 300);
window.setWindowTitle("Calculator");
window.show();
return app.exec();
}
这是主文件。
图:QGridLayout
回顾
在本章的下一个示例中,我们使用QGridLayout
管理器创建一个更复杂的窗口。
review.h
#pragma once
#include <QWidget>
class Review : public QWidget {
public:
Review(QWidget *parent = 0);
};
头文件。
review.cpp
#include "review.h"
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
Review::Review(QWidget *parent)
: QWidget(parent) {
QGridLayout *grid = new QGridLayout(this);
grid->setVerticalSpacing(15);
grid->setHorizontalSpacing(10);
QLabel *title = new QLabel("Title:", this);
grid->addWidget(title, 0, 0, 1, 1);
title->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *edt1 = new QLineEdit(this);
grid->addWidget(edt1, 0, 1, 1, 1);
QLabel *author = new QLabel("Author:", this);
grid->addWidget(author, 1, 0, 1, 1);
author->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *edt2 = new QLineEdit(this);
grid->addWidget(edt2, 1, 1, 1, 1);
QLabel *review = new QLabel("Review:", this);
grid->addWidget(review, 2, 0, 1, 1);
review->setAlignment(Qt::AlignRight | Qt::AlignTop);
QTextEdit *te = new QTextEdit(this);
grid->addWidget(te, 2, 1, 3, 1);
setLayout(grid);
}
该代码创建了一个窗口,可用于输入作者,书名和书评。
QGridLayout *grid = new QGridLayout(this);
QGridLayout
管理器已创建。
grid->setVerticalSpacing(15);
grid->setHorizontalSpacing(10);
我们使用setVerticalSpacing()
方法添加垂直间距,并使用setHorizontalSpacing()
方法添加水平间距。
QLabel *title = new QLabel("Title", this);
grid->addWidget(title, 0, 0, 1, 1);
这些代码行创建一个标签小部件,并将其放入网格布局中。 addWidget()
方法具有五个参数。 第一个参数是子窗口小部件,在本例中为标签。 接下来的两个参数是放置标签的网格中的行和列。 最后,最后一个参数是rowspan
和colspan
。 这些参数指定当前窗口小部件将跨越多少行。 在我们的情况下,标签将仅跨越一列和一行。
title->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
setAlignment()
方法将标题标签对准其单元格。 在水平方向上,它是右对齐的。 在垂直方向上,它居中。
QTextEdit *te = new QTextEdit(this);
grid->addWidget(te, 2, 1, 3, 1);
QTextEdit
小部件位于第三行和第二列; 它跨越三行一列。
main.cpp
#include <QApplication>
#include "review.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Review window;
window.setWindowTitle("Review");
window.show();
return app.exec();
}
主文件。
图:回顾
Qt4 教程的这一部分专门用于布局管理。
Qt4 中的事件和信号
在 Qt4 C++ 编程教程的这一部分中,我们讨论事件和信号。
事件是任何 GUI 程序中的重要组成部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。 在事件模型中,有三个参与者:
- 事件来源
- 事件对象
- 事件目标
事件源是状态更改的对象。 它生成事件。 事件对象(事件)将状态更改封装在事件源中。 事件目标是要通知的对象。 事件源对象将处理事件的任务委托给事件目标。
当我们调用应用的exec()
方法时,应用进入主循环。 主循环获取事件并将其发送到对象。 Qt 具有独特的信号和槽机制。 该信号和槽机制是 C++ 编程语言的扩展。
信号和槽用于对象之间的通信。 当发生特定事件时,会发出信号。 槽是正常的 C++ 方法; 发出与其连接的信号时调用它。
点击
第一个示例显示了一个非常简单的事件处理示例。 我们有一个按钮。 通过单击按钮,我们终止该应用。
click.h
#pragma once
#include <QWidget>
class Click : public QWidget {
public:
Click(QWidget *parent = 0);
};
这是头文件。
click.cpp
#include <QPushButton>
#include <QApplication>
#include <QHBoxLayout>
#include "click.h"
Click::Click(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
QPushButton *quitBtn = new QPushButton("Quit", this);
hbox->addWidget(quitBtn, 0, Qt::AlignLeft | Qt::AlignTop);
connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
}
我们在窗口上显示一个QPushButton
。
connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
connect()
方法将信号连接到槽。 当我们单击退出按钮时,会生成clicked()
信号。 qApp
是指向应用对象的全局指针。 它在<QApplication>
头文件中定义。 发出点击信号时,将调用quit()
方法。
main.cpp
#include <QApplication>
#include "click.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Click window;
window.resize(250, 150);
window.setWindowTitle("Click");
window.show();
return app.exec();
}
这是主文件。
图:点击
按键
在以下示例中,我们对按键进行反应。
keypress.h
#pragma once
#include <QWidget>
class KeyPress : public QWidget {
public:
KeyPress(QWidget *parent = 0);
protected:
void keyPressEvent(QKeyEvent * e);
};
这是keypress.h
头文件。
keypress.cpp
#include <QApplication>
#include <QKeyEvent>
#include "keypress.h"
KeyPress::KeyPress(QWidget *parent)
: QWidget(parent)
{ }
void KeyPress::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
qApp->quit();
}
}
如果我们按 Escape
键,则应用终止。
void KeyPress::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
qApp->quit();
}
}
在 Qt4 中使用事件的一种方法是重新实现事件处理器。 QKeyEvent
是一个事件对象,其中包含有关发生的情况的信息。 在我们的例子中,我们使用事件对象来确定实际按下了哪个键。
main.cpp
#include <QApplication>
#include "keypress.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
KeyPress window;
window.resize(250, 150);
window.setWindowTitle("Key press");
window.show();
return app.exec();
}
这是主文件。
QMoveEvent
QMoveEvent
类包含移动事件的事件参数。 移动事件将发送到已移动的窗口小部件。
move.h
#pragma once
#include <QMainWindow>
class Move : public QWidget {
Q_OBJECT
public:
Move(QWidget *parent = 0);
protected:
void moveEvent(QMoveEvent *e);
};
这是move.h
头文件。
move.cpp
#include <QMoveEvent>
#include "move.h"
Move::Move(QWidget *parent)
: QWidget(parent)
{ }
void Move::moveEvent(QMoveEvent *e) {
int x = e->pos().x();
int y = e->pos().y();
QString text = QString::number(x) + "," + QString::number(y);
setWindowTitle(text);
}
在我们的代码编程示例中,我们对移动事件做出反应。 我们确定窗口客户区左上角的当前 x,y 坐标,并将这些值设置为窗口标题。
int x = e->pos().x();
int y = e->pos().y();
我们使用QMoveEvent
对象来确定x
和y
值。
QString text = QString::number(x) + "," + QString::number(y);
我们将整数值转换为字符串。
setWindowTitle(text);
setWindowTitle()
方法将文本设置为窗口的标题。
main.cpp
#include <QApplication>
#include "move.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Move window;
window.resize(250, 150);
window.setWindowTitle("Move");
window.show();
return app.exec();
}
这是主文件。
图:QMoveEvent
断开信号
可以从槽断开信号。 下一个示例显示了我们如何完成此任务。
disconnect.h
#pragma once
#include <QWidget>
#include <QPushButton>
class Disconnect : public QWidget {
Q_OBJECT
public:
Disconnect(QWidget *parent = 0);
private slots:
void onClick();
void onCheck(int);
private:
QPushButton *clickBtn;
};
在头文件中,我们声明了两个槽。 slots
不是 C++ 关键字,它是 Qt4 扩展名。 在代码编译之前,这些扩展由预处理器处理。 当我们在类中使用信号和时隙时,必须在类定义的开头提供Q_OBJECT
宏。 否则,预处理器会抱怨。
disconnect.cpp
#include <QTextStream>
#include <QCheckBox>
#include <QHBoxLayout>
#include "disconnect.h"
Disconnect::Disconnect(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
clickBtn = new QPushButton("Click", this);
hbox->addWidget(clickBtn, 0, Qt::AlignLeft | Qt::AlignTop);
QCheckBox *cb = new QCheckBox("Connect", this);
cb->setCheckState(Qt::Checked);
hbox->addWidget(cb, 0, Qt::AlignLeft | Qt::AlignTop);
connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
connect(cb, SIGNAL(stateChanged(int)), this, SLOT(onCheck(int)));
}
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
} else {
clickBtn->disconnect(SIGNAL(clicked()));
}
}
在我们的示例中,我们有一个按钮和一个复选框。 复选框用于将槽与单击的信号按钮断开连接。 此示例必须从命令行执行。
connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
connect(cb, SIGNAL(stateChanged(int)), this, SLOT(onCheck(int)));
在这里,我们将信号连接到用户定义的槽。
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
如果单击“单击”按钮,则将"Button clicked"
文本发送到终端窗口。
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
} else {
clickBtn->disconnect(SIGNAL(clicked()));
}
}
在onCheck()
槽内,我们通过单击按钮连接或断开onClick()
槽。
main.cpp
#include <QApplication>
#include "disconnect.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Disconnect window;
window.resize(250, 150);
window.setWindowTitle("Disconnect");
window.show();
return app.exec();
}
这是主文件。
计时器
计时器用于执行单发或重复性任务。 一个使用计时器的好例子是时钟。 每秒,我们必须更新显示当前时间的标签。
timer.h
#pragma once
#include <QWidget>
#include <QLabel>
class Timer : public QWidget {
public:
Timer(QWidget *parent = 0);
protected:
void timerEvent(QTimerEvent *e);
private:
QLabel *label;
};
这是头文件。
timer.cpp
#include "timer.h"
#include <QHBoxLayout>
#include <QTime>
Timer::Timer(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
label = new QLabel("", this);
hbox->addWidget(label, 0, Qt::AlignLeft | Qt::AlignTop);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
startTimer(1000);
}
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
在我们的示例中,我们在窗口上显示当前本地时间。
label = new QLabel("", this);
为了显示时间,我们使用标签小部件。
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
在这里,我们确定当前的本地时间。 我们将其设置为标签小部件。
startTimer(1000);
我们启动计时器。 每 1000ms 会生成一个计时器事件。
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
要处理计时器事件,我们必须重新实现timerEvent()
方法。
main.cpp
#include <QApplication>
#include "timer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Timer window;
window.resize(250, 150);
window.setWindowTitle("Timer");
window.show();
return app.exec();
}
这是主文件。
图:计时器
本章专门介绍 Qt4 中的事件和信号。
Qt4 小部件
在 Qt4 C++ 编程教程的这一部分中,我们将讨论一些基本的 Qt4 小部件。
小部件是 GUI 应用的基本构建块。 Qt4 库具有丰富的各种小部件集。
QLabel
QLabel
用于显示文本和图像。 没有用户交互。 以下示例显示文本。
label.h
#pragma once
#include <QWidget>
#include <QLabel>
class Label : public QWidget {
public:
Label(QWidget *parent = 0);
private:
QLabel *label;
};
这是我们的代码示例的头文件。
label.cpp
#include <QVBoxLayout>
#include <QFont>
#include "label.h"
Label::Label(QWidget *parent)
: QWidget(parent) {
QString lyrics = "Who doesn't long for someone to hold\n\
Who knows how to love you without being told\n\
Somebody tell me why I'm on my own\n\
If there's a soulmate for everyone\n\
\n\
Here we are again, circles never end\n\
How do I find the perfect fit\n\
There's enough for everyone\n\
But I'm still waiting in line\n\
\n\
Who doesn't long for someone to hold\n\
Who knows how to love you without being told\n\
Somebody tell me why I'm on my own\n\
If there's a soulmate for everyone";
label = new QLabel(lyrics, this);
label->setFont(QFont("Purisa", 10));
QVBoxLayout *vbox = new QVBoxLayout();
vbox->addWidget(label);
setLayout(vbox);
}
我们使用QLabel
小部件在窗口中显示歌词。
label = new QLabel(lyrics, this);
label->setFont(QFont("Purisa", 10));
我们创建一个标签小部件并为其设置特定的字体。
main.cpp
#include <QApplication>
#include <QTextStream>
#include "label.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Label window;
window.setWindowTitle("QLabel");
window.show();
return app.exec();
}
这是主文件。
图:QLabel
QSlider
QSlider
是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以为特定任务选择一个值。
slider.h
#pragma once
#include <QWidget>
#include <QSlider>
#include <QLabel>
class Slider : public QWidget {
Q_OBJECT
public:
Slider(QWidget *parent = 0);
private:
QSlider *slider;
QLabel *label;
};
该示例的头文件。
slider.cpp
#include "slider.h"
#include <QHBoxLayout>
Slider::Slider(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
slider = new QSlider(Qt::Horizontal , this);
hbox->addWidget(slider);
label = new QLabel("0", this);
hbox->addWidget(label);
connect(slider, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));
}
我们显示两个小部件:一个滑块和一个标签。 滑块控制标签中显示的数字。
slider = new QSlider(Qt::Horizontal , this);
将创建水平QSlider
。
connect(slider, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));
在此代码行中,我们将valueChanged()
信号连接到标签的内置setNum()
槽。
main.cpp
#include <QApplication>
#include "slider.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Slider window;
window.setWindowTitle("QSlider");
window.show();
return app.exec();
}
这是主文件。
图:QSlider
QSpinBox
QSpinbox
是一个小部件,用于处理整数和离散值集。 在我们的代码示例中,我们将有一个QSpinbox
小部件。 我们可以选择数字0..99
。 当前选择的值显示在标签窗口小部件中。
spinbox.h
#pragma once
#include <QWidget>
#include <QSpinBox>
class SpinBox : public QWidget {
Q_OBJECT
public:
SpinBox(QWidget *parent = 0);
private:
QSpinBox *spinbox;
};
这是QSpinbox
示例的头文件。
spinbox.cpp
#include "spinbox.h"
#include <QHBoxLayout>
#include <QLabel>
SpinBox::SpinBox(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(15);
spinbox = new QSpinBox(this);
QLabel *lbl = new QLabel("0", this);
hbox->addWidget(spinbox);
hbox->addWidget(lbl);
connect(spinbox, SIGNAL(valueChanged(int)), lbl, SLOT(setNum(int)));
}
我们在窗口上放置一个旋转框,并将其valueChanged()
信号连接到QLabel
的setNum()
槽。
main.cpp
#include <QApplication>
#include "spinbox.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
SpinBox window;
window.resize(250, 150);
window.setWindowTitle("QSpinBox");
window.show();
return app.exec();
}
这是主文件。
图:QSpinBox
QLineEdit
QLineEdit
是一个小部件,允许输入和编辑单行纯文本。 QLineEdit
小部件具有撤消/重做,剪切/粘贴和拖放功能。
在我们的示例中,我们显示了三个标签和三行编辑。
ledit.h
#pragma once
#include <QWidget>
class Ledit : public QWidget {
public:
Ledit(QWidget *parent = 0);
};
该示例的头文件。
ledit.cpp
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include "ledit.h"
Ledit::Ledit(QWidget *parent)
: QWidget(parent) {
QLabel *name = new QLabel("Name:", this);
name->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLabel *age = new QLabel("Age:", this);
age->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLabel *occupation = new QLabel("Occupation:", this);
occupation->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *le1 = new QLineEdit(this);
QLineEdit *le2 = new QLineEdit(this);
QLineEdit *le3 = new QLineEdit(this);
QGridLayout *grid = new QGridLayout();
grid->addWidget(name, 0, 0);
grid->addWidget(le1, 0, 1);
grid->addWidget(age, 1, 0);
grid->addWidget(le2, 1, 1);
grid->addWidget(occupation, 2, 0);
grid->addWidget(le3, 2, 1);
setLayout(grid);
}
我们显示三个标签和三行编辑。 这些小部件由QGridLayout
管理器组织。
main.cpp
#include "ledit.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Ledit window;
window.setWindowTitle("QLineEdit");
window.show();
return app.exec();
}
这是主文件。
图:QLineEdit
状态栏
状态栏是一个面板,用于显示有关应用的状态信息。
在我们的示例中,我们有两个按钮和一个状态栏。 如果我们单击每个按钮,则会显示一条消息。 状态栏小部件是QMainWindow
小部件的一部分。
statusbar.h
#pragma once
#include <QMainWindow>
#include <QPushButton>
class Statusbar : public QMainWindow {
Q_OBJECT
public:
Statusbar(QWidget *parent = 0);
private slots:
void OnOkPressed();
void OnApplyPressed();
private:
QPushButton *okBtn;
QPushButton *aplBtn;
};
该示例的头文件。
statusbar.cpp
#include <QLabel>
#include <QFrame>
#include <QStatusBar>
#include "statusbar.h"
Statusbar::Statusbar(QWidget *parent)
: QMainWindow(parent) {
QFrame *frame = new QFrame(this);
setCentralWidget(frame);
QHBoxLayout *hbox = new QHBoxLayout(frame);
okBtn = new QPushButton("OK", frame);
hbox->addWidget(okBtn, 0, Qt::AlignLeft | Qt::AlignTop);
aplBtn = new QPushButton("Apply", frame);
hbox->addWidget(aplBtn, 1, Qt::AlignLeft | Qt::AlignTop);
statusBar();
connect(okBtn, SIGNAL(clicked()), this, SLOT(OnOkPressed()));
connect(aplBtn, SIGNAL(clicked()), this, SLOT(OnApplyPressed()));
}
void Statusbar::OnOkPressed() {
statusBar()->showMessage("OK button pressed", 2000);
}
void Statusbar::OnApplyPressed() {
statusBar()->showMessage("Apply button pressed", 2000);
}
这是statusbar.cpp
文件。
QFrame *frame = new QFrame(this);
setCentralWidget(frame);
QFrame
小部件放在QMainWindow
小部件的中心区域。 中心区域只能容纳一个小部件。
okBtn = new QPushButton("OK", frame);
hbox->addWidget(okBtn, 0, Qt::AlignLeft | Qt::AlignTop);
aplBtn = new QPushButton("Apply", frame);
hbox->addWidget(aplBtn, 1, Qt::AlignLeft | Qt::AlignTop);
我们创建两个QPushButton
小部件,并将它们放置在水平框中。 按钮的父项是框架窗口小部件。
statusBar();
要显示状态栏小部件,我们调用QMainWindow
小部件的statusBar()
方法。
void Statusbar::OnOkPressed() {
statusBar()->showMessage("OK button pressed", 2000);
}
showMessage()
方法在状态栏上显示该消息。 最后一个参数指定消息在状态栏上显示的毫秒数。
main.cpp
#include <QApplication>
#include "statusbar.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Statusbar window;
window.resize(300, 200);
window.setWindowTitle("QStatusBar");
window.show();
return app.exec();
}
这是主文件。
图:状态栏示例
在 Qt4 教程的这一部分中,我们介绍了几个 Qt4 小部件。
Qt4 小部件 II
在 Qt4 C++ 编程教程的这一部分中,我们将继续讨论 Qt4 小部件。
QCheckBox
QCheckBox
是具有两种状态的窗口小部件:开和关。 这是一个带有标签的盒子。 如果选中此复选框,则在方框中用勾号表示。
在我们的示例中,我们在窗口上显示一个复选框。 如果选中此复选框,则显示窗口标题。 否则它是隐藏的。
checkbox.h
#pragma once
#include <QWidget>
class CheckBox : public QWidget {
Q_OBJECT
public:
CheckBox(QWidget *parent = 0);
private slots:
void showTitle(int);
};
这是我们的代码示例的头文件。
checkbox.cpp
#include "checkbox.h"
#include <QCheckBox>
#include <QHBoxLayout>
CheckBox::CheckBox(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QCheckBox *cb = new QCheckBox("Show Title", this);
cb->setCheckState(Qt::Checked);
hbox->addWidget(cb, 0, Qt::AlignLeft | Qt::AlignTop);
connect(cb, SIGNAL(stateChanged(int)), this, SLOT(showTitle(int)));
}
void CheckBox::showTitle(int state) {
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle(" ");
}
}
我们在窗口上显示一个复选框,并将其连接到showTitle()
槽。
cb->setCheckState(Qt::Checked);
示例开始时,该复选框已选中。
void CheckBox::showTitle(int state) {
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle(" ");
}
}
我们确定复选框的状态,并相应地调用setWindowTitle()
。
main.cpp
#include "checkbox.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
CheckBox window;
window.resize(250, 150);
window.setWindowTitle("QCheckBox");
window.show();
return app.exec();
}
这是主文件。
图:QCheckBox
QListWidget
QListWidget
是一个小部件,用于显示项目列表。 在我们的示例中,我们将演示如何在列表小部件中添加,重命名和删除项目。
listwidget.h
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QListWidget>
class ListWidget : public QWidget {
Q_OBJECT
public:
ListWidget(QWidget *parent = 0);
private slots:
void addItem();
void renameItem();
void removeItem();
void clearItems();
private:
QListWidget *lw;
QPushButton *add;
QPushButton *rename;
QPushButton *remove;
QPushButton *removeAll;
};
该示例的头文件。
listwidget.cpp
#include "listwidget.h"
#include <QVBoxLayout>
#include <QInputDialog>
ListWidget::ListWidget(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout();
vbox->setSpacing(10);
QHBoxLayout *hbox = new QHBoxLayout(this);
lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
add = new QPushButton("Add", this);
rename = new QPushButton("Rename", this);
remove = new QPushButton("Remove", this);
removeAll = new QPushButton("Remove All", this);
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeAll);
vbox->addStretch(1);
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
connect(add, SIGNAL(clicked()), this, SLOT(addItem()));
connect(rename, SIGNAL(clicked()), this, SLOT(renameItem()));
connect(remove, SIGNAL(clicked()), this, SLOT(removeItem()));
connect(removeAll, SIGNAL(clicked()), this, SLOT(clearItems()));
setLayout(hbox);
}
void ListWidget::addItem() {
QString c_text = QInputDialog::getText(this, "Item", "Enter new item");
QString s_text = c_text.simplified();
if (!s_text.isEmpty()) {
lw->addItem(s_text);
int r = lw->count() - 1;
lw->setCurrentRow(r);
}
}
void ListWidget::renameItem() {
QListWidgetItem *curitem = lw->currentItem();
int r = lw->row(curitem);
QString c_text = curitem->text();
QString r_text = QInputDialog::getText(this, "Item",
"Enter new item", QLineEdit::Normal, c_text);
QString s_text = r_text.simplified();
if (!s_text.isEmpty()) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
lw->insertItem(r, s_text);
lw->setCurrentRow(r);
}
}
void ListWidget::removeItem() {
int r = lw->currentRow();
if (r != -1) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
}
}
void ListWidget::clearItems(){
if (lw->count() != 0) {
lw->clear();
}
}
我们显示一个列表小部件和四个按钮。 我们将使用这些按钮在列表小部件中添加,重命名和删除项目。
lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote);
创建QListWidget
,并填充五个项目。
void ListWidget::addItem() {
QString c_text = QInputDialog::getText(this, "Item", "Enter new item");
QString s_text = c_text.simplified();
if (!s_text.isEmpty()) {
lw->addItem(s_text);
int r = lw->count() - 1;
lw->setCurrentRow(r);
}
}
addItem()
方法将一个新项目添加到列表小部件。 该方法会弹出一个输入对话框。 该对话框返回一个字符串值。 我们使用simplified()
方法从字符串中删除可能的空格。 如果返回的字符串不为空,则将其添加到列表末尾的列表小部件中。 最后,我们使用setCurrentRow()
方法突出显示当前插入的项目。
void ListWidget::renameItem() {
QListWidgetItem *curitem = lw->currentItem();
int r = lw->row(curitem);
QString c_text = curitem->text();
QString r_text = QInputDialog::getText(this, "Item",
"Enter new item", QLineEdit::Normal, c_text);
QString s_text = r_text.simplified();
if (!s_text.isEmpty()) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
lw->insertItem(r, s_text);
lw->setCurrentRow(r);
}
}
重命名项目包括几个步骤。 首先,我们使用currentItem()
方法获取当前项目。 我们得到项目的文本和项目所在的行。 该项目的文本显示在QInputDialog
对话框中。 从对话框返回的字符串由simplified()
方法处理,以删除潜在的空格。 然后,我们使用takeItem()
方法删除旧项目,然后将其替换为insertItem()
方法。 我们删除了takeItem()
方法删除的项目,因为删除的项目不再由 Qt 管理。 最后,setCurrentRow()
选择新项目。
void ListWidget::removeItem() {
int r = lw->currentRow();
if (r != -1) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
}
}
removeItem()
从列表中删除特定项目。 首先,我们使用currentRow()
方法获得当前选中的行。 (如果没有更多的行,则返回 -1。)使用takeItem()
方法删除当前选择的项目。
void ListWidget::clearItems(){
if (lw->count() != 0) {
lw->clear();
}
}
clear()
方法从列表小部件中删除所有项目。
main.cpp
#include "listwidget.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ListWidget window;
window.setWindowTitle("QListWidget");
window.show();
return app.exec();
}
这是主文件。
图:QListWidget
QPixmap
QPixmap
是用于处理图像的小部件之一。 它经过优化,可在屏幕上显示图像。 在我们的代码示例中,我们将使用QPixmap
在窗口上显示图像。
pixmap.h
#pragma once
#include <QWidget>
class Pixmap : public QWidget {
public:
Pixmap(QWidget *parent = 0);
};
该示例的头文件。
pixmap.cpp
#include <QPixmap>
#include <QLabel>
#include <QHBoxLayout>
#include "pixmap.h"
Pixmap::Pixmap(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QPixmap pixmap("bojnice.jpg");
QLabel *label = new QLabel(this);
label->setPixmap(pixmap);
hbox->addWidget(label, 0, Qt::AlignTop);
}
我们显示了位于斯洛伐克中部的一座著名城堡的图像。
QPixmap pixmap("bojnice.jpg");
QLabel *label = new QLabel(this);
label->setPixmap(pixmap);
我们创建一个像素图并将其放在标签小部件中。
main.cpp
#include <QApplication>
#include "pixmap.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Pixmap window;
window.setWindowTitle("QPixmap");
window.show();
return app.exec();
}
这是主文件。
QSplitter
QSplitter
允许用户通过拖动子控件之间的边界来控制子控件的大小。 在我们的示例中,我们显示了由两个拆分器组成的三个QFrame
小部件。
splitter.h
#pragma once
#include <QWidget>
class Splitter : public QWidget {
public:
Splitter(QWidget *parent = 0);
};
该示例的头文件。
splitter.cpp
#include <QFrame>
#include <QSplitter>
#include <QHBoxLayout>
#include "splitter.h"
Splitter::Splitter(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QFrame *topleft = new QFrame(this);
topleft->setFrameShape(QFrame::StyledPanel);
QFrame *topright = new QFrame(this);
topright->setFrameShape(QFrame::StyledPanel);
QSplitter *splitter1 = new QSplitter(Qt::Horizontal, this);
splitter1->addWidget(topleft);
splitter1->addWidget(topright);
QFrame *bottom = new QFrame(this);
bottom->setFrameShape(QFrame::StyledPanel);
QSplitter *splitter2 = new QSplitter(Qt::Vertical, this);
splitter2->addWidget(splitter1);
splitter2->addWidget(bottom);
QList<int> sizes({50, 100});
splitter2->setSizes(sizes);
hbox->addWidget(splitter2);
}
在示例中,我们有三个框架小部件和两个拆分器小部件。
QSplitter *splitter1 = new QSplitter(Qt::Horizontal, this);
splitter1->addWidget(topleft);
splitter1->addWidget(topright);
我们创建一个拆分器小部件,并将两个框架小部件添加到拆分器中。
QSplitter *splitter2 = new QSplitter(Qt::Vertical, this);
splitter2->addWidget(splitter1);
我们还可以将拆分器添加到另一个拆分器小部件。
QList<int> sizes({50, 100});
splitter2->setSizes(sizes);
使用setSizes()
方法,我们设置拆分器的子窗口小部件的大小。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "splitter.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Splitter window;
window.resize(350, 300);
window.setWindowTitle("QSplitter");
window.show();
return app.exec();
}
这是主文件。
图:QSplitter
在某些桌面主题中,拆分器可能无法很好地显示。
QTableWidget
QTableWidget
是电子表格应用中使用的唯一窗口小部件。 (也称为网格小部件)。 它是较复杂的小部件之一。 在这里,我们仅在窗口上显示小部件。
table.h
#pragma once
#include <QWidget>
class Table : public QWidget {
public:
Table(QWidget *parent = 0);
};
该示例的头文件。
table.cpp
#include <QHBoxLayout>
#include <QTableWidget>
#include "table.h"
Table::Table(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QTableWidget *table = new QTableWidget(25, 25, this);
hbox->addWidget(table);
}
该示例在窗口上显示QTableWidget
。
QTableWidget *table = new QTableWidget(25, 25, this);
在这里,我们创建具有 25 行 25 列的表小部件。
main.cpp
#include <QApplication>
#include "table.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Table window;
window.resize(400, 250);
window.setWindowTitle("QTableWidget");
window.show();
return app.exec();
}
这是主文件。
图:QTableWidget
在本章中,我们描述了其他几个 Qt4 小部件。
Windows API 中的高级控件
在 Windows API 教程的这一部分中,我们将详细讨论两个高级 Windows 控件:选项卡控件和列表框控件。
Label
控件
选项卡控件将具有相应选项卡的多个窗口合并在一起。
tabcontrol.c
#include <windows.h>
#include <commctrl.h>
#include <wchar.h>
#define ID_TABCTRL 1
#define ID_EDIT 2
#define BTN_ADD 3
#define BTN_DEL 4
#define BTN_CLR 5
#define MAX_TAB_LEN 15
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND hTab, hEdit;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow) {
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Tab control";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Tab control",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 380, 230, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
TCITEMW tie;
wchar_t text[4];
LRESULT count, id;
INITCOMMONCONTROLSEX icex;
switch(msg) {
case WM_CREATE:
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
hTab = CreateWindowW(WC_TABCONTROLW, NULL, WS_CHILD | WS_VISIBLE,
0, 0, 200, 150, hwnd,(HMENU) ID_TABCTRL, NULL, NULL);
hEdit = CreateWindowW(WC_EDITW, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
250, 20, 100, 25, hwnd, (HMENU) ID_EDIT, NULL, NULL);
SendMessage(hEdit, EM_SETLIMITTEXT, MAX_TAB_LEN, 0);
CreateWindowW(WC_BUTTONW, L"Add", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
250, 50, 100, 25, hwnd, (HMENU) BTN_ADD, NULL, NULL);
CreateWindowW(WC_BUTTONW, L"Delete", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
250, 80, 100, 25, hwnd, (HMENU) BTN_DEL, NULL, NULL);
CreateWindowW(WC_BUTTONW, L"Clear", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
250, 110, 100, 25, hwnd, (HMENU) BTN_CLR, NULL, NULL);
break;
case WM_COMMAND:
switch(LOWORD(wParam)) {
case BTN_ADD:
GetWindowTextW(hEdit, text, 250);
if (lstrlenW(text) != 0 ) {
tie.mask = TCIF_TEXT;
tie.pszText = text;
count = SendMessageW(hTab, TCM_GETITEMCOUNT, 0, 0);
SendMessageW(hTab, TCM_INSERTITEMW, count,
(LPARAM) (LPTCITEM) &tie);
}
break;
case BTN_DEL:
id = SendMessageW(hTab, TCM_GETCURSEL, 0, 0);
if (id != -1) {
SendMessageW(hTab, TCM_DELETEITEM, 0, id);
}
break;
case BTN_CLR:
SendMessageW(hTab, TCM_DELETEALLITEMS, 0, 0);
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return(DefWindowProcW(hwnd, msg, wParam, lParam));
}
在我们的例子中,我们使用一个标签控件,一个编辑控件,以及三个按钮。 我们将在选项卡控件上动态创建和删除选项卡。
hTab = CreateWindowW(WC_TABCONTROLW, NULL, WS_CHILD | WS_VISIBLE,
0, 0, 200, 150, hwnd,(HMENU) ID_TABCTRL, NULL, NULL);
我们使用WC_TABCONTROL
窗口类创建一个选项卡控件。
hEdit = CreateWindowW(WC_EDITW, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
250, 20, 100, 25, hwnd, (HMENU) ID_EDIT, NULL, NULL);
SendMessage(hEdit, EM_SETLIMITTEXT, MAX_TAB_LEN, 0);
我们创建一个编辑控件,并通过EM_SETLIMITTEXT
消息设置其最大大小。
if (lstrlenW(text) != 0 ) {
tie.mask = TCIF_TEXT;
tie.pszText = text;
count = SendMessageW(hTab, TCM_GETITEMCOUNT, 0, 0);
SendMessageW(hTab, TCM_INSERTITEMW, count,
(LPARAM) (LPTCITEM) &tie);
}
要添加新标签,我们填充TCITEMW
结构。 我们提供要设置的数据类型(在我们的示例中为TCIF_TEXT
)和实际文本。 然后,我们发送两条消息。 TCM_GETITEMCOUNT
消息获取选项卡的数量。 它将在第二条消息中使用。 TCM_INSERTITEMW
消息使用count
变量和TCITEMW
结构在控件中插入新标签。
id = SendMessageW(hTab, TCM_GETCURSEL, 0, 0);
if (id != -1) {
SendMessageW(hTab, TCM_DELETEITEM, 0, id);
}
要删除特定标签,我们需要当前选择的标签。 我们通过将TCM_GETCURSEL
消息发送到选项卡控件来解决。 要删除选项卡,我们发送TCM_DELETEITEM
消息,并在wParam
参数中指定要删除的项目。
SendMessageW(hTab, TCM_DELETEALLITEMS, 0, 0);
要从标签控件中删除所有标签,我们发送TCM_DELETEALLITEMS
消息。
图:标签控件
列表框
List Box
包含一个简单的列表,用户通常可以从中选择一个或多个项目。 所选项目被标记。
listbox.c
#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>
#define IDC_LIST 1
#define IDC_STATIC 2
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
typedef struct {
wchar_t name[30];
wchar_t job[20];
int age;
} Friends;
Friends friends[] = {
{L"Lucy", L"waitress", 18},
{L"Thomas", L"programmer", 25},
{L"George", L"police officer", 26},
{L"Michael", L"producer", 38},
{L"Jane", L"steward", 28},
};
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow) {
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"MyListBox";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"List Box",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 340, 200, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
static HWND hwndList, hwndStatic;
wchar_t buf[128];
switch(msg) {
case WM_CREATE:
hwndList = CreateWindowW(WC_LISTBOXW , NULL, WS_CHILD
| WS_VISIBLE | LBS_NOTIFY, 10, 10, 150, 120, hwnd,
(HMENU) IDC_LIST, NULL, NULL);
hwndStatic = CreateWindowW(WC_STATICW , NULL, WS_CHILD | WS_VISIBLE,
200, 10, 120, 45, hwnd, (HMENU) IDC_STATIC, NULL, NULL);
for (int i = 0; i < ARRAYSIZE(friends); i++) {
SendMessageW(hwndList, LB_ADDSTRING, 0, (LPARAM) friends[i].name);
}
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_LIST) {
if (HIWORD(wParam) == LBN_SELCHANGE) {
int sel = (int) SendMessageW(hwndList, LB_GETCURSEL, 0, 0);
StringCbPrintfW(buf, ARRAYSIZE(buf), L"Job: %ls\nAge: %d",
friends[sel].job, friends[sel].age);
SetWindowTextW(hwndStatic, buf);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return (DefWindowProcW(hwnd, msg, wParam, lParam));
}
在此示例中,我们显示一个列表框控件和一个静态文本控件。 通过从列表框中选择一个人,我们可以在静态控件中显示他的工作和年龄。
hwndList = CreateWindowW(WC_LISTBOXW , NULL, WS_CHILD
| WS_VISIBLE | LBS_NOTIFY, 10, 10, 150, 120, hwnd,
(HMENU) IDC_LIST, g_hinst, NULL);
WC_LISTBOXW
窗口类用于创建列表框控件。 每当用户单击列表框项目(LBN_SELCHANGE
),双击项目(LBN_DBLCLK
)或取消选择(LBN_SELCANCEL
时,LBS_NOTIFY
标志都会使列表框将通知代码发送到父窗口 ])。
for (int i = 0; i < ARRAYSIZE(friends); i++) {
SendMessageW(hwndList, LB_ADDSTRING, 0, (LPARAM) friends[i].name);
}
通过发送多个LB_ADDSTRING
消息,列表框中填充了数据。
if (HIWORD(wParam) == LBN_SELCHANGE) {
int sel = (int) SendMessageW(hwndList, LB_GETCURSEL, 0, 0);
StringCbPrintfW(buf, ARRAYSIZE(buf), L"Job: %ls\nAge: %d",
friends[sel].job, friends[sel].age);
SetWindowTextW(hwndStatic, buf);
}
如果我们从列表框中选择一个项目,则窗口过程会收到LBN_SELCHANGE
消息。 首先,我们通过向列表框中发送LB_GETCURSEL
消息来确定当前选择的项目。 然后,我们将工作名称和年龄从好友结构复制到buf
数组中。 最后,我们通过SetWindowTextW()
函数调用来设置静态文本。
图:列表框
在 Windows API 教程的这一部分中,我们介绍了两个更高级的 Windows 控件。
Qt4 中的绘图
在 Qt4 C++ 编程教程的这一部分中,我们将做一些绘图。
当我们在 Qt4 中进行绘图时,QPainter
类非常有用。 对paintEvent()
方法的反应是使用QPainter
类完成的。
直线
在第一个示例中,我们将在窗口的客户区域上绘制一些线。
lines.h
#pragma once
#include <QWidget>
class Lines : public QWidget {
public:
Lines(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
void drawLines(QPainter *qp);
};
这是头文件。
lines.cpp
#include <QPainter>
#include "lines.h"
Lines::Lines(QWidget *parent) : QWidget(parent)
{ }
void Lines::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawLines(&qp);
}
void Lines::drawLines(QPainter *qp) {
QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);
qp->drawLine(20, 40, 250, 40);
pen.setStyle(Qt::DashLine);
qp->setPen(pen);
qp->drawLine(20, 80, 250, 80);
pen.setStyle(Qt::DashDotLine);
qp->setPen(pen);
qp->drawLine(20, 120, 250, 120);
pen.setStyle(Qt::DotLine);
qp->setPen(pen);
qp->drawLine(20, 160, 250, 160);
pen.setStyle(Qt::DashDotDotLine);
qp->setPen(pen);
qp->drawLine(20, 200, 250, 200);
QVector<qreal> dashes;
qreal space = 4;
dashes << 1 << space << 5 << space;
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(dashes);
qp->setPen(pen);
qp->drawLine(20, 240, 250, 240);
}
我们在窗口上画了六行; 每条线都有不同的笔样式。
void Lines::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawLines(&qp);
}
更新小部件时将调用paintEvent()
。 在这里我们创建QPainter
对象并进行绘制。 由于我们不使用QPaintEvent
对象,因此可以通过Q_UNUSED
宏来抑制编译器警告。 实际图形委托给drawLines()
方法。
QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);
我们创建一个QPen
对象。 笔是实心的,2px 粗,是黑色的。 笔用于绘制线条和形状轮廓。 使用setPen()
方法将笔设置为画家对象。
qp->drawLine(20, 40, 250, 40);
drawLine()
方法画一条线。 四个参数是窗口上两个点的坐标。
pen.setStyle(Qt::DashLine);
QPen
行的setStyle()
方法设置笔样式-Qt::DashLine
。
main.cpp
#include <QApplication>
#include "lines.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Lines window;
window.resize(280, 270);
window.setWindowTitle("Lines");
window.show();
return app.exec();
}
这是主文件。
图:直线
色彩
颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 有效的 RGB 值在 0 到 255 之间。在下面的示例中,我们绘制了九个矩形,其中填充了九种不同的颜色。
colours.h
#pragma once
#include <QWidget>
class Colours : public QWidget {
Q_OBJECT
public:
Colours(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
void drawColouredRectangles(QPainter &);
};
这是头文件。
colours.cpp
#include <QPainter>
#include "colours.h"
Colours::Colours(QWidget *parent) : QWidget(parent)
{ }
void Colours::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawColouredRectangles(qp);
}
void Colours::drawColouredRectangles(QPainter &qp) {
qp.setPen(QColor("#d4d4d4"));
qp.setBrush(QBrush("#c56c00"));
qp.drawRect(10, 15, 90, 60);
qp.setBrush(QBrush("#1ac500"));
qp.drawRect(130, 15, 90, 60);
qp.setBrush(QBrush("#539e47"));
qp.drawRect(250, 15, 90, 60);
qp.setBrush(QBrush("#004fc5"));
qp.drawRect(10, 105, 90, 60);
qp.setBrush(QBrush("#c50024"));
qp.drawRect(130, 105, 90, 60);
qp.setBrush(QBrush("#9e4757"));
qp.drawRect(250, 105, 90, 60);
qp.setBrush(QBrush("#5f3b00"));
qp.drawRect(10, 195, 90, 60);
qp.setBrush(QBrush("#4c4c4c"));
qp.drawRect(130, 195, 90, 60);
qp.setBrush(QBrush("#785f36"));
qp.drawRect(250, 195, 90, 60);
}
我们绘制九个不同颜色填充的矩形。 矩形的轮廓是灰色的。
qp.setBrush(QBrush("#c56c00"));
qp.drawRect(10, 15, 90, 60);
QBrush
类定义QPainter
绘制的形状的填充图案。 drawRect()
方法绘制一个矩形。 它绘制一个矩形,其左上角位于 x,y 点,并具有给定的宽度和高度。 我们使用十六进制表示法来指定颜色值。
main.cpp
#include <QApplication>
#include "colours.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Colours window;
window.resize(360, 280);
window.setWindowTitle("Colours");
window.show();
return app.exec();
}
这是主文件。
图:颜色
图案
以下编程代码示例与上一个示例相似。 这次我们用各种预定义的图案填充矩形。
patterns.h
#pragma once
#include <QWidget>
class Patterns : public QWidget {
public:
Patterns(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
void drawRectangles(QPainter &);
};
头文件。
patterns.cpp
#include <QApplication>
#include <QPainter>
#include "patterns.h"
Patterns::Patterns(QWidget *parent) : QWidget(parent)
{ }
void Patterns::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawRectangles(qp);
}
void Patterns::drawRectangles(QPainter &qp) {
qp.setPen(Qt::NoPen);
qp.setBrush(Qt::HorPattern);
qp.drawRect(10, 15, 90, 60);
qp.setBrush(Qt::VerPattern);
qp.drawRect(130, 15, 90, 60);
qp.setBrush(Qt::CrossPattern);
qp.drawRect(250, 15, 90, 60);
qp.setBrush(Qt::Dense7Pattern);
qp.drawRect(10, 105, 90, 60);
qp.setBrush(Qt::Dense6Pattern);
qp.drawRect(130, 105, 90, 60);
qp.setBrush(Qt::Dense5Pattern);
qp.drawRect(250, 105, 90, 60);
qp.setBrush(Qt::BDiagPattern);
qp.drawRect(10, 195, 90, 60);
qp.setBrush(Qt::FDiagPattern);
qp.drawRect(130, 195, 90, 60);
qp.setBrush(Qt::DiagCrossPattern);
qp.drawRect(250, 195, 90, 60);
}
我们用各种画笔图案绘制了九个矩形。
qp.setBrush(Qt::HorPattern);
qp.drawRect(10, 15, 90, 60);
我们绘制具有特定图案的矩形。 Qt::HorPattern
是用于创建水平线条图案的常数。
main.cpp
#include <QApplication>
#include "patterns.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Patterns window;
window.resize(350, 280);
window.setWindowTitle("Patterns");
window.show();
return app.exec();
}
这是主文件。
图:图案
甜甜圈
在下面的示例中,我们将创建一个甜甜圈形状。
donut.h
#pragma once
#include <QWidget>
class Donut : public QWidget {
Q_OBJECT
public:
Donut(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
void drawDonut(QPainter &);
};
这是头文件。
donut.cpp
#include <QApplication>
#include <QPainter>
#include "donut.h"
Donut::Donut(QWidget *parent) : QWidget(parent)
{ }
void Donut::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawDonut(qp);
}
void Donut::drawDonut(QPainter &qp) {
qp.setPen(QPen(QBrush("#535353"), 0.5));
qp.setRenderHint(QPainter::Antialiasing);
int h = height();
int w = width();
qp.translate(QPoint(w/2, h/2));
for (qreal rot=0; rot < 360.0; rot+=5.0 ) {
qp.drawEllipse(-125, -40, 250, 80);
qp.rotate(5.0);
}
}
“甜甜圈”是类似于此类食物的高级几何形状。 我们通过绘制 72 个旋转椭圆来创建它。
qp.setRenderHint(QPainter::Antialiasing);
我们将以抗锯齿模式绘制。 渲染将具有更高的质量。
int h = height();
int w = width();
qp.translate(QPoint(w/2, h/2));
这些行将坐标系的起点移到窗口的中间。 默认情况下,它位于 0、0 点。 换句话说,在窗口的左上角。 通过移动坐标系,绘图会容易得多。
for (qreal rot=0; rot < 360.0; rot+=5.0 ) {
qp.drawEllipse(-125, -40, 250, 80);
qp.rotate(5.0);
}
在此循环中,我们绘制了 72 个旋转的椭圆。
main.cpp
#include <QApplication>
#include "donut.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Donut window;
window.resize(350, 280);
window.setWindowTitle("Donut");
window.show();
return app.exec();
}
这是主文件。
形状
Qt4 绘图 API 可以绘制各种形状。 以下编程代码示例显示了其中一些。
shapes.h
#pragma once
#include <QWidget>
class Shapes : public QWidget {
Q_OBJECT
public:
Shapes(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
};
这是头文件。
shapes.cpp
#include <QApplication>
#include <QPainter>
#include <QPainterPath>
#include "shapes.h"
Shapes::Shapes(QWidget *parent)
: QWidget(parent)
{ }
void Shapes::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(QBrush("#888"), 1));
painter.setBrush(QBrush(QColor("#888")));
QPainterPath path1;
path1.moveTo(5, 5);
path1.cubicTo(40, 5, 50, 50, 99, 99);
path1.cubicTo(5, 99, 50, 50, 5, 5);
painter.drawPath(path1);
painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
painter.drawChord(240, 30, 90, 60, 0, 16*180);
painter.drawRoundRect(20, 120, 80, 50);
QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
QPoint(220, 110), QPoint(140, 100)});
painter.drawPolygon(polygon);
painter.drawRect(250, 110, 60, 60);
QPointF baseline(20, 250);
QFont font("Georgia", 55);
QPainterPath path2;
path2.addText(baseline, font, "Q");
painter.drawPath(path2);
painter.drawEllipse(140, 200, 60, 60);
painter.drawEllipse(240, 200, 90, 60);
}
我们绘制了九种不同的形状。
QPainterPath path1;
path1.moveTo(5, 5);
path1.cubicTo(40, 5, 50, 50, 99, 99);
path1.cubicTo(5, 99, 50, 50, 5, 5);
painter.drawPath(path1);
QPainterPath
是用于创建复杂形状的对象。 我们用它来绘制贝塞尔曲线。
painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
painter.drawChord(240, 30, 90, 60, 0, 16*180);
painter.drawRoundRect(20, 120, 80, 50);
这些代码行绘制了一个饼图,一个和弦和一个圆角矩形。
QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
QPoint(220, 110), QPoint(140, 100)});
painter.drawPolygon(polygon);
在这里,我们使用drawPolygon()
方法绘制一个多边形。 多边形由五个点组成。
QPointF baseline(20, 250);
QFont font("Georgia", 55);
QPainterPath path2;
path2.addText(baseline, font, "Q");
painter.drawPath(path2);
Qt4 允许基于字体字符创建路径。
painter.drawEllipse(140, 200, 60, 60);
painter.drawEllipse(240, 200, 90, 60);
drawEllipse()
也会绘制一个椭圆和一个圆。 圆是椭圆的特例。 参数是矩形起点的 x 和 y 坐标以及椭圆边界矩形的宽度和高度。
main.cpp
#include <QApplication>
#include "shapes.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Shapes window;
window.resize(350, 280);
window.setWindowTitle("Shapes");
window.show();
return app.exec();
}
这是示例的主文件。
图:形状
渐变
在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。
以下代码示例显示了如何创建线性渐变。
gradients.h
#pragma once
#include <QWidget>
class Gradient : public QWidget {
public:
Gradient(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
};
这是头文件。
gradients.cpp
#include <QApplication>
#include <QPainter>
#include "gradients.h"
Gradient::Gradient(QWidget *parent)
: QWidget(parent)
{ }
void Gradient::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
QLinearGradient grad1(0, 20, 0, 110);
grad1.setColorAt(0.1, Qt::black);
grad1.setColorAt(0.5, Qt::yellow);
grad1.setColorAt(0.9, Qt::black);
painter.fillRect(20, 20, 300, 90, grad1);
QLinearGradient grad2(0, 55, 250, 0);
grad2.setColorAt(0.2, Qt::black);
grad2.setColorAt(0.5, Qt::red);
grad2.setColorAt(0.8, Qt::black);
painter.fillRect(20, 140, 300, 100, grad2);
}
在代码示例中,我们绘制了两个矩形,并用线性渐变填充它们。
QLinearGradient grad1(0, 20, 0, 110);
QLinearGradient
构造线性梯度,并在两个点之间作为参数提供插值区域。
grad1.setColorAt(0.1, Qt::black);
grad1.setColorAt(0.5, Qt::yellow);
grad1.setColorAt(0.9, Qt::black);
使用停止点定义渐变中的颜色。 setColorAt()
在给定位置以给定颜色创建一个停止点。
painter.fillRect(20, 20, 300, 90, grad1);
我们用渐变填充矩形。
main.cpp
#include <QApplication>
#include "gradients.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Gradient window;
window.resize(350, 260);
window.setWindowTitle("Gradients");
window.show();
return app.exec();
}
这是主文件。
图:渐变
径向渐变
径向渐变是两个圆之间颜色或阴影的混合。
radial_gradient.h
#pragma once
#include <QWidget>
class RadialGradient : public QWidget {
public:
RadialGradient(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
};
这是头文件。
radial_gradient.cpp
#include <QApplication>
#include <QPainter>
#include "radial_gradient.h"
RadialGradient::RadialGradient(QWidget *parent)
: QWidget(parent)
{ }
void RadialGradient::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
int h = height();
int w = width();
QRadialGradient grad1(w/2, h/2, 80);
grad1.setColorAt(0, QColor("#032E91"));
grad1.setColorAt(0.3, Qt::white);
grad1.setColorAt(1, QColor("#032E91"));
painter.fillRect(0, 0, w, h, grad1);
}
该示例创建了一个径向渐变; 渐变从窗口的中心扩散。
QRadialGradient grad1(w/2, h/2, 80);
QRadialGradient
创建一个径向渐变; 它在焦点和围绕它的圆上的端点之间插入颜色。 参数是圆心和半径的坐标。 焦点位于圆的中心。
grad1.setColorAt(0, QColor("#032E91"));
grad1.setColorAt(0.3, Qt::white);
grad1.setColorAt(1, QColor("#032E91"));
setColorAt()
方法定义彩色挡块。
painter.fillRect(0, 0, w, h, grad1);
窗口的整个区域都充满了径向渐变。
main.cpp
#include <QApplication>
#include "radial_gradient.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
RadialGradient window;
window.resize(300, 250);
window.setWindowTitle("Radial gradient");
window.show();
return app.exec();
}
这是主文件。
图:径向渐变
泡泡
在本 C++ Qt4 教程章节的最后一个示例中,我们创建一个泡泡效果。 该示例显示一个不断增长的居中文本,该文本从某个点逐渐淡出。 这是一种非常常见的效果,您通常可以在网络上的 Flash 动画中看到这种效果。
puff.h
#pragma once
#include <QWidget>
class Puff : public QWidget {
Q_OBJECT
public:
Puff(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
private:
int x;
qreal opacity;
int timerId;
};
在头文件中,我们定义了两个事件处理器:绘图事件处理器和计时器处理器。
puff.cpp
#include <QPainter>
#include <QTimer>
#include <QTextStream>
#include "puff.h"
Puff::Puff(QWidget *parent)
: QWidget(parent) {
x = 1;
opacity = 1.0;
timerId = startTimer(15);
}
void Puff::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
QTextStream out(stdout);
QString text = "ZetCode";
painter.setPen(QPen(QBrush("#575555"), 1));
QFont font("Courier", x, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(text);
painter.setFont(font);
if (x > 10) {
opacity -= 0.01;
painter.setOpacity(opacity);
}
if (opacity <= 0) {
killTimer(timerId);
out << "timer stopped" << endl;
}
int h = height();
int w = width();
painter.translate(QPoint(w/2, h/2));
painter.drawText(-textWidth/2, 0, text);
}
void Puff::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
x += 1;
repaint();
}
这是puff.cpp
文件。
Puff::Puff(QWidget *parent)
: QWidget(parent) {
x = 1;
opacity = 1.0;
timerId = startTimer(15);
}
在构造器中,我们启动计时器。 每 15ms 会生成一个计时器事件。
void Puff::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
x += 1;
repaint();
}
在timerEvent()
内,我们增加字体大小并重新绘制小部件。
if (x > 10) {
opacity -= 0.01;
painter.setOpacity(opacity);
}
如果字体大小大于 10 磅,我们将逐渐降低不透明度; 文字开始消失。
if (opacity <= 0) {
killTimer(timerId);
out << "timer stopped" << endl;
}
如果文字完全消失,我们将杀死计时器。
main.cpp
#include <QApplication>
#include "puff.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Puff window;
window.resize(350, 280);
window.setWindowTitle("Puff");
window.show();
return app.exec();
}
这是主文件。
本章是关于 Qt4 中的绘图的。
Qt4 中的自定义小部件
在 Qt4 C++ 编程教程的这一部分中,我们将创建一个自定义窗口小部件。
大多数工具箱通常仅提供最常用的小部件,例如按钮,文本小部件或滑块。 没有工具包可以提供所有可能的小部件。 程序员必须自己创建此类小部件。 他们使用工具箱提供的绘图工具来完成此任务。 有两种可能:程序员可以修改或增强现有的小部件,或者可以从头开始创建自定义小部件。
刻录小部件
在下一个示例中,我们创建一个自定义的刻录小部件。 可以在 Nero 或 K3B 之类的应用中看到此小部件。 该小部件将从头开始创建。
burning.h
#pragma once
#include <QWidget>
#include <QSlider>
#include <QFrame>
#include "widget.h"
class Burning : public QFrame {
Q_OBJECT
public:
Burning(QWidget *parent = 0);
int getCurrentWidth();
public slots:
void valueChanged(int);
private:
QSlider *slider;
Widget *widget;
int cur_width;
void initUI();
};
这是示例主窗口的头文件。
public:
Burning(QWidget *parent = 0);
int getCurrentWidth();
getCurrentWidth()
方法将用于确定滑块值。
private:
QSlider *slider;
Widget *widget;
int cur_width;
void initUI();
窗口的工作区上将有两个小部件:内置滑块小部件和自定义小部件。 cur_width
变量将保存滑块中的当前值。 绘制自定义窗口小部件时使用此值。
burning.cpp
#include <QtGui>
#include "burning.h"
Burning::Burning(QWidget *parent)
: QFrame(parent) {
initUI();
}
void Burning::initUI() {
const int MAX_VALUE = 750;
cur_width = 0;
slider = new QSlider(Qt::Horizontal , this);
slider->setMaximum(MAX_VALUE);
slider->setGeometry(50, 50, 130, 30);
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(valueChanged(int)));
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
vbox->addStretch(1);
widget = new Widget(this);
hbox->addWidget(widget, 0);
vbox->addLayout(hbox);
setLayout(vbox);
}
void Burning::valueChanged(int val) {
cur_width = val;
widget->repaint();
}
int Burning::getCurrentWidth() {
return cur_width;
}
在这里,我们构建示例的主窗口。
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(valueChanged(int)));
当我们移动滑块时,将执行valueChanged()
槽。
void Burning::valueChanged(int val) {
cur_width = val;
widget->repaint();
}
更改滑块的值时,我们将存储新值并重新绘制自定义窗口小部件。
widget.h
#pragma once
#include <QFrame>
class Burning;
class Widget : public QFrame {
Q_OBJECT
public:
Widget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
void drawWidget(QPainter &qp);
private:
QWidget *m_parent;
Burning *burn;
};
这是自定义刻录窗口小部件的头文件。
private:
QWidget *m_parent;
Burning *burn;
我们存储一个指向父窗口小部件的指针。 我们通过该指针获得cur_width
。
widget.cpp
#include <QtGui>
#include "widget.h"
#include "burning.h"
const int PANEL_HEIGHT = 30;
Widget::Widget(QWidget *parent)
: QFrame(parent) {
m_parent = parent;
setMinimumHeight(PANEL_HEIGHT);
}
void Widget::paintEvent(QPaintEvent *e) {
QPainter qp(this);
drawWidget(qp);
QFrame::paintEvent(e);
}
void Widget::drawWidget(QPainter &qp) {
const int DISTANCE = 19;
const int LINE_WIDTH = 5;
const int DIVISIONS = 10;
const float FULL_CAPACITY = 700;
const float MAX_CAPACITY = 750;
QString num[] = { "75", "150", "225", "300", "375", "450",
"525", "600", "675" };
int asize = sizeof(num)/sizeof(num[1]);
QColor redColor(255, 175, 175);
QColor yellowColor(255, 255, 184);
int width = size().width();
Burning *burn = (Burning *) m_parent;
int cur_width = burn->getCurrentWidth();
int step = (int) qRound(width / DIVISIONS);
int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);
if (cur_width >= FULL_CAPACITY) {
qp.setPen(yellowColor);
qp.setBrush(yellowColor);
qp.drawRect(0, 0, full, 30);
qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT);
} else if (till > 0) {
qp.setPen(yellowColor);
qp.setBrush(yellowColor);
qp.drawRect(0, 0, till, PANEL_HEIGHT);
}
QColor grayColor(90, 80, 60);
qp.setPen(grayColor);
for (int i=1; i <=asize; i++) {
qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
QFont newFont = font();
newFont.setPointSize(7);
setFont(newFont);
QFontMetrics metrics(font());
int w = metrics.width(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
}
}
在这里,我们绘制自定义窗口小部件。 我们绘制矩形,垂直线和数字。
void Widget::paintEvent(QPaintEvent *e) {
QPainter qp(this);
drawWidget(qp);
QFrame::paintEvent(e);
}
自定义窗口小部件的图形委托给drawWidget()
方法。
const int DISTANCE = 19;
const int LINE_WIDTH = 5;
const int DIVISIONS = 10;
const float FULL_CAPACITY = 700;
const float MAX_CAPACITY = 750;
这些是重要的常数。 DISTANCE
是比例尺上的数字与其父边界顶部之间的距离。 LINE_WIDTH
是垂直线的宽度。 DIVISIONS
是秤的数量。 FULL_CAPACITY
是媒体的容量。 达到目标后,就会发生过度刻录。 这通过红色可视化。 MAX_CAPACITY
是介质的最大容量。
QString num[] = { "75", "150", "225", "300", "375", "450",
"525", "600", "675" };
我们使用这些数字来构建刻录小部件的比例。
int width = size().width();
我们得到小部件的宽度。 自定义窗口小部件的宽度是动态的。 用户可以调整大小。
Burning *burn = (Burning *) m_parent;
int cur_width = burn->getCurrentWidth();
我们得到cur_width
值。
int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);
我们使用width
变量在比例尺值和自定义小部件的度量之间进行转换。
qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT);
这三行画出红色矩形,表示过度燃烧。
qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
在这里,我们画出小的垂直线。
QFontMetrics metrics(font());
int w = metrics.width(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
在这里,我们绘制刻度的数字。 为了精确定位数字,我们必须获得字符串的宽度。
main.cpp
#include <QApplication>
#include "burning.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Burning window;
window.resize(370, 200);
window.setWindowTitle("The Burning widget");
window.show();
return app.exec();
}
这是主文件。
图:刻录小部件
在 Qt4 教程的这一部分中,我们创建了一个自定义的刻录小部件。
Qt4 中的打砖块游戏
在 Qt4 教程的这一部分中,我们创建一个简单的打砖块游戏克隆。
打砖块是 Atari Inc.开发的一款街机游戏。该游戏创建于 1976 年。在该游戏中,玩家移动桨叶并弹跳球。 目的是销毁窗口顶部的砖块。 可以在此处下载游戏图像。
开发
在我们的游戏中,我们只有一个桨,一个球和三十个砖头。 计时器用于创建游戏周期。 我们不使用角度,而是仅更改方向:顶部,底部,左侧和右侧。 该代码的灵感来自 Nathan Dawson 在 PyGame 库中开发的 PyBreakout 游戏。
游戏是故意简单的。 没有奖金,等级或分数。 这样更容易理解。
Qt4 库是为创建计算机应用而开发的。 但是,它也可以用于创建游戏。 开发计算机游戏是了解 Qt4 的好方法。
paddle.h
#pragma once
#include <QImage>
#include <QRect>
class Paddle {
public:
Paddle();
~Paddle();
public:
void resetState();
void move();
void setDx(int);
QRect getRect();
QImage & getImage();
private:
QImage image;
QRect rect;
int dx;
static const int INITIAL_X = 200;
static const int INITIAL_Y = 360;
};
这是桨对象的头文件。 INITIAL_X
和INITIAL_Y
是代表桨状对象的初始坐标的常数。
paddle.cpp
#include "paddle.h"
#include <iostream>
Paddle::Paddle() {
dx = 0;
image.load("paddle.png");
rect = image.rect();
resetState();
}
Paddle::~Paddle() {
std::cout << ("Paddle deleted") << std::endl;
}
void Paddle::setDx(int x) {
dx = x;
}
void Paddle::move() {
int x = rect.x() + dx;
int y = rect.top();
rect.moveTo(x, y);
}
void Paddle::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
QRect Paddle::getRect() {
return rect;
}
QImage & Paddle::getImage() {
return image;
}
桨板可以向右或向左移动。
Paddle::Paddle() {
dx = 0;
image.load("paddle.png");
rect = image.rect();
resetState();
}
在构造器中,我们启动dx
变量并加载桨图像。 我们得到图像矩形并将图像移动到其初始位置。
void Paddle::move() {
int x = rect.x() + dx;
int y = rect.top();
rect.moveTo(x, y);
}
move()
方法移动桨的矩形。 移动方向由dx
变量控制。
void Paddle::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
resetState()
将拨片移动到其初始位置。
brick.h
#pragma once
#include <QImage>
#include <QRect>
class Brick {
public:
Brick(int, int);
~Brick();
public:
bool isDestroyed();
void setDestroyed(bool);
QRect getRect();
void setRect(QRect);
QImage & getImage();
private:
QImage image;
QRect rect;
bool destroyed;
};
这是砖对象的头文件。 如果销毁了积木,则destroyed
变量将设置为true
。
brick.cpp
#include "brick.h"
#include <iostream>
Brick::Brick(int x, int y) {
image.load("brickie.png");
destroyed = false;
rect = image.rect();
rect.translate(x, y);
}
Brick::~Brick() {
std::cout << ("Brick deleted") << std::endl;
}
QRect Brick::getRect() {
return rect;
}
void Brick::setRect(QRect rct) {
rect = rct;
}
QImage & Brick::getImage() {
return image;
}
bool Brick::isDestroyed() {
return destroyed;
}
void Brick::setDestroyed(bool destr) {
destroyed = destr;
}
Brick
类代表砖对象。
Brick::Brick(int x, int y) {
image.load("brickie.png");
destroyed = false;
rect = image.rect();
rect.translate(x, y);
}
砖的构造器加载其图像,启动destroyed
标志,然后将图像移至其初始位置。
bool Brick::isDestroyed() {
return destroyed;
}
砖块具有destroyed
标志。 如果设置了destroyed
标志,则不会在窗口上绘制砖块。
ball.h
#pragma once
#include <QImage>
#include <QRect>
class Ball {
public:
Ball();
~Ball();
public:
void resetState();
void autoMove();
void setXDir(int);
void setYDir(int);
int getXDir();
int getYDir();
QRect getRect();
QImage & getImage();
private:
int xdir;
int ydir;
QImage image;
QRect rect;
static const int INITIAL_X = 230;
static const int INITIAL_Y = 355;
static const int RIGHT_EDGE = 300;
};
这是球形对象的头文件。 xdir
和ydir
变量存储球的运动方向。
ball.cpp
#include "ball.h"
#include <iostream>
Ball::Ball() {
xdir = 1;
ydir = -1;
image.load("ball.png");
rect = image.rect();
resetState();
}
Ball::~Ball() {
std::cout << ("Ball deleted") << std::endl;
}
void Ball::autoMove() {
rect.translate(xdir, ydir);
if (rect.left() == 0) {
xdir = 1;
}
if (rect.right() == RIGHT_EDGE) {
xdir = -1;
}
if (rect.top() == 0) {
ydir = 1;
}
}
void Ball::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
void Ball::setXDir(int x) {
xdir = x;
}
void Ball::setYDir(int y) {
ydir = y;
}
int Ball::getXDir() {
return xdir;
}
int Ball::getYDir() {
return ydir;
}
QRect Ball::getRect() {
return rect;
}
QImage & Ball::getImage() {
return image;
}
Ball
类表示球对象。
xdir = 1;
ydir = -1;
开始时,球向东北方向移动。
void Ball::autoMove() {
rect.translate(xdir, ydir);
if (rect.left() == 0) {
xdir = 1;
}
if (rect.right() == RIGHT_EDGE) {
xdir = -1;
}
if (rect.top() == 0) {
ydir = 1;
}
}
在每个游戏周期都会调用autoMove()
方法来在屏幕上移动球。 如果它破坏了边界,球的方向就会改变。 如果球越过底边,则球不会反弹回来-游戏结束。
breakout.h
#pragma once
#include <QWidget>
#include <QKeyEvent>
#include "ball.h"
#include "brick.h"
#include "paddle.h"
class Breakout : public QWidget {
Q_OBJECT
public:
Breakout(QWidget *parent = 0);
~Breakout();
protected:
void paintEvent(QPaintEvent *);
void timerEvent(QTimerEvent *);
void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
void drawObjects(QPainter *);
void finishGame(QPainter *, QString);
void moveObjects();
void startGame();
void pauseGame();
void stopGame();
void victory();
void checkCollision();
private:
int x;
int timerId;
static const int N_OF_BRICKS = 30;
static const int DELAY = 10;
static const int BOTTOM_EDGE = 400;
Ball *ball;
Paddle *paddle;
Brick *bricks[N_OF_BRICKS];
bool gameOver;
bool gameWon;
bool gameStarted;
bool paused;
};
这是突破对象的头文件。
void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
使用光标键控制桨。 在游戏中,我们监听按键和按键释放事件。
int x;
int timerId;
x
变量存储桨的当前 x 位置。 timerId
用于识别计时器对象。 当我们暂停游戏时,这是必需的。
static const int N_OF_BRICKS = 30;
N_OF_BRICKS
常数存储游戏中的积木数量。
static const int DELAY = 10;
DELAY
常数控制游戏的速度。
static const int BOTTOM_EDGE = 400;
当球通过底边时,比赛结束。
Ball *ball;
Paddle *paddle;
Brick *bricks[N_OF_BRICKS];
游戏包括一个球,一个球拍和一系列砖块。
bool gameOver;
bool gameWon;
bool gameStarted;
bool paused;
这四个变量代表游戏的各种状态。
breakout.cpp
#include <QPainter>
#include <QApplication>
#include "breakout.h"
Breakout::Breakout(QWidget *parent)
: QWidget(parent) {
x = 0;
gameOver = false;
gameWon = false;
paused = false;
gameStarted = false;
ball = new Ball();
paddle = new Paddle();
int k = 0;
for (int i=0; i<5; i++) {
for (int j=0; j<6; j++) {
bricks[k] = new Brick(j*40+30, i*10+50);
k++;
}
}
}
Breakout::~Breakout() {
delete ball;
delete paddle;
for (int i=0; i<N_OF_BRICKS; i++) {
delete bricks[i];
}
}
void Breakout::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
if (gameOver) {
finishGame(&painter, "Game lost");
} else if(gameWon) {
finishGame(&painter, "Victory");
}
else {
drawObjects(&painter);
}
}
void Breakout::finishGame(QPainter *painter, QString message) {
QFont font("Courier", 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
painter->setFont(font);
int h = height();
int w = width();
painter->translate(QPoint(w/2, h/2));
painter->drawText(-textWidth/2, 0, message);
}
void Breakout::drawObjects(QPainter *painter) {
painter->drawImage(ball->getRect(), ball->getImage());
painter->drawImage(paddle->getRect(), paddle->getImage());
for (int i=0; i<N_OF_BRICKS; i++) {
if (!bricks[i]->isDestroyed()) {
painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage());
}
}
}
void Breakout::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
moveObjects();
checkCollision();
repaint();
}
void Breakout::moveObjects() {
ball->autoMove();
paddle->move();
}
void Breakout::keyReleaseEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = 0;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 0;
paddle->setDx(dx);
break;
}
}
void Breakout::keyPressEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = -1;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 1;
paddle->setDx(dx);
break;
case Qt::Key_P:
pauseGame();
break;
case Qt::Key_Space:
startGame();
break;
case Qt::Key_Escape:
qApp->exit();
break;
default:
QWidget::keyPressEvent(e);
}
}
void Breakout::startGame() {
if (!gameStarted) {
ball->resetState();
paddle->resetState();
for (int i=0; i<N_OF_BRICKS; i++) {
bricks[i]->setDestroyed(false);
}
gameOver = false;
gameWon = false;
gameStarted = true;
timerId = startTimer(DELAY);
}
}
void Breakout::pauseGame() {
if (paused) {
timerId = startTimer(DELAY);
paused = false;
} else {
paused = true;
killTimer(timerId);
}
}
void Breakout::stopGame() {
killTimer(timerId);
gameOver = true;
gameStarted = false;
}
void Breakout::victory() {
killTimer(timerId);
gameWon = true;
gameStarted = false;
}
void Breakout::checkCollision() {
if (ball->getRect().bottom() > BOTTOM_EDGE) {
stopGame();
}
for (int i=0, j=0; i<N_OF_BRICKS; i++) {
if (bricks[i]->isDestroyed()) {
j++;
}
if (j == N_OF_BRICKS) {
victory();
}
}
if ((ball->getRect()).intersects(paddle->getRect())) {
int paddleLPos = paddle->getRect().left();
int ballLPos = ball->getRect().left();
int first = paddleLPos + 8;
int second = paddleLPos + 16;
int third = paddleLPos + 24;
int fourth = paddleLPos + 32;
if (ballLPos < first) {
ball->setXDir(-1);
ball->setYDir(-1);
}
if (ballLPos >= first && ballLPos < second) {
ball->setXDir(-1);
ball->setYDir(-1*ball->getYDir());
}
if (ballLPos >= second && ballLPos < third) {
ball->setXDir(0);
ball->setYDir(-1);
}
if (ballLPos >= third && ballLPos < fourth) {
ball->setXDir(1);
ball->setYDir(-1*ball->getYDir());
}
if (ballLPos > fourth) {
ball->setXDir(1);
ball->setYDir(-1);
}
}
for (int i=0; i<N_OF_BRICKS; i++) {
if ((ball->getRect()).intersects(bricks[i]->getRect())) {
int ballLeft = ball->getRect().left();
int ballHeight = ball->getRect().height();
int ballWidth = ball->getRect().width();
int ballTop = ball->getRect().top();
QPoint pointRight(ballLeft + ballWidth + 1, ballTop);
QPoint pointLeft(ballLeft - 1, ballTop);
QPoint pointTop(ballLeft, ballTop -1);
QPoint pointBottom(ballLeft, ballTop + ballHeight + 1);
if (!bricks[i]->isDestroyed()) {
if(bricks[i]->getRect().contains(pointRight)) {
ball->setXDir(-1);
}
else if(bricks[i]->getRect().contains(pointLeft)) {
ball->setXDir(1);
}
if(bricks[i]->getRect().contains(pointTop)) {
ball->setYDir(1);
}
else if(bricks[i]->getRect().contains(pointBottom)) {
ball->setYDir(-1);
}
bricks[i]->setDestroyed(true);
}
}
}
}
在breakout.cpp
文件中,我们有游戏逻辑。
int k = 0;
for (int i=0; i<5; i++) {
for (int j=0; j<6; j++) {
bricks[k] = new Brick(j*40+30, i*10+50);
k++;
}
}
在打砖块对象的构造器中,我们实例化了三十个砖块。
void Breakout::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
if (gameOver) {
finishGame(&painter, "Game lost");
} else if(gameWon) {
finishGame(&painter, "Victory");
}
else {
drawObjects(&painter);
}
}
根据gameOver
和gameWon
变量,我们要么用消息结束游戏,要么在窗口上绘制游戏对象。
void Breakout::finishGame(QPainter *painter, QString message) {
QFont font("Courier", 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
painter->setFont(font);
int h = height();
int w = width();
painter->translate(QPoint(w/2, h/2));
painter->drawText(-textWidth/2, 0, message);
}
finishGame()
方法在窗口中心绘制一条最终消息。 它是"Game Over"
或"Victory"
。 QFontMetrics'
width()
用于计算字符串的宽度。
void Breakout::drawObjects(QPainter *painter) {
painter->drawImage(ball->getRect(), ball->getImage());
painter->drawImage(paddle->getRect(), paddle->getImage());
for (int i=0; i<N_OF_BRICKS; i++) {
if (!bricks[i]->isDestroyed()) {
painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage());
}
}
}
drawObjects()
方法在窗口上绘制游戏的所有对象:球,球拍和砖头。 这些对象由图像表示,drawImage()
方法将它们绘制在窗口上。
void Breakout::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
moveObjects();
checkCollision();
repaint();
}
在timerEvent()
中,我们移动对象,检查球是否与桨或砖相撞,并生成绘图事件。
void Breakout::moveObjects() {
ball->autoMove();
paddle->move();
}
moveObjects()
方法移动球和桨对象。 他们自己的移动方法被调用。
void Breakout::keyReleaseEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = 0;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 0;
paddle->setDx(dx);
break;
}
}
当播放器释放左
光标键或右
光标键时,我们将板的dx
变量设置为零。 结果,桨停止运动。
void Breakout::keyPressEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = -1;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 1;
paddle->setDx(dx);
break;
case Qt::Key_P:
pauseGame();
break;
case Qt::Key_Space:
startGame();
break;
case Qt::Key_Escape:
qApp->exit();
break;
default:
QWidget::keyPressEvent(e);
}
}
在keyPressEvent()
方法中,我们监听与游戏相关的按键事件。 左
和右
光标键移动桨状对象。 他们设置dx
变量,该变量随后添加到桨的 x 坐标中。 P
键暂停游戏,空格键
启动游戏。 Esc
键退出应用。
void Breakout::startGame() {
if (!gameStarted) {
ball->resetState();
paddle->resetState();
for (int i=0; i<N_OF_BRICKS; i++) {
bricks[i]->setDestroyed(false);
}
gameOver = false;
gameWon = false;
gameStarted = true;
timerId = startTimer(DELAY);
}
}
startGame()
方法重置球和桨对象; 他们被转移到他们的初始位置。 在for
循环中,我们将每个积木的destroyed
标志重置为false
,从而将它们全部显示在窗口中。 gameOver
,gameWon
和gameStarted
变量获得其初始布尔值。 最后,使用startTimer()
方法启动计时器。
void Breakout::pauseGame() {
if (paused) {
timerId = startTimer(DELAY);
paused = false;
} else {
paused = true;
killTimer(timerId);
}
}
pauseGame()
用于暂停和开始暂停的游戏。 状态由paused
变量控制。 我们还存储计时器的 ID。 为了暂停游戏,我们使用killTimer()
方法终止计时器。 要重新启动它,我们调用startTimer()
方法。
void Breakout::stopGame() {
killTimer(timerId);
gameOver = true;
gameStarted = false;
}
在stopGame()
方法中,我们终止计时器并设置适当的标志。
void Breakout::checkCollision() {
if (ball->getRect().bottom() > BOTTOM_EDGE) {
stopGame();
}
...
}
在checkCollision()
方法中,我们对游戏进行碰撞检测。 如果球撞到底边,则比赛结束。
for (int i=0, j=0; i<N_OF_BRICKS; i++) {
if (bricks[i]->isDestroyed()) {
j++;
}
if (j == N_OF_BRICKS) {
victory();
}
}
我们检查了多少砖被破坏了。 如果我们摧毁了所有积木,我们将赢得这场比赛。
if (ballLPos < first) {
ball->setXDir(-1);
ball->setYDir(-1);
}
如果球碰到了桨的第一部分,我们会将球的方向更改为西北。
if(bricks[i]->getRect().contains(pointTop)) {
ball->setYDir(1);
}
如果球撞击砖的底部,我们将改变球的 y 方向; 它下降了。
main.cpp
#include <QApplication>
#include "breakout.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Breakout window;
window.resize(300, 400);
window.setWindowTitle("Breakout");
window.show();
return app.exec();
}
这是主文件。
图:打砖块游戏
这是 Qt4 中的打砖块游戏。
PySide 教程
这是 PySide 教程。 本教程适合初学者和中级程序员。 阅读完本教程后,您将能够编写平凡的 PySide 应用。
目录
PySide
PySide 是 Python 库,用于创建跨平台的图形用户界面。 它是 Qt 框架的 Python 绑定。 Qt 库是功能最强大的 GUI 库之一。 它是由 Digia 和 Qt 项目开发的。
相关教程
在 ZetCode 上有完整的 Python 教程。 其他 GUI Python 绑定的教程包括 PyQt4 教程, wxPython 教程, PyGTK 教程和 Tkinter 教程。
PySide 工具包简介
这是一个介绍性的 PySide 教程。 本教程的目的是帮助您开始使用 PySide 工具包。 该教程已在 Linux 上创建并测试。
关于 PySide
PySide 是 Python 库,用于创建跨平台的图形用户界面。 它是 Qt 框架的 Python 绑定。 Qt 库是功能最强大的 GUI 库之一。 PySide 的官方主页为 qt-project.org/wiki/PySide 。 可以在 pypi.python.org/pypi/PySide 中找到安装说明。
PySide 被实现为一组 Python 模块。 目前它有 15 个模块。 这些模块提供了强大的工具来处理 GUI,多媒体,XML 文档,网络或数据库。 在我们的教程中,我们将使用其中两个模块。 QtGui
和QtCore
模块。
QtCore
模块包含核心的非 GUI 功能。 该模块用于处理时间,文件和目录,各种数据类型,流,URL,mime 类型,线程或进程。 QtGui
模块包含图形组件和相关类。 这些包括例如按钮,窗口,状态栏,工具栏,滑块,位图,颜色,字体等。
在 Qt 工具包的所有者诺基亚未能与 Riverbank Computing 达成协议(将 LGPL 作为替代许可证)后,PySide 已发布。 PySide 与 PyQt4 具有高度的 API 兼容性,因此迁移到 PySide 并不困难。
Python
Python 是一种通用的,动态的,面向对象的编程语言。 Python 语言的设计目的强调程序员的生产力和代码可读性。 Python 最初是由 Guido van Rossum 开发的。 它于 1991 年首次发布。Python 受 ABC,Haskell,Java,Lisp,Icon 和 Perl 编程语言的启发。 Python 是高级通用多平台解释型语言。 Python 是一种简约语言。 它最明显的功能之一是它不使用分号或方括号。 Python 使用缩进代替。 目前,Python 有两个主要分支。 Python 2.x 和 Python3.x。 Python 3.x 打破了与早期版本 Python 的向后兼容性。 它的创建是为了纠正该语言的某些设计缺陷并使该语言更简洁。 本教程介绍了 Python 2.x 版本。 大多数代码是用 Python 2.x 版本编写的。 软件基础和程序员将需要一些时间才能迁移到 Python3.x。 今天,Python 由世界各地的一大批志愿者维护。 Python 是开源软件。
对于那些想学习编程的人来说,Python 是一个理想的起点。
Python 编程语言支持多种编程样式。 它不会强迫程序员采用特定的示例。 Python 支持面向对象和过程编程。 对函数式编程的支持也很有限。
Python 编程语言的官方网站是 python.org
Python 是最受欢迎的编程语言之一。 根据 langpop.com ,Python 排在第六位。 TIOBE 索引将 Python 放在第 8 位。 在 github.com (一种流行的软件项目仓库)上,Python 是第三流行的语言,在托管的所有项目中占 9% 的份额。
Python 工具包
为了创建现代的图形用户界面,Python 程序员可以在以下不错的选项中进行选择:PySide,PyQt4,Python/Gnome(以前的 PyGTK)和 wxPython。
本章是对 PySide 工具箱的介绍。
PySide 中的第一个程序
在 PySide 教程的这一部分中,我们将学习一些基本功能。
简单的例子
该代码示例非常简单。 它只显示一个小窗口。 然而,我们可以利用这个窗口做很多事情。 我们可以调整大小,最大化或最小化它。 这需要大量的编码。 已经有人对该功能进行了编码。 由于它在大多数应用中都会重复出现,因此无需重新编码。 因此它已被程序员隐藏。 PySide 是一个高级工具包。 如果我们在较低级别的工具箱中进行编码,则以下代码示例可能很容易包含数十行。
#!/usr/bin/python
# -*- coding: utf-8 -*-
# simple.py
import sys
from PySide import QtGui
app = QtGui.QApplication(sys.argv)
wid = QtGui.QWidget()
wid.resize(250, 150)
wid.setWindowTitle('Simple')
wid.show()
sys.exit(app.exec_())
上面的代码在屏幕上显示了一个小窗口。
import sys
from PySide import QtGui
在这里,我们提供必要的导入。 基本的 GUI 小部件位于QtGui
模块中。
app = QtGui.QApplication(sys.argv)
每个 PySide 应用都必须创建一个应用对象。 该应用对象位于QtGui
模块中。 sys.argv
参数是命令行中的参数列表。 可以从外壳运行 Python 脚本。 这是一种我们可以控制脚本启动的方式。
wid = QtGui.QWidget()
QWidget
小部件是 PySide 中所有用户界面对象的基类。 我们为QWidget
提供了默认的构造器。 默认构造器没有父代。 没有父级的窗口小部件称为窗口。
wid.resize(250, 150)
resize()
方法调整窗口小部件的大小。 宽 250 像素,高 150 像素。
wid.setWindowTitle('Simple')
在这里,我们为窗口设置标题。 标题显示在标题栏中。
wid.show()
show()
方法在屏幕上显示小部件。
sys.exit(app.exec_())
最后,我们进入应用的主循环。 事件处理从这一点开始。 主循环从窗口系统接收事件,并将其分配给应用小部件。 如果调用exit()
方法或主窗口小部件被销毁,则主循环结束。 sys.exit()
方法可确保干净退出。 将告知环境应用如何结束。
您想知道为什么exec_()
方法下划线吗? 一切都有意义。 显然是因为exec
是 Python 关键字。 因此,使用了exec_()
。
图:简单
应用图标
应用图标是一个小图像,通常显示在标题栏的左上角。 在任务栏中也可以看到它。 在下面的示例中,我们将展示如何在 PySide 中进行操作。 我们还将介绍一些新方法。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows an icon
in the titlebar of the window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Icon')
self.setWindowIcon(QtGui.QIcon('web.png'))
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
前面的示例以过程样式编码。 Python 编程语言支持过程和面向对象的编程风格。 在 PySide 中进行编程意味着在 OOP 中进行编程。
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
面向对象编程中最重要的三件事是类,数据和方法。 在这里,我们创建一个名为Example
的新类。 Example
类继承自QtGui.QWidget
类。 这意味着我们必须调用两个构造器。 第一个用于Example
类,第二个用于继承类。 使用super()
方法调用第二个。
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Icon')
self.setWindowIcon(QtGui.QIcon('web.png'))
所有这三种方法都从QtGui.QWidget
类继承。 setGeometry()
做两件事。 它在屏幕上找到窗口并设置窗口的大小。 前两个参数是窗口的 x 和 y 位置。 第三个是窗口的宽度,第四个是窗口的高度。 最后一种方法设置应用图标。 为此,我们创建了一个QtGui.QIcon
对象。 QtGui.QIcon
接收到要显示的图标的路径。
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们将启动代码放入main()
方法中。 这是 Python 惯用语。
图:图标
在窗口的左上角可见一个图标。
显示工具提示
我们可以为我们的任何小部件提供气球帮助。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows a tooltip on
a window and a button
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
self.setToolTip('This is a <b>QWidget</b> widget')
btn = QtGui.QPushButton('Button', self)
btn.setToolTip('This is a <b>QPushButton</b> widget')
btn.resize(btn.sizeHint())
btn.move(50, 50)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Tooltips')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在此示例中,我们显示了两个 PySide 小部件的工具提示。
QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
此静态方法设置用于呈现工具提示的字体。 我们使用 10px SansSerif 字体。
self.setToolTip('This is a <b>QWidget</b> widget')
要创建工具提示,我们调用setTooltip()
方法。 我们可以使用富文本格式。
btn = QtGui.QPushButton('Button', self)
btn.setToolTip('This is a <b>QPushButton</b> widget')
我们创建一个按钮小部件并为其设置工具提示。
btn.resize(btn.sizeHint())
btn.move(50, 50)
调整按钮的大小并在窗口上移动。 sizeHint()
方法为按钮提供了建议的大小。
图:工具提示 s
关闭窗口
关闭窗口的明显方法是单击标题栏上的 x 标记。 在下一个示例中,我们将展示如何以编程方式关闭窗口。 我们将简要介绍信号和槽。
以下是QtGui.QPushButton
的构造器,我们将在示例中使用它。
class PySide.QtGui.QPushButton(text[, parent=None])
text
参数是将在按钮上显示的文本。 parent
是窗口小部件,我们在其上放置了按钮。 在我们的情况下,它将是QtGui.QWidget
。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program creates a quit
button. When we press the button,
the application terminates.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
qbtn = QtGui.QPushButton('Quit', self)
qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
qbtn.resize(qbtn.sizeHint())
qbtn.move(50, 50)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Quit button')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在此示例中,我们创建一个退出按钮。 单击窗口后,应用终止。
qbtn = QtGui.QPushButton('Quit', self)
我们创建一个按钮。 该按钮是QtGui.QPushButton
类的实例。 构造器的第一个参数是按钮的标签。 第二个参数是父窗口小部件。 父窗口小部件是Example
窗口小部件,通过继承它是QtGui.QWidget
。
qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
PySide 中的事件处理系统是通过信号和槽机制构建的。 如果单击按钮,将发出信号clicked
。 该槽可以是 Qt 槽或任何可调用的 Python。 QtCore.QCoreApplication
包含主事件循环。 它处理并调度所有事件。 instance()
方法为我们提供了当前实例。 注意,QtCore.QCoreApplication
是用QtGui.QApplication
创建的。 单击的信号连接到quit()
方法,这将终止应用。 通信是在两个对象之间完成的。 发送者和接收者。 发送者是按钮,接收者是应用对象。
图:退出按钮
MessageDialog
默认情况下,如果单击标题栏上的 x 按钮,则QtGui.QWidget
将关闭。 有时我们想修改此默认行为。 例如,如果我们在编辑器中打开了一个文件,对此我们做了一些更改。 我们显示一个消息框以确认操作。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program shows a confirmation
message box when we click on the close
button of the application window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.show()
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Message',
"Are you sure to quit?", QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
如果我们关闭QtGui.QWidget
,则会生成QCloseEvent
。 要修改小部件的行为,我们需要重新实现closeEvent()
事件处理器。
reply = QtGui.QMessageBox.question(self, 'Message',
"Are you sure to quit?", QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
我们显示一个带有两个按钮的消息框。 是和否。第一个字符串显示在标题栏上。 第二个字符串是对话框显示的消息文本。 第三个参数指定出现在对话框中的按钮的组合。 最后一个参数是默认按钮。 它是按钮,最初具有键盘焦点。 返回值存储在回复变量中。
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
在这里,我们测试返回值。 如果单击“是”按钮,我们将接受导致小部件关闭和应用终止的事件。 否则,我们将忽略关闭事件。
图:消息框
屏幕上的居中窗口
以下脚本显示了如何在桌面屏幕上居中放置窗口。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program centers a window
on the screen.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.resize(250, 150)
self.center()
self.setWindowTitle('Center')
self.show()
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在屏幕上将窗口居中。
self.center()
将使窗口居中的代码位于自定义center()
方法中。
qr = self.frameGeometry()
我们得到一个指定主窗口几何形状的矩形。 这包括任何窗框。
cp = QtGui.QDesktopWidget().availableGeometry().center()
我们计算出显示器的屏幕分辨率。 从这个分辨率,我们得到了中心点。
qr.moveCenter(cp)
我们的矩形已经具有宽度和高度。 现在,我们将矩形的中心设置为屏幕的中心。 矩形的大小不变。
self.move(qr.topLeft())
我们将应用窗口的左上角移动到qr
矩形的左上角,从而将窗口居中放置在屏幕上。
在 PySide 教程的这一部分中,我们介绍了一些基础知识。
PySide 中的菜单和工具栏
在 PySide 教程的这一部分中,我们将创建菜单和工具栏。 菜单是位于菜单栏中的一组命令。 工具栏上有带有应用中常用命令的按钮。
主窗口
QtGui.QMainWindow
类提供一个主应用窗口。 这样就可以使用状态栏,工具栏和菜单栏创建经典的应用框架。
状态栏
状态栏是用于显示状态信息的小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program creates a statusbar.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.statusBar().showMessage('Ready')
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Statusbar')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
借助QtGui.QMainWindow
小部件创建状态栏。
self.statusBar().showMessage('Ready')
要获取状态栏,我们调用QtGui.QMainWindow
类的statusBar()
方法。 该方法的第一次调用将创建一个状态栏。 后续调用返回状态栏对象。 showMessage()
在状态栏上显示一条消息。
菜单栏
菜单栏是 GUI 应用的常见部分。 它是位于各个菜单中的一组命令。 在控制台应用中,我们必须记住各种命令及其选项,而在这里,我们将大多数命令分组为逻辑部分。 这些公认的标准可进一步减少学习新应用的时间。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program creates a menubar.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Menubar')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在上面的示例中,我们创建一个带有一个菜单的菜单栏。 该菜单将包含一个操作,如果选择该操作,它将终止该应用。 也会创建一个状态栏。 可通过 Ctrl + Q
快捷方式访问该操作。
exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
PySide QtGui.QAction
是使用菜单栏,工具栏或自定义键盘快捷键执行的操作的抽象。 在以上三行中,我们创建一个带有特定图标和“退出”标签的动作。 此外,为此操作定义了快捷方式。 第三行创建一个状态提示,当我们将鼠标指针悬停在菜单项上方时,状态提示将显示在状态栏中。
exitAction.triggered.connect(self.close)
当我们选择此特定动作时,将触发信号。 信号连接到QtGui.QMainWindow
小部件的close()
方法。 这将终止应用。
menubar = self.menuBar()
创建菜单栏。
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
我们使用菜单栏对象的addMenu()
方法创建文件菜单。 我们将先前创建的操作添加到文件菜单。
工具栏
菜单将我们可以在应用中使用的所有命令分组。 使用工具栏可以快速访问最常用的命令。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program creates a toolbar.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(self.close)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('Toolbar')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在上面的示例中,我们创建了一个简单的工具栏。 工具栏有一个工具动作。 退出动作,关闭应用。
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(self.close)
与上面的菜单栏示例类似,我们创建一个动作对象。 该对象具有标签,图标和快捷方式。 QtGui.QMainWindow
的关闭方法连接到触发信号。
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
在这里,我们创建了一个工具栏,并在其中插入了动作对象。
图:工具栏
把它放在一起
在本节的最后一个示例中,我们将创建一个菜单栏,工具栏和一个状态栏。 我们还将创建一个中央小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This program creates a skeleton of
a classic GUI application with a menubar,
toolbar, statusbar and a central widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
textEdit = QtGui.QTextEdit()
self.setCentralWidget(textEdit)
exitAction = QtGui.QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
toolbar = self.addToolBar('Exit')
toolbar.addAction(exitAction)
self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('Main window')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
此代码示例使用菜单栏,工具栏和状态栏创建经典 GUI 应用的框架。
textEdit = QtGui.QTextEdit()
self.setCentralWidget(textEdit)
在这里,我们创建一个文本编辑小部件。 我们将其设置为QtGui.QMainWindow
的中央小部件。 中央窗口小部件将占据剩余的所有空间。
图:QtGui.QMainWindow
在 PySide 教程的这一部分中,我们使用了菜单,工具栏,状态栏和主应用窗口。
PySide 中的布局管理
GUI 编程中的重要事项是布局管理。 布局管理是我们将小部件放置在窗口上的方式。 管理可以通过两种方式完成。 我们可以使用绝对定位或布局类。
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。 使用绝对定位时,您必须了解几件事。
- 如果我们调整窗口大小,则小部件的大小和位置不会改变
- 各种平台上的应用看起来可能有所不同
- 在我们的应用中更改字体可能会破坏布局
- 如果我们决定更改布局,则必须完全重做布局,这既繁琐又耗时
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows three labels on a window
using absolute positioning.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
label1 = QtGui.QLabel('Zetcode', self)
label1.move(15, 10)
label2 = QtGui.QLabel('tutorials', self)
label2.move(35, 40)
label3 = QtGui.QLabel('for programmers', self)
label3.move(55, 70)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Absolute')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们只需调用move()
方法来定位小部件。 在我们的例子中,这些小部件是标签。 我们通过提供 x 和 y 坐标来定位它们。 坐标系的起点在左上角。 x 值从左到右增长。 y 值从上到下增长。
图:绝对定位
盒子布局
具有布局类的布局管理更加灵活和实用。 这是在窗口上放置小部件的首选方法。 基本布局类是QtGui.QHBoxLayout
和QtGui.QVBoxLayout
。 他们水平和垂直排列小部件。
想象一下,我们想在右下角放置两个按钮。 为了创建这样的布局,我们将使用一个水平框和一个垂直框。 为了创建必要的空间,我们将添加一个stretch factor
。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we position two push
buttons in the bottom-right corner
of the window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
okButton = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Buttons')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例在窗口的右下角放置了两个按钮。 当我们调整应用窗口的大小时,它们会停留在该位置。 我们同时使用QtGui.HBoxLayout
和QtGui.QVBoxLayout
。
okButton = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")
在这里,我们创建两个按钮。
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
我们创建一个水平框布局。 添加一个拉伸因子和两个按钮。 拉伸在两个按钮之前增加了可拉伸的空间。 这会将它们推到窗口的右侧。
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
为了创建必要的布局,我们将水平布局放入垂直布局。 垂直框中的拉伸因子会将带有按钮的水平框推到窗口底部。
self.setLayout(vbox)
最后,我们设置窗口的基本布局。 它是垂直框。
图:按钮示例
网格布局
PySide 中最通用的布局类是网格布局。 此布局将空间分为行和列。 要创建网格布局,我们使用QtGui.QGridLayout
类。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we create a skeleton
of a calculator using a QGridLayout.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
names = ['Cls', 'Bck', '', 'Close', '7', '8', '9', '/',
'4', '5', '6', '*', '1', '2', '3', '-',
'0', '.', '=', '+']
grid = QtGui.QGridLayout()
j = 0
pos = [(0, 0), (0, 1), (0, 2), (0, 3),
(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3),
(3, 0), (3, 1), (3, 2), (3, 3 ),
(4, 0), (4, 1), (4, 2), (4, 3)]
for i in names:
button = QtGui.QPushButton(i)
if j == 2:
grid.addWidget(QtGui.QLabel(''), 0, 2)
else: grid.addWidget(button, pos[j][0], pos[j][1])
j = j + 1
self.setLayout(grid)
self.move(300, 150)
self.setWindowTitle('Calculator')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们创建了一个按钮网格。 为了填补一个空白,我们还添加了一个QtGui.QLabel
小部件。
grid = QtGui.QGridLayout()
在这里,我们创建一个网格布局。
if j == 2:
grid.addWidget(QtGui.QLabel(''), 0, 2)
else: grid.addWidget(button, pos[j][0], pos[j][1])
要将小部件添加到网格,我们调用addWidget()
方法。 参数是小部件,行和列号。
图:计算机骨架
回顾示例
小部件可以跨越网格中的多个列或行。 在下一个示例中,我们将对此进行说明。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we create a bit
more complicated window layout using
the QGridLayout manager.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
title = QtGui.QLabel('Title')
author = QtGui.QLabel('Author')
review = QtGui.QLabel('Review')
titleEdit = QtGui.QLineEdit()
authorEdit = QtGui.QLineEdit()
reviewEdit = QtGui.QTextEdit()
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(title, 1, 0)
grid.addWidget(titleEdit, 1, 1)
grid.addWidget(author, 2, 0)
grid.addWidget(authorEdit, 2, 1)
grid.addWidget(review, 3, 0)
grid.addWidget(reviewEdit, 3, 1, 5, 1)
self.setLayout(grid)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('Review')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们创建一个窗口,其中有三个标签,两个行编辑和一个文本编辑小部件。 使用QtGui.QGridLayout
完成布局。
grid = QtGui.QGridLayout()
grid.setSpacing(10)
我们创建网格布局并设置小部件之间的间距。
grid.addWidget(reviewEdit, 3, 1, 5, 1)
如果我们将小部件添加到网格,则可以提供小部件的行跨度和列跨度。 在我们的例子中,我们使reviewEdit
小部件跨越 5 行。
图:回顾 example
PySide 教程的这一部分专门用于布局管理。
PySide 中的事件和信号
在 PySide 编程教程的这一部分中,我们将探讨应用中发生的事件和信号。
事件
事件是任何 GUI 程序中的重要组成部分。 事件是由用户或系统生成的。 当我们调用应用的exec_()
方法时,应用进入主循环。 主循环获取事件并将其发送到对象。 PySide 具有独特的信号和槽机制。
所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成。 例如互联网连接,窗口管理器,计时器。 在事件模型中,有三个参与者:
- 事件来源
- 事件对象
- 事件目标
event source
是状态改变的对象。 它生成事件。 event object
(事件)将状态更改封装在事件源中。 event target
是要通知的对象。 事件源对象将处理事件的任务委托给事件目标。
当我们调用应用的exec_()
方法时,应用进入主循环。 主循环获取事件并将其发送到对象。 信号和槽用于对象之间的通信。 发生特定事件时会发出signal
。 slot
可以是任何 Python 可调用的。 当发出与其连接的信号时,将调用槽。
信号和槽
这是一个简单的示例,展示了 PySide 中的信号和槽。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we connect a signal
of a QtGui.QSlider to a slot
of a QtGui.QLCDNumber.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
lcd = QtGui.QLCDNumber(self)
sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(sld)
self.setLayout(vbox)
sld.valueChanged.connect(lcd.display)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal & slot')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们显示QtGui.QLCDNumber
和QtGui.QSlider
。 我们通过拖动滑块来更改 LCD 编号。
sld.valueChanged.connect(lcd.display)
在这里,我们将滑块的valueChanged
信号连接到 LCD 编号的display
槽。
sender
是发送信号的对象。 receiver
是接收信号的对象。 slot
是对信号做出反应的方法。
图:信号和槽
重新实现事件处理器
PySide 中的事件通常通过重新实现事件处理器来处理。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we reimplement an
event handler.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Event handler')
self.show()
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape:
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们重新实现了keyPressEvent()
事件处理器。
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Escape:
self.close()
如果单击退出按钮,则应用终止。
事件发送者
有时很容易知道哪个控件是信号的发送者。 为此,PySide 具有sender()
方法。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we determine the event sender
object.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
btn1 = QtGui.QPushButton("Button 1", self)
btn1.move(30, 50)
btn2 = QtGui.QPushButton("Button 2", self)
btn2.move(150, 50)
btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)
self.statusBar()
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Event sender')
self.show()
def buttonClicked(self):
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们的示例中有两个按钮。 在buttonClicked()
方法中,我们通过调用sender()
方法来确定单击了哪个按钮。
btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)
两个按钮都连接到同一槽。
def buttonClicked(self):
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
我们通过调用sender()
方法来确定信号源。 在应用的状态栏中,我们显示了被按下的按钮的标签。
图:事件发送者
发射信号
从QtCore.QObject
创建的对象可以发出信号。 如果单击按钮,将生成clicked
信号。 在下面的示例中,我们将看到如何发出自定义信号。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we show how to emit a
signal.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Communicate(QtCore.QObject):
closeApp = QtCore.Signal()
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.c = Communicate()
self.c.closeApp.connect(self.close)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Emit signal')
self.show()
def mousePressEvent(self, event):
self.c.closeApp.emit()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们创建一个名为closeApp
的新信号。 在鼠标按下事件期间将发出此信号。 信号连接到QtGui.QMainWindow
的close()
槽。
class Communicate(QtCore.QObject):
closeApp = QtCore.Signal()
我们基于QtCore.QObject
创建一个类。 实例化时会创建一个closeApp
信号。
self.c = Communicate()
self.c.closeApp.connect(self.close)
创建Communicate
类的实例。 我们将QtGui.QMainWindow
的close()
槽连接到 closeApp 信号。
def mousePressEvent(self, event):
self.c.closeApp.emit()
当我们用鼠标指针单击窗口时,会发出closeApp
信号。
在 PySide 教程的这一部分中,我们介绍了信号和槽。
PySide 中的对话框
对话框窗口或对话框在现代 GUI 应用中很常见。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。
QtGui.QInputDialog
QtGui.QInputDialog
提供了一个简单的便捷对话框,可从用户那里获取单个值。 输入值可以是字符串,数字或列表中的项目。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we receive data from
a QtGui.QInputDialog dialog.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.btn = QtGui.QPushButton('Dialog', self)
self.btn.move(20, 20)
self.btn.clicked.connect(self.showDialog)
self.le = QtGui.QLineEdit(self)
self.le.move(130, 22)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Input dialog')
self.show()
def showDialog(self):
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog',
'Enter your name:')
if ok:
self.le.setText(str(text))
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例具有一个按钮和一个行编辑小部件。 该按钮显示用于获取文本值的输入对话框。 输入的文本将显示在行编辑小部件中。
text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog',
'Enter your name:')
这行显示输入对话框。 第一个字符串是对话框标题,第二个字符串是对话框中的消息。 对话框返回输入的文本和布尔值。 如果单击“确定”按钮,则布尔值将为true
,否则为false
。
if ok:
self.le.setText(str(text))
我们从对话框中收到的文本设置为行编辑小部件。
图:输入对话框
QtGui.QColorDialog
QtGui.QColorDialog
提供了一个对话框小部件,用于选择颜色。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we select a colour value
from the QtGui.QColorDialog and change the background
colour of a QtGui.QFrame widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
col = QtGui.QColor(0, 0, 0)
self.btn = QtGui.QPushButton('Dialog', self)
self.btn.move(20, 20)
self.btn.clicked.connect(self.showDialog)
self.frm = QtGui.QFrame(self)
self.frm.setStyleSheet("QWidget { background-color: %s }"
% col.name())
self.frm.setGeometry(130, 22, 100, 100)
self.setGeometry(300, 300, 250, 180)
self.setWindowTitle('Color dialog')
self.show()
def showDialog(self):
col = QtGui.QColorDialog.getColor()
if col.isValid():
self.frm.setStyleSheet("QWidget { background-color: %s }"
% col.name())
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该应用示例显示了一个按钮和一个QtGui.QFrame
。 窗口小部件背景设置为黑色。 使用QtGui.QColorDialog
,我们可以更改其背景。
col = QtGui.QColor(0, 0, 0)
这是QtGui.QFrame
背景的初始颜色。
col = QtGui.QColorDialog.getColor()
这行会弹出QtGui.QColorDialog
。
if col.isValid():
self.frm.setStyleSheet("QWidget { background-color: %s }"
% col.name())
我们检查颜色是否有效。 如果单击取消按钮,则不会返回有效颜色。 如果颜色有效,我们将使用样式表更改背景颜色。
图:颜色对话框
QtGui.QFontDialog
QtGui.QFontDialog
是用于选择字体的对话框小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we select a font name
and change the font of a label.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
vbox = QtGui.QVBoxLayout()
btn = QtGui.QPushButton('Dialog', self)
btn.setSizePolicy(QtGui.QSizePolicy.Fixed,
QtGui.QSizePolicy.Fixed)
btn.move(20, 20)
vbox.addWidget(btn)
btn.clicked.connect(self.showDialog)
self.lbl = QtGui.QLabel('Knowledge only matters', self)
self.lbl.move(130, 20)
vbox.addWidget(self.lbl)
self.setLayout(vbox)
self.setGeometry(300, 300, 250, 180)
self.setWindowTitle('Font dialog')
self.show()
def showDialog(self):
font, ok = QtGui.QFontDialog.getFont()
if ok:
self.lbl.setFont(font)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有一个按钮和一个标签。 使用QtGui.QFontDialog
,我们更改标签的字体。
font, ok = QtGui.QFontDialog.getFont()
在这里我们弹出字体对话框。 getFont()
方法返回字体名称和ok
参数。 如果用户单击“确定”,则等于True
。 否则为False
。
if ok:
self.label.setFont(font)
如果单击确定,则标签的字体将被更改。
QtGui.QFileDialog
QtGui.QFileDialog
是允许用户选择文件或目录的对话框。 可以选择打开和保存文件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we select a file with a
QtGui.QFileDialog and display its contents
in a QtGui.QTextEdit.
author: Jan Bodnar
website: zetcode.com
last edited: October 2011
"""
import sys
from PySide import QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.textEdit = QtGui.QTextEdit()
self.setCentralWidget(self.textEdit)
self.statusBar()
openFile = QtGui.QAction(QtGui.QIcon('open.png'), 'Open', self)
openFile.setShortcut('Ctrl+O')
openFile.setStatusTip('Open new File')
openFile.triggered.connect(self.showDialog)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(openFile)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('File dialog')
self.show()
def showDialog(self):
fname, _ = QtGui.QFileDialog.getOpenFileName(self, 'Open file',
'/home')
f = open(fname, 'r')
with f:
data = f.read()
self.textEdit.setText(data)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例显示了一个菜单栏,集中设置的文本编辑小部件和一个状态栏。 菜单项显示QtGui.QFileDialog
,用于选择文件。 文件的内容被加载到文本编辑小部件中。
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
该示例基于QtGui.QMainWindow
小部件。 我们可以轻松地创建状态栏,工具栏和中央小部件。
fname, _ = QtGui.QFileDialog.getOpenFileName(self, 'Open file',
'/home')
我们弹出QtGui.QFileDialog
。 getOpenFileName()
方法中的第一个字符串是标题。 第二个字符串指定对话框的工作目录。 该方法返回选定的文件名和过滤器。 我们只对文件名感兴趣。
f = open(fname, 'r')
with f:
data = f.read()
self.textEdit.setText(data)
读取所选文件名,并将文件内容设置为文本编辑小部件。
在 PySide 教程的这一部分中,我们使用了对话框。
Windows API 中的自定义控件
在这里,我们将演示如何创建自己的自定义控件。 Windows API 具有各种预先构建的控件的集合。 必须手动创建更多特定的控件。 我们使用 GDI 创建自定义控件。
刻录控件
可以在各种媒体刻录应用中找到此控件,例如 Nero 刻录 ROM。
#include <windows.h>
#include <commctrl.h>
#include <wchar.h>
LRESULT CALLBACK PanelProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hinst;
LRESULT g_pos = 150;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Application";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
g_hinst = hInstance;
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Burning control",
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
100, 100, 400, 250, 0, 0, hInstance, 0);
while( GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
static HWND hwndTrack, hwndBurn;
WNDCLASSW rwc = {0};
INITCOMMONCONTROLSEX InitCtrlEx;
InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCtrlEx.dwICC = ICC_BAR_CLASSES;
InitCommonControlsEx(&InitCtrlEx);
switch(msg)
{
case WM_CREATE:
rwc.lpszClassName = L"BurningControl";
rwc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
rwc.style = CS_HREDRAW;
rwc.lpfnWndProc = PanelProc;
rwc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&rwc);
hwndBurn = CreateWindowExW(WS_EX_STATICEDGE , L"BurningControl", NULL,
WS_CHILD | WS_VISIBLE, 0, 330, 490, 30, hwnd, (HMENU)1, NULL, NULL);
hwndTrack = CreateWindowExW(0, TRACKBAR_CLASSW, NULL,
WS_CHILD | WS_VISIBLE | TBS_FIXEDLENGTH | TBS_NOTICKS,
40, 25, 150, 25, hwnd, (HMENU) 2, g_hinst, NULL);
SendMessage(hwndTrack, TBM_SETRANGE, TRUE, MAKELONG(0, 750));
SendMessage(hwndTrack, TBM_SETPAGESIZE, 0, 20);
SendMessage(hwndTrack, TBM_SETTICFREQ, 20, 0);
SendMessage(hwndTrack, TBM_SETPOS, TRUE, 150);
break;
case WM_SIZE:
SetWindowPos(hwndBurn, NULL, 0, HIWORD(lParam)-30,
LOWORD(lParam), 30, SWP_NOZORDER);
break;
case WM_HSCROLL:
g_pos = SendMessage(hwndTrack, TBM_GETPOS, 0, 0);
InvalidateRect(hwndBurn, NULL, TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK PanelProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
HBRUSH hBrushYellow, hBrushRed, holdBrush;
HPEN hPen, holdPen;
HFONT hFont, holdFont;
PAINTSTRUCT ps;
RECT rect, rect2;
wchar_t *cap[] = { L"75", L"150", L"225", L"300", L"375", L"450",
L"525", L"600", L"675"};
HDC hdc;
int till;
int step, full;
int i;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
till = (rect.right / 750.0) * g_pos;
step = rect.right / 10.0;
full = (rect.right / 750.0) * 700;
hBrushYellow = CreateSolidBrush(RGB(255, 255, 184));
hBrushRed = CreateSolidBrush(RGB(255, 110, 110));
hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
holdPen = SelectObject(hdc, hPen);
hFont = CreateFontW(13, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0,
0, 0, 0, 0, L"Tahoma");
holdFont = SelectObject(hdc, hFont);
if(till > full) {
SelectObject(hdc, hBrushYellow);
Rectangle(hdc, 0, 0, full, 30);
holdBrush = SelectObject(hdc, hBrushRed);
Rectangle(hdc, full, 0, till, 30);
} else {
holdBrush = SelectObject(hdc, hBrushYellow);
Rectangle(hdc, 0, 0, till, 30);
}
SelectObject(hdc, holdPen);
for ( i = 1; i < 10; i++) {
MoveToEx(hdc, i*step, 0, NULL);
LineTo(hdc, i*step, 7);
rect2.bottom = 28;
rect2.top = 8;
rect2.left = i*step-10;
rect2.right = i*step+10;
SetBkMode(hdc, TRANSPARENT) ;
DrawTextW(hdc, cap[i-1], wcslen(cap[i-1]), &rect2, DT_CENTER);
}
SelectObject(hdc, holdBrush);
DeleteObject(hBrushYellow);
DeleteObject(hBrushRed);
DeleteObject(hPen);
SelectObject(hdc, holdFont);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
在我们的示例中,我们显示一个轨迹栏控件和我们的自定义刻录控件。 跟踪栏控件用于控制刻录控件的状态。
刻录控件是一个简单的窗口。 它放置在父窗口的底部。 它在WM_PAINT
消息期间被完全绘制。 使用 GDI 函数调用绘制线条,文本和背景。
图:刻录控件
在 Windows API 教程的这一部分中,我们创建了刻录自定义控件。
PySide 小部件
小部件是应用的基本构建块。 PySide 编程工具包包含各种小部件。 按钮,复选框,滑块,列表框等。程序员完成工作所需的一切。 在本教程的这一部分中,我们将描述几个有用的小部件。 即QtGui.QCheckBox
,ToggleButton
,QtGui.QSlider
,QtGui.QProgressBar
和QtGui.QCalendarWidget
。
QtGui.QCheckBox
QtGui.QCheckBox
是具有两种状态的窗口小部件:打开和关闭。 这是一个带有标签的盒子。 复选框通常用于表示应用中可以启用或禁用而不会影响其他功能的功能。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, a QtGui.QCheckBox widget
is used to toggle the title of a window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
cb = QtGui.QCheckBox('Show title', self)
cb.move(20, 20)
cb.toggle()
cb.stateChanged.connect(self.changeTitle)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('QtGui.QCheckBox')
self.show()
def changeTitle(self, state):
if state == QtCore.Qt.Checked:
self.setWindowTitle('Checkbox')
else:
self.setWindowTitle('')
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们将创建一个复选框,以切换窗口标题。
cb = QtGui.QCheckBox('Show title', self)
这是QtGui.QCheckBox
构造器。
cb.toggle()
我们设置了窗口标题,因此我们还必须选中该复选框。
cb.stateChanged.connect(self.changeTitle)
我们将用户定义的changeTitle()
方法连接到stateChanged
信号。 changeTitle()
方法将切换窗口标题。
def changeTitle(self, state):
if state == QtCore.Qt.Checked:
self.setWindowTitle('Checkbox')
else:
self.setWindowTitle('')
我们在状态变量中接收复选框的状态。 如果已设置,则设置窗口的标题。 否则,我们使用一个空字符串作为标题。
图:QtGui.QCheckBox
开关按钮
PySide 没有用于开关按钮的小部件。 要创建开关按钮,我们在特殊模式下使用QtGui.QPushButton
。 开关按钮是具有两种状态的按钮。 已按下但未按下。 通过单击可以在这两种状态之间切换。 在某些情况下此功能非常合适。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we create three toggle buttons.
They will control the background color of a
QtGui.QFrame.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.col = QtGui.QColor(0, 0, 0)
redb = QtGui.QPushButton('Red', self)
redb.setCheckable(True)
redb.move(10, 10)
redb.clicked[bool].connect(self.setColor)
greenb = QtGui.QPushButton('Green', self)
greenb.setCheckable(True)
greenb.move(10, 60)
greenb.clicked[bool].connect(self.setColor)
blueb = QtGui.QPushButton('Blue', self)
blueb.setCheckable(True)
blueb.move(10, 110)
blueb.clicked[bool].connect(self.setColor)
self.square = QtGui.QFrame(self)
self.square.setGeometry(150, 20, 100, 100)
self.square.setStyleSheet("QWidget { background-color: %s }" %
self.col.name())
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Toggle button')
self.show()
def setColor(self, pressed):
source = self.sender()
if pressed:
val = 255
else: val = 0
if source.text() == "Red":
self.col.setRed(val)
elif source.text() == "Green":
self.col.setGreen(val)
else:
self.col.setBlue(val)
self.square.setStyleSheet("QFrame { background-color: %s }" %
self.col.name())
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们创建了三个 ToggleButtons。 我们还创建了一个QtGui.QFrame
小部件。 我们将小部件的背景色设置为黑色。 切换按钮将切换颜色值的红色,绿色和蓝色部分。 背景颜色取决于我们按下的切换按钮。
self.col = QtGui.QColor(0, 0, 0)
这是初始颜色值。 它是黑色的。
greenb = QtGui.QPushButton('Green', self)
greenb.setCheckable(True)
要创建开关按钮,我们创建一个QtGui.QPushButton
并通过调用setCheckable()
方法使其可检查。
greenb.clicked[bool].connect(self.setColor)
我们将clicked[bool]
信号连接到用户定义的方法。 请注意,此信号类型将bool
参数发送给该方法。 参数值是 true 还是 false,取决于按钮的状态,例如是否检查/切换。
source = self.sender()
我们得到信号的发送者。 这是被切换的按钮。
if source.text() == "Red":
self.col.setRed(val)
如果它是红色按钮,我们将相应地更新颜色的红色部分。
self.square.setStyleSheet("QFrame { background-color: %s }" %
self.col.name())
我们使用样式表来更改QtGui.QFrame
小部件的背景颜色。
图:ToggleButton
QtGui.QSlider
QtGui.QSlider
是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以为特定任务选择一个值。 有时使用滑块比只提供一个数字或使用旋转框更为自然。 QtGui.QLabel
显示文本或图像。
在我们的示例中,我们将显示一个滑块和一个标签。 这次,标签将显示图像。 滑块将控制标签。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows a QtGui.QSlider widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
sld.setFocusPolicy(QtCore.Qt.NoFocus)
sld.setGeometry(30, 40, 100, 30)
sld.valueChanged[int].connect(self.changeValue)
self.label = QtGui.QLabel(self)
self.label.setPixmap(QtGui.QPixmap('mute.png'))
self.label.setGeometry(160, 40, 80, 30)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QtGui.QSlider')
self.show()
def changeValue(self, value):
if value == 0:
self.label.setPixmap(QtGui.QPixmap('mute.png'))
elif value > 0 and value <= 30:
self.label.setPixmap(QtGui.QPixmap('min.png'))
elif value > 30 and value < 80:
self.label.setPixmap(QtGui.QPixmap('med.png'))
else:
self.label.setPixmap(QtGui.QPixmap('max.png'))
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们模拟了音量控制。 通过拖动滑块的手柄,我们可以更改标签上的图像。
sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
在这里,我们创建一个水平QtGui.QSlider
。
self.label = QtGui.QLabel(self)
self.label.setPixmap(QtGui.QPixmap('mute.png'))
我们创建一个QtGui.QLabel
小部件。 并为其设置初始静音图像。
sld.valueChanged[int].connect(self.changeValue)
我们将valueChanged[int]
信号连接到用户定义的changeValue()
方法。
if value == 0:
self.label.setPixmap(QtGui.QPixmap('mute.png'))
...
基于滑块的值,我们将图像设置为标签。 在上面的代码中,如果滑块值等于零,则将mute.png
图像设置为标签。
图:QtGui.QSlider
小部件
QtGui.QProgressBar
进度条是当我们处理冗长的任务时使用的小部件。 它具有动画效果,以便用户知道我们的任务正在进行中。 QtGui.QProgressBar
小部件在 PySide 工具箱中提供了水平或垂直进度条。 程序员可以为进度条设置最小值和最大值。 默认值为 0,99。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows a QtGui.QProgressBar widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.pbar = QtGui.QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QtGui.QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.doAction)
self.timer = QtCore.QBasicTimer()
self.step = 0
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QtGui.QProgressBar')
self.show()
def timerEvent(self, e):
if self.step >= 100:
self.timer.stop()
self.btn.setText('Finished')
return
self.step = self.step + 1
self.pbar.setValue(self.step)
def doAction(self):
if self.timer.isActive():
self.timer.stop()
self.btn.setText('Start')
else:
self.timer.start(100, self)
self.btn.setText('Stop')
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有一个水平进度条和一个按钮。 该按钮将启动和停止进度条。
self.pbar = QtGui.QProgressBar(self)
这是一个QtGui.QProgressBar
构造器。
self.timer = QtCore.QBasicTimer()
要激活进度条,我们使用计时器对象。
self.timer.start(100, self)
要启动计时器事件,我们调用start()
方法。 此方法有两个参数。 超时和将接收事件的对象。
def timerEvent(self, e):
if self.step >= 100:
self.timer.stop()
self.btn.setText('Finished')
return
self.step = self.step + 1
self.pbar.setValue(self.step)
每个QtCore.QObject
及其子代都有一个timerEvent()
事件处理器。 为了对计时器事件做出反应,我们重新实现了事件处理器。 我们更新self.step
变量,并为进度栏小部件设置一个新值。
def doAction(self):
if self.timer.isActive():
self.timer.stop()
self.btn.setText('Start')
else:
self.timer.start(100, self)
self.btn.setText('Stop')
在doAction()
方法中,我们启动和停止计时器。
图:QtGui.QProgressBar
QtGui.QCalendarWidget
QtGui.QCalendarWidget
提供基于月度的日历小部件。 它允许用户以简单直观的方式选择日期。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows a QtGui.QCalendarWidget widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
cal = QtGui.QCalendarWidget(self)
cal.setGridVisible(True)
cal.move(20, 20)
cal.clicked[QtCore.QDate].connect(self.showDate)
self.lbl = QtGui.QLabel(self)
date = cal.selectedDate()
self.lbl.setText(date.toString())
self.lbl.move(130, 260)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('Calendar')
self.show()
def showDate(self, date):
self.lbl.setText(date.toString())
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例具有日历小部件和标签小部件。 当前选择的日期显示在标签窗口小部件中。
self.cal = QtGui.QCalendarWidget(self)
我们构造一个日历小部件。
cal.clicked[QtCore.QDate].connect(self.showDate)
如果我们从小部件中选择一个日期,则会发出clicked[QtCore.QDate]
信号。 我们将此信号连接到用户定义的showDate()
方法。
def showDate(self, date):
self.lbl.setText(date.toString())
我们调用selectedDate()
方法检索所选日期。 然后,我们将日期对象转换为字符串并将其设置为标签小部件。
图:QtGui.QCalendarWidget
小部件
在 PySide 教程的这一部分中,我们介绍了几个小部件。
PySide 小部件 II
在这里,我们将继续介绍 PySide 小部件。 我们将介绍QtGui.QPixmap
,QtGui.QLineEdit
,QtGui.QSplitter
和QtGui.QComboBox
。
QtGui.QPixmap
QtGui.QPixmap
是用于处理图像的小部件之一。 它经过优化,可在屏幕上显示图像。 在我们的代码示例中,我们将使用QtGui.QPixmap
在窗口上显示图像。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we dispay an image
on the window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
pixmap = QtGui.QPixmap("redrock.png")
lbl = QtGui.QLabel(self)
lbl.setPixmap(pixmap)
hbox.addWidget(lbl)
self.setLayout(hbox)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Red Rock')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们在窗口上显示图像。 我们使用QtGui.QPixmap
从文件中加载图像,并使用QtGui.QLabel
小部件在窗口上显示图像。
pixmap = QtGui.QPixmap("redrock.png")
我们创建一个QtGui.QPixmap
对象。 它以文件名作为参数。
lbl = QtGui.QLabel(self)
lbl.setPixmap(pixmap)
我们将像素图放入QtGui.QLabel
小部件。
QtGui.QLineEdit
QtGui.QLineEdit
是一个小部件,允许输入和编辑单行纯文本。 QtGui.QLineEdit
小部件具有撤消/重做,剪切/粘贴和拖放功能。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows text which
is entered in a QtGui.QLineEdit
in a QtGui.QLabel widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.lbl = QtGui.QLabel(self)
qle = QtGui.QLineEdit(self)
qle.move(60, 100)
self.lbl.move(60, 40)
qle.textChanged[str].connect(self.onChanged)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QtGui.QLineEdit')
self.show()
def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
此示例显示了行编辑小部件和标签。 我们在行编辑中键入的文本会立即显示在标签窗口小部件中。
qle = QtGui.QLineEdit(self)
QtGui.QLineEdit
小部件已创建。
qle.textChanged[str].connect(self.onChanged)
如果行编辑窗口小部件中的文本更改,我们将调用onChanged()
方法。
def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
在onChanged()
方法内部,我们将键入的文本设置为标签小部件。 我们调用adjustSize()
方法将标签的大小调整为文本的长度。
图:QtGui.QLineEdit
QtGui.QSplitter
QtGui.QSplitter
允许用户通过拖动子控件之间的边界来控制子控件的大小。 在我们的示例中,我们显示了由两个拆分器组成的三个QtGui.QFrame
小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows
how to use QtGui.QSplitter widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
topleft = QtGui.QFrame(self)
topleft.setFrameShape(QtGui.QFrame.StyledPanel)
topright = QtGui.QFrame(self)
topright.setFrameShape(QtGui.QFrame.StyledPanel)
bottom = QtGui.QFrame(self)
bottom.setFrameShape(QtGui.QFrame.StyledPanel)
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)
splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QSplitter')
self.show()
def onChanged(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有三个框架小部件和两个拆分器。
topleft = QtGui.QFrame(self)
topleft.setFrameShape(QtGui.QFrame.StyledPanel)
我们使用样式化的框架以查看QtGui.QFrame
小部件之间的边界。
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)
我们创建一个QtGui.QSplitter
小部件,并在其中添加两个框架。
splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter2.addWidget(splitter1)
我们还可以将分割器添加到另一个分割器小部件。
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
我们使用Cleanlooks
风格。 在某些样式中,框架不可见。
图:QtGui.QSplitter
小部件
QtGui.QComboBox
QtGui.QComboBox
是一个小部件,允许用户从选项列表中进行选择。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example shows
how to use QtGui.QComboBox widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.lbl = QtGui.QLabel("Ubuntu", self)
combo = QtGui.QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Red Hat")
combo.addItem("Gentoo")
combo.move(50, 50)
self.lbl.move(50, 150)
combo.activated[str].connect(self.onActivated)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QtGui.QComboBox')
self.show()
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该示例显示了QtGui.QComboBox
和QtGui.QLabel
。 组合框具有五个选项的列表。 这些是 Linux 发行版的名称。 标签窗口小部件显示了从组合框中选择的选项。
combo = QtGui.QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Red Hat")
combo.addItem("Gentoo")
我们创建一个QtGui.QComboBox
小部件,并在其中添加五个选项。
combo.activated[str].connect(self.onActivated)
选择项目后,我们调用onActivated()
方法。
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
在方法内部,我们将所选项目的文本设置为标签小部件。 我们调整标签的大小。
图:QtGui.QComboBox
在 PySide 教程的这一部分中,我们介绍了其他四个 PySide 小部件。
在 PySide 中拖放
在 PySide 教程的这一部分中,我们将讨论拖放操作。
在计算机图形用户界面中,拖放是单击虚拟对象并将其拖动到其他位置或另一个虚拟对象上的动作(或支持以下动作)。 通常,它可用于调用多种动作,或在两个抽象对象之间创建各种类型的关联。 (维基百科)
拖放功能是图形用户界面最明显的方面之一。 拖放操作使用户可以直观地完成复杂的事情。
通常,我们可以拖放两件事。 数据或某些图形对象。 如果将图像从一个应用拖到另一个应用,则会拖放二进制数据。 如果我们在 Firefox 中拖动选项卡并将其移动到另一个位置,则将拖放图形组件。
简单的拖放
在第一个示例中,我们将具有QtGui.QLineEdit
和QtGui.QPushButton
。 我们将从行编辑小部件中拖动纯文本并将其拖放到按钮小部件上。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This is a simple drag and
drop example.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
self.setText(e.mimeData().text())
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
qe = QtGui.QLineEdit('', self)
qe.setDragEnabled(True)
qe.move(30, 65)
button = Button("Button", self)
button.move(190, 65)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Simple Drag & Drop')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
简单拖动&放下操作。
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
为了在QtGui.QPushButton
小部件上放置文本,我们必须重新实现一些方法。 因此,我们创建了自己的Button
类,该类将从QtGui.QPushButton
类继承。
self.setAcceptDrops(True)
我们为小部件启用放置事件。
def dragEnterEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
e.accept()
else:
e.ignore()
首先,我们重新实现dragEnterEvent()
方法。 我们会告知数据类型,我们将接受。 在我们的情况下,它是纯文本。
def dropEvent(self, e):
self.setText(e.mimeData().text())
通过重新实现dropEvent()
方法,我们将定义在放置事件上将要执行的操作。 在这里,我们更改按钮小部件的文本。
qe = QtGui.QLineEdit('', self)
qe.setDragEnabled(True)
QtGui.QLineEdit
小部件具有对拖动操作的内置支持。 我们需要做的就是调用setDragEnabled()
方法来激活它。
图:简单 Drag & Drop
拖动和放置一个按钮小部件
在下面的示例中,我们将演示如何拖放按钮小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we drag and drop a
button.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != QtCore.Qt.RightButton:
return
mimeData = QtCore.QMimeData()
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())
dropAction = drag.start(QtCore.Qt.MoveAction)
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.btn = Button('Button', self)
self.btn.move(100, 65)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Click or move')
self.show()
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
position = e.pos()
self.btn.move(position)
e.setDropAction(QtCore.Qt.MoveAction)
e.accept()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的代码示例中,窗口上有一个QtGui.QPushButton
。 如果我们用鼠标左键单击该按钮,则会在控制台上打印'press'
。 通过右键单击并移动按钮,我们在按钮小部件上执行拖放操作。
class Button(QtGui.QPushButton):
def __init__(self, title, parent):
super(Button, self).__init__(title, parent)
我们创建一个Button
类,该类将从QtGui.QPushButton
派生。 我们还重新实现了QtGui.QPushButton
的两种方法mouseMoveEvent()
和mousePressEvent()
。mouseMoveEvent()
方法是拖放操作开始的地方。
if event.buttons() != QtCore.Qt.RightButton:
return
在这里,我们决定只能使用鼠标右键执行拖放操作。 鼠标左键保留用于单击该按钮。
mimeData = QtCore.QMimeData()
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(event.pos() - self.rect().topLeft())
在这里,我们创建一个QtGui.QDrag
对象。
dropAction = drag.start(QtCore.Qt.MoveAction)
拖动对象的start()
方法开始拖放操作。
def mousePressEvent(self, e):
QtGui.QPushButton.mousePressEvent(self, e)
if e.button() == QtCore.Qt.LeftButton:
print 'press'
如果我们用鼠标左键单击按钮,我们将在控制台上打印'press'
。 注意,我们也在父对象上调用了mousePressEvent()
方法。 否则,我们将看不到按钮被按下。
position = e.pos()
self.btn.move(position)
在dropEvent()
方法中,我们编码释放鼠标键并完成放置操作后发生的情况。 我们找出当前鼠标指针的位置并相应地移动一个按钮。
e.setDropAction(QtCore.Qt.MoveAction)
e.accept()
我们指定放置动作的类型。 在我们的情况下,这是一个动作。
PySide 教程的这一部分专门用于拖放。
在 PySide 中绘图
当我们要更改或增强现有的小部件时,使用绘图。 或者,如果我们要从头开始创建自定义窗口小部件。 要进行绘制,我们使用 PySide 工具箱提供的绘制 API。
绘图是在paintEvent()
方法中完成的。 绘图代码放置在QtGui.QPainter
对象的begin()
和end()
方法之间。
绘制文字
我们首先在窗口客户区上绘制一些 Unicode 文本。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we draw text in Russian azbuka.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Draw text')
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
def drawText(self, event, qp):
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们在西里尔字母中绘制了一些文本。 文本在垂直和水平方向上对齐。
def paintEvent(self, event):
绘制是在绘画事件内完成的。
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
QtGui.QPainter
类负责所有低级绘图。 所有绘图方法都在begin()
和end()
方法之间。 实际绘图将委托给drawText()
方法。
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
在这里,我们定义用于绘制文本的笔和字体。
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
drawText()
方法在窗口上绘制文本。 绘画事件的rect()
方法返回需要更新的矩形。
图:绘制文本
绘制点
点是可以绘制的最简单的图形对象。 这是窗口上的一个小地方。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In the example, we draw randomly 1000 red points
on the window.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys, random
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Points')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()
def drawPoints(self, qp):
qp.setPen(QtCore.Qt.red)
size = self.size()
for i in range(1000):
x = random.randint(1, size.width()-1)
y = random.randint(1, size.height()-1)
qp.drawPoint(x, y)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们在客户区域上随机绘制了 1000 个红点。
qp.setPen(QtCore.Qt.red)
我们将笔设置为红色。 我们使用预定义的颜色常数。
size = self.size()
每次我们调整窗口大小时,都会生成一个绘制事件。 我们使用size()
方法获得窗口的当前大小。 我们使用窗口的大小将点分布在整个窗口的客户区域中。
qp.drawPoint(x, y)
我们用drawPoint()
方法画点。
图:点
色彩
颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 有效的 RGB 值在 0 到 255 之间。我们可以通过多种方式定义颜色。 最常见的是 RGB 十进制值或十六进制值。 我们还可以使用 RGBA 值,代表红色,绿色,蓝色,Alpha。 在这里,我们添加了一些有关透明度的额外信息。 Alpha 值为 255 表示完全不透明,0 表示完全透明,例如颜色是不可见的。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example draws three rectangles in three
different colors.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 350, 100)
self.setWindowTitle('Colors')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawRectangles(qp)
qp.end()
def drawRectangles(self, qp):
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
qp.setPen(color)
qp.setBrush(QtGui.QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
qp.setBrush(QtGui.QColor(255, 80, 0, 160))
qp.drawRect(130, 15, 90, 60)
qp.setBrush(QtGui.QColor(25, 0, 90, 200))
qp.drawRect(250, 15, 90, 60)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们绘制了 3 个彩色矩形。
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
在这里,我们使用十六进制符号定义颜色。
qp.setPen(color)
上面定义的颜色用于笔,用于绘制形状的轮廓。
qp.setBrush(QtGui.QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
在这里,我们定义了一个画笔并绘制了一个矩形。 画笔是基本图形对象,用于绘制形状的背景。 drawRect()
方法接受四个参数。 前两个是轴上的 x,y 值。 第三个和第四个参数是矩形的宽度和高度。 该方法使用当前的笔和当前的笔刷绘制一个矩形。
图:颜色
QtGui.QPen
QtGui.QPen
是基本图形对象。 它用于绘制矩形,椭圆形,多边形或其他形状的线,曲线和轮廓。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example draws three rectangles in three
different colors.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 270)
self.setWindowTitle('Pen styles')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()
def drawLines(self, qp):
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(20, 40, 250, 40)
pen.setStyle(QtCore.Qt.DashLine)
qp.setPen(pen)
qp.drawLine(20, 80, 250, 80)
pen.setStyle(QtCore.Qt.DashDotLine)
qp.setPen(pen)
qp.drawLine(20, 120, 250, 120)
pen.setStyle(QtCore.Qt.DotLine)
qp.setPen(pen)
qp.drawLine(20, 160, 250, 160)
pen.setStyle(QtCore.Qt.DashDotDotLine)
qp.setPen(pen)
qp.drawLine(20, 200, 250, 200)
pen.setStyle(QtCore.Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
qp.drawLine(20, 240, 250, 240)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们绘制了六条线。 线条以六种不同的笔样式绘制。 有五种预定义的笔样式。 我们还可以创建自定义笔样式。 最后一行是使用自定义笔样式绘制的。
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)
我们创建一个QtGui.QPen
对象。 颜色是黑色。 宽度设置为 2 像素,以便我们可以看到笔样式之间的差异。 QtCore.Qt.SolidLine
是预定义的笔样式之一。
pen.setStyle(QtCore.Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
在这里,我们定义了自定义笔样式。 我们设置QtCore.Qt.CustomDashLine
笔样式并调用setDashPattern()
方法。 数字列表定义样式。 数字必须是偶数。 奇数定义笔划线,偶数空格。 数字越大,空格或笔划线越大。 我们的模式是 1px 笔划线 4px 空格 5px 笔划线 4px 空格等。
图:笔的样式
QtGui.QBrush
QtGui.QBrush
是基本图形对象。 它用于绘制图形形状的背景,例如矩形,椭圆形或多边形。 刷子可以是三种不同的类型。 预定义的画笔可以绘制渐变或纹理图案。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This example draws 9 rectangles in different
brush styles.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 355, 280)
self.setWindowTitle('Brushes')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawBrushes(qp)
qp.end()
def drawBrushes(self, qp):
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense1Pattern)
qp.setBrush(brush)
qp.drawRect(130, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense2Pattern)
qp.setBrush(brush)
qp.drawRect(250, 15, 90, 60)
brush.setStyle(QtCore.Qt.Dense3Pattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)
brush.setStyle(QtCore.Qt.DiagCrossPattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)
brush.setStyle(QtCore.Qt.Dense5Pattern)
qp.setBrush(brush)
qp.drawRect(130, 105, 90, 60)
brush.setStyle(QtCore.Qt.Dense6Pattern)
qp.setBrush(brush)
qp.drawRect(250, 105, 90, 60)
brush.setStyle(QtCore.Qt.HorPattern)
qp.setBrush(brush)
qp.drawRect(10, 195, 90, 60)
brush.setStyle(QtCore.Qt.VerPattern)
qp.setBrush(brush)
qp.drawRect(130, 195, 90, 60)
brush.setStyle(QtCore.Qt.BDiagPattern)
qp.setBrush(brush)
qp.drawRect(250, 195, 90, 60)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们使用 9 种不同的画笔样式绘制 9 个不同的矩形。
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
我们定义一个笔刷对象。 将其设置为画家对象。 并绘制调用drawRect()
方法的矩形。 这是图片中显示的第一个矩形。
图:笔刷
在 PySide 教程的这一部分中,我们做了一些基本的绘图。
PySide 中的自定义小部件
PySide 在各种小部件上都很丰富。 没有工具包可以向程序员提供在其应用中可能需要的所有小部件。 工具箱通常仅提供最常见的窗口小部件,例如按钮,文本窗口小部件,滑块等。如果需要更专业的窗口小部件,则必须自己创建。
使用工具箱提供的绘图工具创建自定义窗口小部件。 有两种可能性。 程序员可以修改或增强现有的小部件。 或者,他可以从头开始创建自定义窗口小部件。
刻录小部件
这是一个小部件,我们可以在 Nero,K3B 或其他 CD/DVD 刻录软件中看到。 我们完全从头开始创建小部件。 它基于最小的QtGui.QWidget
小部件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
In this example, we create a custom widget.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys
from PySide import QtGui, QtCore
class Communicate(QtCore.QObject):
updateBW = QtCore.Signal(int)
class BurningWidget(QtGui.QWidget):
def __init__(self):
super(BurningWidget, self).__init__()
self.initUI()
def initUI(self):
self.setMinimumSize(1, 30)
self.value = 75
self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
def setValue(self, value):
self.value = value
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawWidget(qp)
qp.end()
def drawWidget(self, qp):
font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
qp.setFont(font)
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
if self.value >= 700:
qp.setPen(QtGui.QColor(255, 255, 255))
qp.setBrush(QtGui.QColor(255, 255, 184))
qp.drawRect(0, 0, full, h)
qp.setPen(QtGui.QColor(255, 175, 175))
qp.setBrush(QtGui.QColor(255, 175, 175))
qp.drawRect(full, 0, till-full, h)
else:
qp.setPen(QtGui.QColor(255, 255, 255))
qp.setBrush(QtGui.QColor(255, 255, 184))
qp.drawRect(0, 0, till, h)
pen = QtGui.QPen(QtGui.QColor(20, 20, 20), 1,
QtCore.Qt.SolidLine)
qp.setPen(pen)
qp.setBrush(QtCore.Qt.NoBrush)
qp.drawRect(0, 0, w-1, h-1)
j = 0
for i in range(step, 10*step, step):
qp.drawLine(i, 0, i, 5)
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
j = j + 1
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
sld.setFocusPolicy(QtCore.Qt.NoFocus)
sld.setRange(1, 750)
sld.setValue(75)
sld.setGeometry(30, 40, 150, 30)
self.c = Communicate()
self.wid = BurningWidget()
self.c.updateBW[int].connect(self.wid.setValue)
sld.valueChanged[int].connect(self.changeValue)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.wid)
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.setGeometry(300, 300, 390, 210)
self.setWindowTitle('Burning widget')
self.show()
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
在我们的示例中,我们有一个QtGui.QSlider
和一个自定义小部件。 滑块控制自定义窗口小部件。 此小部件以图形方式显示了介质的总容量和可供我们使用的可用空间。 自定义窗口小部件的最小值是 1,最大值是 750。如果值达到 700,我们将开始绘制红色。 这通常表示过度燃烧。
刻录小部件位于窗口的底部。 这可以通过使用一个QtGui.QHBoxLayout
和一个QtGui.QVBoxLayout
来实现
class BurningWidget(QtGui.QWidget):
def __init__(self):
super(BurningWidget, self).__init__()
它基于QtGui.QWidget
小部件的刻录小部件。
self.setMinimumSize(1, 30)
我们更改小部件的最小大小(高度)。 默认值对我们来说有点小。
font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
qp.setFont(font)
我们使用的字体比默认字体小。 那更适合我们的需求。
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
我们动态绘制小部件。 窗口越大,刻录小部件越大。 反之亦然。 这就是为什么我们必须计算在其上绘制自定义窗口小部件的窗口小部件的大小的原因。 直到参数确定要绘制的总大小。 该值来自滑块小部件。 它占整个面积的一部分。 full
参数确定了我们开始绘制红色的点。 注意使用浮点算法。 这是为了达到更高的精度。
实际图纸包括三个步骤。 我们绘制黄色或红色和黄色矩形。 然后,我们绘制垂直线,这些垂直线将小部件分为几个部分。 最后,我们画出数字来表示介质的容量。
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
我们使用字体指标来绘制文本。 我们必须知道文本的宽度,以便使其围绕垂直线居中。
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
当我们移动滑块时,将调用changeValue()
方法。 在方法内部,我们发送带有参数的自定义updateBW
信号。 该参数是滑块的当前值。 该值随后用于计算刻录小部件的容量。 然后将自定义窗口小部件重新粉刷。
图:刻录小部件
在 PySide 教程的这一部分中,我们创建了一个自定义小部件。
PySide 中的俄罗斯方块游戏
制作计算机游戏具有挑战性。 程序员迟早会希望有一天创建一个计算机游戏。 实际上,许多人对编程很感兴趣,因为他们玩游戏并想创建自己的游戏。 制作计算机游戏将有助于提高您的编程技能。
俄罗斯方块
俄罗斯方块游戏是有史以来最受欢迎的计算机游戏之一。 原始游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。此后,几乎所有版本的几乎所有计算机平台上都可以使用俄罗斯方块。 甚至我的手机都有俄罗斯方块游戏的修改版。
俄罗斯方块被称为下降块益智游戏。 在这个游戏中,我们有七个不同的形状,称为tetrominoes
。 S 形,Z 形,T 形,L 形,线形,镜像 L 形和正方形。 这些形状中的每一个都形成有四个正方形。 形状从板上掉下来。 俄罗斯方块游戏的目的是移动和旋转形状,以便它们尽可能地适合。 如果我们设法形成一行,则该行将被破坏并得分。 我们玩俄罗斯方块游戏,直到达到顶峰。
图:Tetrominoes
PySide 是旨在创建应用的工具包。 还有其他一些旨在创建计算机游戏的库。 但是,可以使用 PySide 和其他应用工具包来创建游戏。
开发
我们的俄罗斯方块游戏没有图像,我们使用 PySide 编程工具包中提供的绘图 API 绘制四面体。 每个计算机游戏的背后都有一个数学模型。 俄罗斯方块也是如此。
游戏背后的一些想法:
- 我们使用
QtCore.QBasicTimer()
创建游戏周期。 - 绘制四方块。
- 形状以正方形为单位移动(而不是逐个像素移动)。
- 从数学上讲,棋盘是一个简单的数字列表。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PySide tutorial
This is a simple Tetris clone
in PySide.
author: Jan Bodnar
website: zetcode.com
last edited: August 2011
"""
import sys, random
from PySide import QtCore, QtGui
class Communicate(QtCore.QObject):
msgToSB = QtCore.Signal(str)
class Tetris(QtGui.QMainWindow):
def __init__(self):
super(Tetris, self).__init__()
self.setGeometry(300, 300, 180, 380)
self.setWindowTitle('Tetris')
self.Tetrisboard = Board(self)
self.setCentralWidget(self.Tetrisboard)
self.statusbar = self.statusBar()
self.Tetrisboard.c.msgToSB[str].connect(self.statusbar.showMessage)
self.Tetrisboard.start()
self.center()
def center(self):
screen = QtGui.QDesktopWidget().screenGeometry()
size = self.geometry()
self.move((screen.width()-size.width())/2,
(screen.height()-size.height())/2)
class Board(QtGui.QFrame):
BoardWidth = 10
BoardHeight = 22
Speed = 300
def __init__(self, parent):
super(Board, self).__init__()
self.timer = QtCore.QBasicTimer()
self.isWaitingAfterLine = False
self.curPiece = Shape()
self.nextPiece = Shape()
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.isStarted = False
self.isPaused = False
self.clearBoard()
self.c = Communicate()
self.nextPiece.setRandomShape()
def shapeAt(self, x, y):
return self.board[(y * Board.BoardWidth) + x]
def setShapeAt(self, x, y, shape):
self.board[(y * Board.BoardWidth) + x] = shape
def squareWidth(self):
return self.contentsRect().width() / Board.BoardWidth
def squareHeight(self):
return self.contentsRect().height() / Board.BoardHeight
def start(self):
if self.isPaused:
return
self.isStarted = True
self.isWaitingAfterLine = False
self.numLinesRemoved = 0
self.clearBoard()
self.c.msgToSB.emit(str(self.numLinesRemoved))
self.newPiece()
self.timer.start(Board.Speed, self)
def pause(self):
if not self.isStarted:
return
self.isPaused = not self.isPaused
if self.isPaused:
self.timer.stop()
self.c.msgToSB.emit("paused")
else:
self.timer.start(Board.Speed, self)
self.c.msgToSB.emit(str(self.numLinesRemoved))
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
for i in range(Board.BoardHeight):
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoes.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
if self.curPiece.shape() != Tetrominoes.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
def keyPressEvent(self, event):
if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape:
QtGui.QWidget.keyPressEvent(self, event)
return
key = event.key()
if key == QtCore.Qt.Key_P:
self.pause()
return
if self.isPaused:
return
elif key == QtCore.Qt.Key_Left:
self.tryMove(self.curPiece, self.curX - 1, self.curY)
elif key == QtCore.Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
elif key == QtCore.Qt.Key_Down:
self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY)
elif key == QtCore.Qt.Key_Up:
self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY)
elif key == QtCore.Qt.Key_Space:
self.dropDown()
elif key == QtCore.Qt.Key_D:
self.oneLineDown()
else:
QtGui.QWidget.keyPressEvent(self, event)
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
QtGui.QFrame.timerEvent(self, event)
def clearBoard(self):
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoes.NoShape)
def dropDown(self):
newY = self.curY
while newY > 0:
if not self.tryMove(self.curPiece, self.curX, newY - 1):
break
newY -= 1
self.pieceDropped()
def oneLineDown(self):
if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
self.pieceDropped()
def pieceDropped(self):
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.setShapeAt(x, y, self.curPiece.shape())
self.removeFullLines()
if not self.isWaitingAfterLine:
self.newPiece()
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoes.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
numFullLines = numFullLines + len(rowsToRemove)
if numFullLines > 0:
self.numLinesRemoved = self.numLinesRemoved + numFullLines
print self.numLinesRemoved
self.c.msgToSB.emit(str(self.numLinesRemoved))
self.isWaitingAfterLine = True
self.curPiece.setShape(Tetrominoes.NoShape)
self.update()
def newPiece(self):
self.curPiece = self.nextPiece
self.nextPiece.setRandomShape()
self.curX = Board.BoardWidth / 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoes.NoShape)
self.timer.stop()
self.isStarted = False
self.c.msgToSB.emit("Game over")
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoes.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
def drawSquare(self, painter, x, y, shape):
colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
color = QtGui.QColor(colorTable[shape])
painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
self.squareHeight() - 2, color)
painter.setPen(color.lighter())
painter.drawLine(x, y + self.squareHeight() - 1, x, y)
painter.drawLine(x, y, x + self.squareWidth() - 1, y)
painter.setPen(color.darker())
painter.drawLine(x + 1, y + self.squareHeight() - 1,
x + self.squareWidth() - 1, y + self.squareHeight() - 1)
painter.drawLine(x + self.squareWidth() - 1,
y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
class Tetrominoes(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
class Shape(object):
coordsTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
((0, -1), (0, 0), (1, 0), (1, 1)),
((0, -1), (0, 0), (0, 1), (0, 2)),
((-1, 0), (0, 0), (1, 0), (0, 1)),
((0, 0), (1, 0), (0, 1), (1, 1)),
((-1, -1), (0, -1), (0, 0), (0, 1)),
((1, -1), (0, -1), (0, 0), (0, 1))
)
def __init__(self):
self.coords = [[0,0] for i in range(4)]
self.pieceShape = Tetrominoes.NoShape
self.setShape(Tetrominoes.NoShape)
def shape(self):
return self.pieceShape
def setShape(self, shape):
table = Shape.coordsTable[shape]
for i in range(4):
for j in range(2):
self.coords[i][j] = table[i][j]
self.pieceShape = shape
def setRandomShape(self):
self.setShape(random.randint(1, 7))
def x(self, index):
return self.coords[index][0]
def y(self, index):
return self.coords[index][1]
def setX(self, index, x):
self.coords[index][0] = x
def setY(self, index, y):
self.coords[index][1] = y
def minX(self):
m = self.coords[0][0]
for i in range(4):
m = min(m, self.coords[i][0])
return m
def maxX(self):
m = self.coords[0][0]
for i in range(4):
m = max(m, self.coords[i][0])
return m
def minY(self):
m = self.coords[0][1]
for i in range(4):
m = min(m, self.coords[i][1])
return m
def maxY(self):
m = self.coords[0][1]
for i in range(4):
m = max(m, self.coords[i][1])
return m
def rotatedLeft(self):
if self.pieceShape == Tetrominoes.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
def rotatedRight(self):
if self.pieceShape == Tetrominoes.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, -self.y(i))
result.setY(i, self.x(i))
return result
def main():
app = QtGui.QApplication(sys.argv)
t = Tetris()
t.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我对游戏做了一些简化,以便于理解。 游戏启动后立即开始。 我们可以通过按 p
键暂停游戏。 空格键将把俄罗斯方块放在底部。 游戏以恒定速度进行,没有实现加速。 分数是我们已删除的行数。
self.statusbar = self.statusBar()
self.Tetrisboard.c.msgToSB[str].connect(self.statusbar.showMessage)
我们创建一个状态栏,在其中显示消息。 我们将显示三种可能的消息。 已删除的行数。 暂停的消息和游戏结束消息。
...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...
在开始游戏周期之前,我们先初始化一些重要的变量。 self.board
变量是一个从 0 到 7 的数字的列表。它表示各种形状的位置以及板上形状的其余部分。
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoes.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
游戏的绘图分为两个步骤。 在第一步中,我们绘制所有形状或已放置到板底部的形状的其余部分。 所有正方形都记在self.board
列表变量中。 我们使用shapeAt()
方法访问它。
if self.curPiece.shape() != Tetrominoes.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
下一步是绘制掉落的实际零件。
elif key == QtCore.Qt.Key_Left:
self.tryMove(self.curPiece, self.curX - 1, self.curY)
elif key == QtCore.Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
在keyPressEvent
中,我们检查是否有按下的键。 如果按向右箭头键,我们将尝试将棋子向右移动。 我们说尝试,因为这片可能无法移动。
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoes.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
在tryMove()
方法中,我们尝试移动形状。 如果形状在板的边缘或与其他零件相邻,则返回false
。 否则,我们将当前的下降片放到新位置。
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
QtGui.QFrame.timerEvent(self, event)
在计时器事件中,我们可以在上一个下降到底部之后创建一个新的片断,或者将下降的片断向下移动一行。
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoes.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
...
如果片段触底,我们将调用removeFullLines()
方法。 首先,我们找出所有实线。 然后我们将其删除。 通过将所有行移动到当前全行上方来将其向下移动一行来实现。 注意,我们颠倒了要删除的行的顺序。 否则,它将无法正常工作。 在我们的情况下,我们使用天真重力。 这意味着碎片可能会漂浮在空的间隙上方。
def newPiece(self):
self.curPiece = self.nextPiece
self.nextPiece.setRandomShape()
self.curX = Board.BoardWidth / 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoes.NoShape)
self.timer.stop()
self.isStarted = False
self.c.msgToSB.emit("Game over")
newPiece()
方法随机创建一个新的俄罗斯方块。 如果棋子无法进入其初始位置,则游戏结束。
Shape
类保存有关俄罗斯方块的信息。
self.coords = [[0,0] for i in range(4)]
创建后,我们将创建一个空坐标列表。 该列表将保存俄罗斯方块的坐标。 例如,这些元组(0,-1),(0,0),(1,0),(1,1)表示旋转的 S 形。 下图说明了形状。
图:坐标
当绘制当前下降片时,将其绘制在self.curX
,self.curY position
处。 然后,我们查看坐标表并绘制所有四个正方形。
图:俄罗斯方块
这是 PySide 中的俄罗斯方块游戏。
Tcl/Tk 教程
这是 Tcl/Tk 教程。 在本教程中,您将学习使用 Tcl 和 Tk 进行编程的基础知识。 本教程适合初学者和中级程序员。
目录
Tk
Tk 是一个开源的,跨平台的小部件工具包,它提供了用于构建图形用户界面(GUI)的基本元素库。 Tk 的第一个公共发行版于 1991 年。具有其他几种语言的绑定,包括 Ada,Perl,Ruby,Python 或 Common Lisp。 Tk 库通常以其主要语言称为 Tcl/Tk。
相关教程
ZetCode 提供了您可能感兴趣的其他两个相关教程: Tcl 教程和 Tkinter 教程。
Tcl/Tk 简介
在 Tcl/Tk 教程的这一部分中,我们将介绍 Tk 工具包并创建我们的第一个程序。
本教程的目的是帮助您开始使用 Tcl 语言的 Tk 工具包。 可以在此处下载本教程中使用的图像。 我使用了 Gnome 项目的 Tango 图标包中的一些图标。
Tk
Tk 是一个开源的,跨平台的小部件工具包,它提供了用于构建图形用户界面(GUI)的基本元素库。 Tk 于 1991 年首次公开发行。Tk 是 Tcl 语言的扩展。 这意味着 Tk 通过用于构建用户界面的其他命令扩展了 Tcl 语言。 有几种其他语言的绑定,包括 Ada,Perl,Ruby,Python 或 Common Lisp。 Tk 库通常以其主要语言称为 Tcl/Tk。
Tcl
Tcl 是基于字符串的脚本语言。 源代码被编译成字节码,然后由 Tcl 解释器解释。 它由 John Osterhout 于 1988 年创建。其目的是创建一种易于嵌入到应用中的语言。 但通常在其原始区域之外使用。 该语言通常用于快速原型,脚本应用,GUI 或测试。 Tcl 代表工具命令语言,其中 Tcl 脚本的源代码由命令组成。
Tcl 是一种过程语言。 它具有一些函数式特征。 计划在下一个正式版本中提供 OOP 支持。
Tcl 和 Tk 的官方网站是 tcl.tk
简单的例子
在第一个示例中,我们将在屏幕上显示一个基本窗口。
#!/usr/bin/wish
frame .fr
wm title . Simple
wm geometry . 250x150+300+300
尽管这段代码很小,但是应用窗口可以做很多事情。 可以调整大小,最大化或最小化。 随之而来的所有复杂性对应用员都是隐藏的。
#!/usr/bin/wish
wish
是 Tcl/Tk 解释器。 它了解 Tcl 和 Tk 命令。
frame .fr
frame
小部件已创建。 框架是创建框架小部件的 Tk 命令。 该命令的参数是小部件路径名。 小部件路径名以点字符开头。 此字符代表主应用窗口。 在 Tk 中,小部件形成一个层次结构。 .fr
表示框架小部件位于主应用窗口内。 窗口小部件路径是一个以点开头的字符串,由多个以点分隔的名称组成。 这些名称是组成窗口小部件层次的窗口小部件名称。
wm title . Simple
wm
命令用于与窗口管理器进行交互。 此代码行设置窗口标题。
wm geometry . 250x150+300+300
在这里,我们设置窗口的大小并将其放置在屏幕上。 前两个数字指定窗口的宽度和高度。 第三个和第四个参数是监视器屏幕上的 x,y 坐标。
图:简单 window
居中窗口
该脚本使屏幕上的窗口居中。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we center a window
# on the screen.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
set width 250
set height 150
set x [expr { ( [winfo vrootwidth .] - $width ) / 2 }]
set y [expr { ( [winfo vrootheight .] - $height ) / 2 }]
wm title . "Center"
wm geometry . ${width}x${height}+${x}+${y}
我们需要有窗口的大小和屏幕的大小,才能将窗口放置在监视器屏幕的中央。
set width 250
set height 150
这些是应用窗口的宽度和高度值。
set x [expr { ( [winfo vrootwidth .] - $width ) / 2 }]
set y [expr { ( [winfo vrootheight .] - $height ) / 2 }]
给定其宽度和高度,我们确定居中窗口的x
和y
坐标。
wm geometry . ${width}x${height}+${x}+${y}
窗口被放置在屏幕上。
退出按钮
在本节的最后一个示例中,我们将创建一个退出按钮。 当我们按下此按钮时,应用终止。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# This program creates a quit
# button. When we press the button,
# the application terminates.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
button .hello -text "Quit" -command { exit }
place .hello -x 50 -y 50
wm title . "Quit button"
wm geometry . 250x150+300+300
我们在窗口上放置一个button
。 单击该按钮将终止该应用。
button .hello -text "Quit" -command { exit }
button
小部件已创建。 按钮的标签随-text
选项一起提供。 -command
选项指定按下按钮时要执行的过程。 在我们的情况下,应用使用内置的exit
命令终止。
place .hello -x 50 -y 50
我们使用place
几何图形管理器将按钮定位在绝对坐标中。 从根窗口的左上角起50x50px
。
图:退出按钮
参考
wikipedia.org 和 tcl.tk 用于创建本教程。
本节是对 Tcl/Tk 的介绍。
Tcl/Tk 中的布局管理
在 Tcl/Tk 编程教程的这一部分中,我们将介绍布局管理器。
在设计应用的 GUI 时,我们决定要使用哪些小部件以及如何在应用中组织这些小部件。 为了组织小部件,我们使用专门的不可见对象,称为布局管理器。
有两种小部件:容器及其子级。 容器将子项分组为合适的布局。
Tk 具有三个内置布局管理器:pack
,grid
和place
管理器。 包几何形状管理器在垂直和水平框中组织小部件。 网格几何管理器将小部件放置在二维网格中。 最后,place
几何管理器使用绝对定位将小部件放置在其容器上。
绝对定位
在大多数情况下,程序员应使用布局管理器。 在某些情况下,我们可以使用绝对定位。 在绝对定位中,程序员以像素为单位指定每个小部件的位置和大小。 如果我们调整窗口大小,则小部件的大小和位置不会改变。 在各种平台上,应用看起来都不同,在 Linux 上看起来不错,在 Mac OS 上看起来不太正常。 在我们的应用中更改字体可能会破坏布局。 如果我们将应用翻译成另一种语言,则必须重做您的布局。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we lay out images
# using absolute positioning.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
package require Img
frame .fr -background "#333"
pack .fr -fill both -expand 1
image create photo img1 -file "bardejov.jpg"
label .fr.lbl1 -image img1
place .fr.lbl1 -x 20 -y 20
image create photo img2 -file "rotunda.jpg"
label .fr.lbl2 -image img2
place .fr.lbl2 -x 40 -y 160
image create photo img3 -file "mincol.jpg"
label .fr.lbl3 -image img3
place .fr.lbl3 -x 170 -y 50
wm title . "absolute"
wm geometry . 300x280+300+300
在此示例中,我们使用绝对定位放置了三个图像。 我们将使用位置几何图形管理器。
package require Img
我们使用Img
包来显示 JPG 图像。 在 Ubuntu Linux 上,我们必须安装libtk-img
包。
package require Img
要显示 JPG 图像,我们使用Img
包。
frame .fr -background "#333"
我们的框架将具有深灰色背景。
image create photo img1 -file "bardejov.jpg"
我们从当前工作目录中的图像创建照片图像对象。
label .fr.lbl1 -image img1
我们用图像创建一个label
。 标签可以包含文本或图像。
place .fr.lbl1 -x 20 -y 20
标签放置在框架上的x = 20
,y = 20
坐标处。 绝对定位通过place
命令完成。
图:绝对定位
按钮示例
在下面的示例中,我们将在窗口的右下角放置两个按钮。 我们将使用pack
管理器。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we use pack manager
# to position two buttons in the
# bottom right corner of the window.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr
pack .fr -fill both -expand 1
frame .fr.pnl -relief raised -borderwidth 1
pack .fr.pnl -fill both -expand 1
ttk::button .fr.cb -text "Close"
pack .fr.cb -padx 5 -pady 5 -side right
ttk::button .fr.ok -text "OK"
pack .fr.ok -side right
wm title . "buttons"
wm geometry . 300x200+300+300
我们将有两个框架。 有一个基础框架和一个附加框架,该框架将在两个方向上扩展并将两个按钮按到基础框架的底部。 这些按钮放置在水平框中,并位于此框的右侧。
frame .fr.pnl -relief raised -borderwidth 1
pack .fr.pnl -fill both -expand 1
我们创建另一个frame
小部件。 该小部件占用了大部分区域。 我们更改框架的边框,以使框架可见。 默认情况下,它是平坦的。 pack
管理器在两个方向上扩展框架。 水平和垂直。
ttk::button .fr.cb -text "Close"
pack .fr.cb -padx 5 -pady 5 -side right
创建一个关闭按钮。 将其放入水平盒中。 -side
选项将创建一个水平框布局,其中按钮位于框的右侧。 -padx
和-pady
选项将在小部件之间放置一些空间。 -padx
在按钮小部件之间以及关闭按钮和根窗口的右边框之间放置一些空间。 -pady
在按钮小部件与框架和根窗口的边框之间放置一些空间。
pack .fr.ok -side right
ok
按钮位于关闭按钮旁边。 它们之间有 5px 的间距。
图:按钮示例
计算器
我们将使用 Tk 网格几何管理器来创建计算器的骨架。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we use the grid manager
# to create a skeleton of a calculator.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr -padx 5 -pady 5
pack .fr -fill both -expand 1
ttk::style configure TButton -width 8 -height 8 -font "serif 10"
entry .fr.ent
grid .fr.ent -row 0 -columnspan 4 -sticky we
ttk::button .fr.cls -text "Cls"
grid .fr.cls -row 1 -column 0
ttk::button .fr.bck -text "Back"
grid .fr.bck -row 1 -column 1
ttk::button .fr.lbl
grid .fr.lbl -row 1 -column 2
ttk::button .fr.clo -text "Close"
grid .fr.clo -row 1 -column 3
ttk::button .fr.sev -text "7"
grid .fr.sev -row 2 -column 0
ttk::button .fr.eig -text "8"
grid .fr.eig -row 2 -column 1
ttk::button .fr.nin -text "9"
grid .fr.nin -row 2 -column 2
ttk::button .fr.div -text "/"
grid .fr.div -row 2 -column 3
ttk::button .fr.fou -text "4"
grid .fr.fou -row 3 -column 0
ttk::button .fr.fiv -text "5"
grid .fr.fiv -row 3 -column 1
ttk::button .fr.six -text "6"
grid .fr.six -row 3 -column 2
ttk::button .fr.mul -text "*"
grid .fr.mul -row 3 -column 3
ttk::button .fr.one -text "1"
grid .fr.one -row 4 -column 0
ttk::button .fr.two -text "2"
grid .fr.two -row 4 -column 1
ttk::button .fr.thr -text "3"
grid .fr.thr -row 4 -column 2
ttk::button .fr.mns -text "-"
grid .fr.mns -row 4 -column 3
ttk::button .fr.zer -text "0"
grid .fr.zer -row 5 -column 0
ttk::button .fr.dot -text "."
grid .fr.dot -row 5 -column 1
ttk::button .fr.equ -text "="
grid .fr.equ -row 5 -column 2
ttk::button .fr.pls -text "+"
grid .fr.pls -row 5 -column 3
grid columnconfigure .fr 0 -pad 3
grid columnconfigure .fr 1 -pad 3
grid columnconfigure .fr 2 -pad 3
grid columnconfigure .fr 3 -pad 3
grid rowconfigure .fr 0 -pad 3
grid rowconfigure .fr 1 -pad 3
grid rowconfigure .fr 2 -pad 3
grid rowconfigure .fr 3 -pad 3
grid rowconfigure .fr 4 -pad 3
wm title . "calculator"
wm geometry . +300+300
在此示例中,我们使用grid
管理器来组织框架容器小部件中的按钮。
ttk::style configure TButton -width 8 -height 8 -font "serif 10"
我们将主题button
小部件配置为具有特定字体并具有一些内部填充。
entry .fr.ent
grid .fr.ent -row 0 -columnspan 4 -sticky we
entry
小部件是显示数字的地方。 小部件位于第一行,它将覆盖所有四列。 小部件可能不会占用网格中单元所分配的所有空间。 -sticky
选项将沿给定方向扩展小部件。 在我们的案例中,我们确保条目小部件从左向右展开。
ttk::button .fr.cls -text "Cls"
cls
按钮位于第二行和第一列。 请注意,行和列从零开始。 ttk::button
是一个主题按钮。
grid columnconfigure .fr 0 -pad 3
...
grid rowconfigure .fr 0 -pad 3
我们使用columnconfigure
和rowconfigure
命令在网格列和行中定义一些空间。 这样,我们可以实现按钮之间有一定的间隔。
图:计算器
Windows 示例
以下示例使用网格几何管理器创建 Windows 对话框。 该对话框来自 JDeveloper 应用。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we use the grid
# manager to create a more complicated
# layout.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr -padx 5 -pady 5
pack .fr -fill both -expand 1
label .fr.lbl -text Windows
grid .fr.lbl -sticky w -pady 4 -padx 5
text .fr.area
grid .fr.area -row 1 -column 0 -columnspan 2 \
-rowspan 4 -padx 5 -sticky ewsn
ttk::button .fr.act -text Activate
grid .fr.act -row 1 -column 3
ttk::button .fr.cls -text Close
grid .fr.cls -row 2 -column 3 -pady 4
ttk::button .fr.hlp -text Help
grid .fr.hlp -row 5 -column 0 -padx 5
ttk::button .fr.ok -text OK
grid .fr.ok -row 5 -column 3
grid columnconfigure .fr 1 -weight 1
grid columnconfigure .fr 3 -pad 7
grid rowconfigure .fr 3 -weight 1
grid rowconfigure .fr 5 -pad 7
wm title . "Windows"
wm geometry . 350x300+300+300
在此示例中,我们将使用label
小部件,text
小部件和四个按钮。
label .fr.lbl -text Windows
grid .fr.lbl -sticky w -pady 4 -padx 5
标签窗口小部件已创建并放入网格中。 如果未指定列和行,则假定为第一列或行。 该标签向西粘贴,并且其文本周围有一些填充。
text .fr.area
grid .fr.area -row 1 -column 0 -columnspan 2 \
-rowspan 4 -padx 5 -sticky ewsn
text
小部件已创建,并从第二行第一列开始。 它跨越2
列和4
行。 小部件和根窗口的左边界之间有 4px 空间。 最后,它坚持所有四个方面。 因此,调整窗口大小时,text
小部件会向各个方向扩展。
grid columnconfigure .fr 1 -weight 1
grid columnconfigure .fr 3 -pad 7
grid rowconfigure .fr 3 -weight 1
grid rowconfigure .fr 5 -pad 7
我们在网格中的小部件之间定义一些空间。 在text
小部件和按钮之间放置最大的空间。
图:窗口示例
在 Tcl/Tk 教程的这一部分中,我们提到了小部件的布局管理。
Tcl/Tk 小部件
在 Tcl/Tk 教程的这一部分中,我们将介绍一些基本的 Tk 小部件。 我们将为checkbutton
,label
,scale
和listbox
小部件提供示例。
小部件是 GUI 应用的基本构建块。 多年来,几个小部件已成为所有 OS 平台上所有工具包中的标准。 例如,按钮,复选框或滚动条。 其中一些名称可能不同。 例如,一个复选框在 Tk 中称为复选框。 Tk 具有一小组可满足基本编程需求的小部件。 可以将更多专门的窗口小部件创建为自定义窗口小部件。
checkbutton
checkbutton
是具有两种状态的窗口小部件:开和关。 接通状态通过复选标记显示。 它用来表示一些布尔属性。 checkbutton
小部件提供一个带有文本标签的复选框。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# This program toggles the title of the
# window with the checkbutton widget.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr
pack .fr -fill both -expand 1
checkbutton .fr.cb -text "Show title" -command onClick \
-onvalue true -offvalue false -variable selected
.fr.cb select
place .fr.cb -x 50 -y 50
proc onClick {} {
global selected
if {$selected==true} {
wm title . checkbutton
} else {
wm title . ""
}
}
wm title . checkbutton
wm geometry . 250x150+300+300
在我们的示例中,我们在窗口上放置了一个检查按钮。 选中按钮显示/隐藏窗口的标题。
checkbutton .fr.cb -text "Show title" -command onClick \
-onvalue true -offvalue false -variable selected
checkbutton
命令创建一个检查按钮小部件。 -text
选项指定由小部件显示的文本。 当我们单击小部件时,将执行onClick
过程。 选中checkbutton
时,所选变量的值为真。 否则,它具有错误的值。
.fr.cb select
最初,标题显示在标题栏中。 因此,一开始,我们使用select
命令检查了小部件。
place .fr.cb -x 50 -y 50
我们将检查按钮小部件放置在框架上的x = 50
和y = 50
坐标处。
if {$selected==true} {
wm title . checkbutton
} else {
wm title . ""
}
在onClick
过程中,我们根据所选变量显示或隐藏标题。
图:CheckButton
label
label
小部件用于显示文本或图像。 没有用户交互。
sudo apt-get install libtk-img
为了运行此示例,我们必须安装libtk-img
包。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we use a label
# widget to show an image.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
package require Img
frame .fr
pack .fr
image create photo img -file "tatras.jpg"
label .fr.lbl -image img
pack .fr.lbl
wm title . label
wm geometry . +300+300
我们的示例在窗口上显示图像。
package require Img
默认情况下,label
小部件只能显示一组有限的图像类型。 要显示 JPG 图像,我们必须使用Img
包。
image create photo img -file "tatras.jpg"
我们根据文件系统上的图像创建照片图像。
label .fr.lbl -image img
将照片图像提供给标签窗口小部件的image
参数。
pack .fr.lbl
图像包装在框架中。
wm geometry . +300+300
我们指定 x 和 y 坐标。 这些用于在屏幕上放置窗口。 我们忽略了窗口的大小。 打包管理器会将窗口大小设置为图像大小。
HScale
scale
是一个小部件,可让用户通过在有限间隔内滑动旋钮以图形方式选择一个值。 我们的示例将在标签小部件中显示一个选定的数字。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we show how to
# use the scale widget.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr
pack .fr -fill both -expand 1
scale .fr.scl -orient horizontal -from 0 -to 100 \
-length 150 -variable val -showvalue 0
place .fr.scl -x 50 -y 50
label .fr.lbl -textvariable val
place .fr.lbl -x 80 -y 110
wm title . scale
wm geometry . 250x150+300+300
上面的脚本中有两个小部件。 标尺和标签。 标签控件中显示了比例控件的值。
scale .fr.scl -orient horizontal -from 0 -to 100 \
-length 150 -variable val -showvalue 0
scale
小部件已创建。 -orient
选项使窗口小部件水平。 我们提供-from
和-to
选项的上限和下限。 当前选择的数字存储在 val 变量中。 默认情况下,比例小部件还显示选定的数字。 使用-showvalue
选项,我们将其隐藏。
label .fr.lbl -textvariable val
label
小部件已创建。 它将显示上述val
变量。
图:scale
小部件
listbox
listbox
是显示对象列表的窗口小部件。 它允许用户选择一项或多项。
#!/usr/bin/wish
# ZetCode Tcl/Tk tutorial
#
# In this script, we show how to
# use the listbox widget.
#
# author: Jan Bodnar
# last modified: March 2011
# website: www.zetcode.com
frame .fr
pack .fr -fill both -expand 1
listbox .fr.lb
.fr.lb insert end "Scarlett Johansson" "Rachel Weiss" "Natalie Portman" \
"Jessica Alba"
bind .fr.lb <<ListboxSelect>> { setLabel [%W curselection]}
place .fr.lb -x 20 -y 20
label .fr.lbl
place .fr.lbl -x 20 -y 210
wm title . listbox
wm geometry . 300x250+300+300
proc setLabel { idx } {
set val [.fr.lb get $idx]
.fr.lbl configure -text $val
}
在我们的示例中,我们显示了listbox
中的女演员列表。 当前选择的女演员显示在标签小部件中。
listbox .fr.lb
使用listbox
命令创建列表框小部件。
.fr.lb insert end "Scarlett Johansson" "Rachel Weiss" "Natalie Portman" \
"Jessica Alba"
在这里,我们将四个女演员插入小部件。
bind .fr.lb <<ListboxSelect>> { setLabel [%W curselection]}
当我们在列表框中选择一个项目时,将生成<<ListboxSelect>>
事件。 我们将setLabel
过程绑定到此事件。 我们还向过程发送参数。 当前所选值的索引。 %W curselection
返回索引。 %W
是有关小部件的处理器,curselection
是在此小部件上执行的命令。
proc setLabel { idx } {
set val [.fr.lb get $idx]
.fr.lbl configure -text $val
}
在setLabel
过程中,我们根据获得的索引找出值。 此值稍后显示在标签窗口小部件中。
图:listbox
小部件
在 Tcl/Tk 教程的这一部分中,我们介绍了几个 Tk 小部件。
Windows API 中的 GDI
图形设备接口(GDI)是用于处理图形的接口。 它用于与图形设备(例如监视器,打印机或文件)进行交互。 GDI 允许程序员在屏幕或打印机上显示数据,而不必担心特定设备的详细信息。 GDI 使程序员与硬件隔离。 从程序员的角度来看,GDI 是一组用于处理图形的 API 函数。 GDI 由 2D 向量图形,字体和图像组成。 要开始绘制图形,我们必须获得设备上下文(DC)对象。
每当需要重绘窗口时,都会生成WM_PAINT
消息。 程序员在窗口的客户区域画图。 操作系统会自动绘制包括标题栏在内的周围框架。
HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint);
BeginPaint()
函数为指定的绘图准备窗口,并用绘图信息填充PAINTSTRUCT
结构。 它返回设备上下文的句柄。 设备上下文是我们执行绘制操作所通过的对象。
BOOL EndPaint(HWND hWnd, const PAINTSTRUCT *lpPaint);
每个绘图操作都以EndPaint()
结束。 每次调用BeginPaint()
函数都需要此函数,但是仅在绘制完成之后才需要。
像素点
像素是可以在视频显示系统中单独处理的图像的最小元素。 SetPixel()
是在窗口上绘制单个像素的功能。
COLORREF SetPixel(HDC hdc, int x, int y, COLORREF crColor);
函数的第一个参数是设备上下文的句柄。 接下来的两个参数是该点的 x 和 y 坐标。 最后一个参数是用于绘制点的颜色。 如果函数成功,则返回值为函数将像素设置为的 RGB 值。
pixels.c
#include <windows.h>
#include <time.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawPixels(HWND hwnd);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Pixels";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Pixels",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 250, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
srand(time(NULL));
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
DrawPixels(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void DrawPixels(HWND hwnd) {
PAINTSTRUCT ps;
RECT r;
GetClientRect(hwnd, &r);
if (r.bottom == 0) {
return;
}
HDC hdc = BeginPaint(hwnd, &ps);
for (int i=0; i<1000; i++) {
int x = rand() % r.right;
int y = rand() % r.bottom;
SetPixel(hdc, x, y, RGB(255, 0, 0));
}
EndPaint(hwnd, &ps);
}
在我们的示例中,我们在窗口的客户区域随机显示 1000 个红色像素。
wc.style = CS_HREDRAW | CS_VREDRAW;
这两个标志会导致在调整窗口大小时重新绘制窗口。
srand(time(NULL));
srand()
函数为随机数生成器提供种子。
case WM_PAINT:
DrawPixels(hwnd);
break;
绘制是对WM_PAINT
消息的反应。 实际图形委托给DrawPixels()
函数。
HDC hdc = BeginPaint(hwnd, &ps);
BeginPaint()
函数准备指定的窗口进行绘图。 它用有关绘图的信息填充PAINTSTRUCT
结构。 它为指定窗口返回显示设备上下文的句柄。
GetClientRect(hwnd, &r);
我们检索窗口客户区的坐标。 我们随机在窗口上绘制,我们需要知道当前可以在哪里绘制。
for (int i=0; i<1000; i++) {
int x = rand() % r.right;
int y = rand() % r.bottom;
SetPixel(hdc, x, y, RGB(255, 0, 0));
}
在窗口上随机绘制一千个点。 SetPixel()
函数使用所选颜色在指定位置绘制像素。
EndPaint(hwnd, &ps);
在绘图的结尾,我们调用EndPaint()
函数。 该函数释放BeginPaint()
检索到的显示设备上下文。
图:像素
直线
线是基本的图形基元。 它具有两个函数:MoveToEx()
和LineTo()
。
BOOL MoveToEx(HDC hdc, int x, int y, LPPOINT lpPoint);
MoveToEx()
函数将当前位置更新到指定点,并有选择地返回先前位置。
BOOL LineTo(HDC hdc, int nXEnd, int nYEnd);
LineTo()
函数从当前位置开始绘制一条线,但不包括指定点。
lines.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Lines";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Lines",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 200, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 50, 50, NULL);
LineTo(hdc, 250, 50);
HPEN hWhitePen = GetStockObject(WHITE_PEN);
HPEN hOldPen = SelectObject(hdc, hWhitePen);
MoveToEx(hdc, 50, 100, NULL);
LineTo(hdc, 250, 100);
SelectObject(hdc, hOldPen);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
这个例子画了两条线。 一种是黑色,另一种是白色。
MoveToEx(hdc, 50, 50, NULL);
LineTo(hdc, 250, 50);
在点(50,50)和(250,50)之间绘制了一条线。 使用默认的BLACK_PEN
。
HPEN hWhitePen = GetStockObject(WHITE_PEN);
GetStockObject()
函数检索用WHITE_PEN
值指定的内置白笔的句柄。 通过调用DeleteObject()
不必删除库存对象(但这不是有害的)。
HPEN hOldPen = SelectObject(hdc, hWhitePen);
SelectObject()
函数将一个对象选择到指定的设备上下文(DC)中。 新对象将替换相同类型的先前对象。
SelectObject(hdc, hOldPen);
我们恢复到旧的BLACK_PEN
笔。
图:直线
长方形
要绘制矩形,我们使用Rectangle()
函数。
BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect,
int nBottomRect);
函数的第一个参数是设备上下文的句柄。 接下来的两个参数是矩形左上角的 x 和 y 坐标。 最后两个参数是矩形右下角的 x,y 坐标。 如果函数失败,则返回值为零。 如果成功,则返回值为非零。
rectangle.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Rectangle";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Rectangle",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 250, 200, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, 50, 50, 200, 100);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
使用当前的笔绘制矩形的轮廓。 使用当前画笔绘制背景。
Rectangle(hdc, 50, 50, 200, 100);
使用Rectangle()
函数绘制矩形。 我们使用两个点绘制矩形:左上角点和右下角点。
图:矩形
贝塞尔曲线
贝塞尔曲线是由数学公式定义的曲线。 绘制曲线的数学方法由 PierreBézier 在 1960 年代后期创建,用于雷诺的汽车制造。
BOOL PolyBezier(HDC hdc, const POINT *lppt, DWORD cPoints);
函数的第一个参数是设备上下文的句柄。 第二个参数是指向POINT
结构数组的指针,该数组包含曲线的端点和控制点。
beziercurve.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"BezierCurve";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Beziér curve",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 500, 200, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
POINT points[4] = { 20, 40, 320, 200, 330, 110, 450, 40 };
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
PolyBezier(hdc, points, 4);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
在示例中,我们使用PolyBezier()
函数绘制一条曲线。
POINT points[4] = { 20, 40, 320, 200, 330, 110, 450, 40 };
这些点形成贝塞尔曲线。 第一点是起点。 接下来的两个点是控制点。 最后一点是曲线的终点。
PolyBezier(hdc, points, 4);
PolyBezier()
函数绘制曲线。
图:贝塞尔曲线
钢笔
笔是基本图形对象。 它用于绘制矩形,椭圆形,多边形或其他形状的线,曲线和轮廓。
笔有两种类型:化妆笔和几何笔。化妆笔是固定宽度为 1 的简单笔。它们具有三个属性:宽度,样式和颜色。 它们比几何笔更有效。 可以使用CreatePen()
,CreatePenIndirect()
或ExtCreatePen()
函数创建化妆笔。
几何笔比化妆笔复杂。 它们具有七个属性:宽度,样式,颜色,图案,剖面线,端盖和连接样式。 几何笔是使用ExtCreatePen()
函数创建的。
HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);
CreatePen()
函数创建具有指定样式,宽度和颜色的逻辑笔。
HPEN ExtCreatePen(DWORD dwPenStyle, DWORD dwWidth, const LOGBRUSH *lplb,
DWORD dwStyleCount, const DWORD *lpStyle);
ExtCreatePen()
函数创建逻辑的化妆笔或几何笔。 第一个参数是类型,样式,端盖和联接属性的组合。 第二个参数是笔的宽度。 第三个参数是指向LOGBRUSH
结构的指针。 该结构定义了物理笔刷的样式,颜色和图案。 第四个参数是lpStyle
数组的长度,以DWORD
单位。 如果dwPenStyle
不是PS_USERSTYLE
,则此值必须为零。 样式计数限制为 16。最后一个参数是指向数组的指针。 第一个值以用户定义的样式指定第一个笔划线的长度,第二个值指定第一个空格的长度,依此类推。 如果dwPenStyle
不是S_USERSTYLE
,则此指针必须为NULL
。
创建笔后,我们使用SelectObject()
函数将其选择到应用的设备上下文中。 从现在开始,应用使用此笔在其客户区中进行任何画线操作。
笔样式
笔样式是应用于线对象的特定图案。 有预定义的笔样式,例如PS_SOLID
,PS_DASH
,PS_DOT
或PS_DASHDOT
。 也可以创建自定义笔样式。
penstyles.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawLines(HWND);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Pen styles";
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Pen styles",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 350, 180, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
DrawLines(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void DrawLines(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HPEN hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
HPEN hPen2 = CreatePen(PS_DASH, 1, RGB(0, 0, 0));
HPEN hPen3 = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
HPEN hPen4 = CreatePen(PS_DASHDOT, 1, RGB(0, 0, 0));
HPEN hPen5 = CreatePen(PS_DASHDOTDOT, 1, RGB(0, 0, 0));
HPEN holdPen = SelectObject(hdc, hPen1);
MoveToEx(hdc, 50, 30, NULL);
LineTo(hdc, 300, 30);
SelectObject(hdc, hPen2);
MoveToEx(hdc, 50, 50, NULL);
LineTo(hdc, 300, 50);
SelectObject(hdc, hPen2);
MoveToEx(hdc, 50, 70, NULL);
LineTo(hdc, 300, 70);
SelectObject(hdc, hPen3);
MoveToEx(hdc, 50, 90, NULL);
LineTo(hdc, 300, 90);
SelectObject(hdc, hPen4);
MoveToEx(hdc, 50, 110, NULL);
LineTo(hdc, 300, 110);
SelectObject(hdc, holdPen);
DeleteObject(hPen1);
DeleteObject(hPen2);
DeleteObject(hPen3);
DeleteObject(hPen4);
DeleteObject(hPen5);
EndPaint(hwnd, &ps);
}
在我们的示例中,我们使用五种不同的笔样式绘制五根不同的线。
case WM_PAINT:
DrawLines(hwnd);
break;
实际图形委托给DrawLines()
函数。
HPEN hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
CreatePen()
函数创建具有指定样式,宽度和颜色的逻辑笔。 PS_SOLID
代表实心笔。 我们使用RGB
宏为笔生成颜色。
SelectObject(hdc, hPen1);
要激活笔,我们调用SelectObject()
函数。
MoveToEx(hdc, 50, 30, NULL);
LineTo(hdc, 300, 30);
要绘制线条,我们使用MoveToEx()
和LineTo()
函数。
DeleteObject(hPen1);
DeleteObject(hPen2);
DeleteObject(hPen3);
DeleteObject(hPen4);
DeleteObject(hPen5);
最后,我们清理资源。
图:笔的样式
直线连接
可以使用三种不同的连接样式来连接线:PS_JOIN_BEVEL
,PS_JOIN_MITEl
和PS_JOIN_ROUND
。
linejoins.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DoDrawing(HWND);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Pens";
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Line joins",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 450, 200, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
DoDrawing(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void DoDrawing(HWND hwnd) {
LOGBRUSH brush;
COLORREF col = RGB(0, 0, 0);
DWORD pen_style = PS_SOLID | PS_JOIN_MITER | PS_GEOMETRIC;
brush.lbStyle = BS_SOLID;
brush.lbColor = col;
brush.lbHatch = 0;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HPEN hPen1 = ExtCreatePen(pen_style, 8, &brush, 0, NULL);
HPEN holdPen = SelectObject(hdc, hPen1);
POINT points[5] = { { 30, 30 }, { 130, 30 }, { 130, 100 },
{ 30, 100 }, { 30, 30}};
Polygon(hdc, points, 5);
pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_BEVEL;
HPEN hPen2 = ExtCreatePen(pen_style, 8, &brush, 0, NULL);
SelectObject(hdc, hPen2);
DeleteObject(hPen1);
POINT points2[5] = { { 160, 30 }, { 260, 30 }, { 260, 100 },
{ 160, 100 }, {160, 30 }};
MoveToEx(hdc, 130, 30, NULL);
Polygon(hdc, points2, 5);
pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_ROUND;
HPEN hPen3 = ExtCreatePen(pen_style, 8, &brush, 0, NULL);
SelectObject(hdc, hPen3);
DeleteObject(hPen2);
POINT points3[5] = { { 290, 30 }, { 390, 30 }, { 390, 100 },
{ 290, 100 }, {290, 30 }};
MoveToEx(hdc, 260, 30, NULL);
Polygon(hdc, points3, 5);
SelectObject(hdc, holdPen);
DeleteObject(hPen3);
EndPaint(hwnd, &ps);
}
在示例中,我们显示了矩形上的三种类型的线连接。
pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_BEVEL;
HPEN hPen2 = ExtCreatePen(pen_style, 8, &brush, 0, NULL);
ExtCreatePen()
函数创建一个带有PS_JOIN_BEVEL
连接的实心几何笔。
POINT points2[5] = { { 160, 30 }, { 260, 30 }, { 260, 100 },
{ 160, 100 }, {160, 30 }};
MoveToEx(hdc, 130, 30, NULL);
Polygon(hdc, points2, 5);
从提供的点开始,我们使用Polygon()
函数创建一个矩形。
图:直线连接
笔刷
画笔是基本图形对象。 它用于绘制图形形状的背景,例如矩形,椭圆形或多边形。 笔刷可以是纯色,阴影线或自定义位图图案。
实心笔刷
实心画笔是一种颜色。 它是用CreateSolidBrush()
函数创建的。
HBRUSH CreateSolidBrush(COLORREF crColor);
CreateSolidBrush()
函数创建具有指定纯色的画笔。
solidbrush.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRectangles(HWND);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Brush";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Solid Brush",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 220, 240, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
DrawRectangles(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void DrawRectangles(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HPEN hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
HPEN holdPen = SelectObject(hdc, hPen);
HBRUSH hBrush1 = CreateSolidBrush(RGB(121, 90, 0));
HBRUSH hBrush2 = CreateSolidBrush(RGB(240, 63, 19));
HBRUSH hBrush3 = CreateSolidBrush(RGB(240, 210, 18));
HBRUSH hBrush4 = CreateSolidBrush(RGB(9, 189, 21));
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
Rectangle(hdc, 30, 30, 100, 100);
SelectObject(hdc, hBrush2);
Rectangle(hdc, 110, 30, 180, 100);
SelectObject(hdc, hBrush3);
Rectangle(hdc, 30, 110, 100, 180);
SelectObject(hdc, hBrush4);
Rectangle(hdc, 110, 110, 180, 180);
SelectObject(hdc, holdPen);
SelectObject(hdc, holdBrush);
DeleteObject(hPen);
DeleteObject(hBrush1);
DeleteObject(hBrush2);
DeleteObject(hBrush3);
DeleteObject(hBrush4);
EndPaint(hwnd, &ps);
}
在示例中,我们创建了 4 个矩形,其中填充了 4 种不同的纯色。
HBRUSH hBrush1 = CreateSolidBrush(RGB(121, 90, 0));
在这里,我们创建一个纯色笔刷。
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
在设备上下文中选择了一个新画笔。
图:实心刷
舱口笔刷
有六种预定义的舱口笔刷。 在我们的示例中,我们展示了所有这些。
HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref);
CreateHatchBrush()
函数创建具有指定填充图案和颜色的画笔。
hatchbrushes.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRectangles(HWND hwnd);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpszClassName = L"Brush";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Hatch brushes",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 220, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_PAINT:
DrawRectangles(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void DrawRectangles(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HPEN hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
HPEN holdPen = SelectObject(hdc, hPen);
HBRUSH hBrush1 = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 0, 0));
HBRUSH hBrush2 = CreateHatchBrush(HS_FDIAGONAL, RGB(0, 0, 0));
HBRUSH hBrush3 = CreateHatchBrush(HS_CROSS, RGB(0, 0, 0));
HBRUSH hBrush4 = CreateHatchBrush(HS_HORIZONTAL, RGB(0, 0, 0));
HBRUSH hBrush5 = CreateHatchBrush(HS_DIAGCROSS, RGB(0, 0, 0));
HBRUSH hBrush6 = CreateHatchBrush(HS_VERTICAL, RGB(0, 0, 0));
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
DWORD col = GetSysColor(COLOR_BTNFACE);
SetBkColor(hdc, col);
Rectangle(hdc, 30, 30, 100, 80);
SelectObject(hdc, hBrush2);
Rectangle(hdc, 110, 30, 180, 80);
SelectObject(hdc, hBrush3);
Rectangle(hdc, 190, 30, 260, 80);
SelectObject(hdc, hBrush4);
Rectangle(hdc, 30, 110, 100, 160);
SelectObject(hdc, hBrush5);
Rectangle(hdc, 110, 110, 180, 160);
SelectObject(hdc, hBrush6);
Rectangle(hdc, 190, 110, 260, 160);
SelectObject(hdc, holdPen);
SelectObject(hdc, holdBrush);
DeleteObject(hPen);
DeleteObject(hBrush1);
DeleteObject(hBrush2);
DeleteObject(hBrush3);
DeleteObject(hBrush4);
DeleteObject(hBrush5);
DeleteObject(hBrush6);
EndPaint(hwnd, &ps);
}
此示例与上一个示例非常相似。 我们仅使用一个新的函数调用CreateHatchBrush()
。
HBRUSH hBrush1 = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 0, 0));
将创建对角线阴影笔刷。
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
画笔被选择到设备上下文中。 返回旧画笔的句柄。
DeleteObject(hBrush1);
笔刷对象被删除。
图:舱口刷
定制笔刷
可以使用CreatePatternBrush()
函数创建自定义画笔。
HBRUSH CreatePatternBrush(HBITMAP hbmp);
该函数获取要用于创建画笔的位图的句柄。
custombrush.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Custom brush";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Custom brush",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 200, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
static HBITMAP hBtm;
UINT bits[8] = { 0x111111ff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000 };
switch(msg) {
case WM_CREATE:
hBtm = CreateBitmap(8, 8, 1, 1, (LPBYTE) bits);
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
HBRUSH hCustomBrush = CreatePatternBrush(hBtm);
HBRUSH hOldBrush = SelectObject(hdc, hCustomBrush);
SelectObject(hdc, GetStockObject(NULL_PEN));
Rectangle(hdc, 20, 20, 250, 160);
SelectObject(hdc, hOldBrush);
DeleteObject(hCustomBrush);
SelectObject(hdc, GetStockObject(BLACK_PEN));
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
DeleteObject(hBtm);
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
该示例绘制了一个矩形。 其内部充满了自定义的画笔图案。
hBtm = CreateBitmap(8, 8, 1, 1, (LPBYTE) bits);
我们使用CreateBitmap()
函数创建位图图案。
HBRUSH hCustomBrush = CreatePatternBrush(hBtm);
CreatePatternBrush()
函数从提供的位图创建画笔对象。
HBRUSH hOldBrush = SelectObject(hdc, hCustomBrush);
我们使用SelectObject()
函数选择自定义画笔。
SelectObject(hdc, GetStockObject(NULL_PEN));
我们不会绘制矩形的轮廓。 当我们选择NULL_PEN
时,没有画出轮廓。
Rectangle(hdc, 20, 20, 250, 160);
矩形用Rectangle()
函数绘制; 其内部使用选定的自定义画笔绘制。
图:自定义刷
形状
形状是更复杂的几何对象。 在下面的示例中,我们将绘制各种几何形状。
shapes.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Shapes";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Shapes",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 390, 230, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
const POINT polygon[10] = { 30, 145, 85, 165, 105,
110, 65, 125, 30, 105 };
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Ellipse(hdc, 30, 30, 120, 90);
RoundRect(hdc, 150, 30, 240, 90, 15, 20);
Chord(hdc, 270, 30, 360, 90, 270, 45, 360, 45);
Polygon(hdc, polygon, 5);
Rectangle(hdc, 150, 110, 230, 160);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
在我们的示例中,我们创建了一个椭圆,一个圆角矩形,一个弦,一个多边形和一个矩形。
Ellipse(hdc, 30, 30, 120, 90);
Ellipse()
函数绘制一个椭圆。 Ellipse()
的参数是边界矩形的左上角和右下角的 x 和 y 坐标。 在此矩形内绘制椭圆。
RoundRect(hdc, 150, 30, 240, 90, 15, 20);
RoundRect()
函数绘制带有圆角的矩形。 RoundRect()
的参数是边界矩形的左上角和右下角的 x 和 y 坐标。 最后两个参数是用于绘制圆角的椭圆的宽度和高度。
Chord(hdc, 270, 30, 360, 90, 270, 45, 360, 45);
Chord()
函数绘制和弦。 和弦是由椭圆和线段的交点界定的区域。 前四个参数是边界矩形的左上角的 x 和 y 坐标以及右下角的 x 和 y 坐标。 接下来的四个参数是定义弦的起点的径向的 x 和 y 坐标以及定义弦的终点的径向的 x 和 y 坐标。
Polygon(hdc, polygon, 5);
Polygon()
函数绘制由两个或多个通过直线连接的顶点组成的多边形。 多边形是指向POINT
结构数组的指针,该数组指定多边形的顶点。 最后一个参数是数组中的点数。
Rectangle(hdc, 150, 110, 230, 160);
Rectangle()
函数绘制一个矩形。 该函数的参数是矩形左上角和右下角的 x 和 y 坐标。
图:形状
星形
在下面的示例中,我们使用Polyline()
函数绘制星形。
BOOL Polyline(HDC hdc, const POINT *lppt, int cPoints);
Polyline()
函数通过连接指定数组中的点来绘制一系列线段。 函数的第一个参数是设备上下文的句柄。 第二个参数是指向POINT
结构数组的指针。 第三个参数是数组中的点数。 此数字必须大于或等于 2。
star.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Star";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Star",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 250, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
POINT points[11] = {
{ 10, 85 },
{ 85, 75 },
{ 110, 10 },
{ 135, 75 },
{ 210, 85 },
{ 160, 125 },
{ 170, 190 },
{ 110, 150 },
{ 50, 190 },
{ 60, 125 },
{ 10, 85 }
};
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Polyline(hdc, points, 11);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
该示例绘制一个星形对象。
POINT points[11] = {
{ 10, 85 },
{ 85, 75 },
{ 110, 10 },
{ 135, 75 },
{ 210, 85 },
{ 160, 125 },
{ 170, 190 },
{ 110, 150 },
{ 50, 190 },
{ 60, 125 },
{ 10, 85 }
};
这是恒星的POINTS
的数组。
Polyline(hdc, points, 11);
Polyline()
函数绘制星形。
图:星星
文本
TextOutW()
函数使用当前选择的字体,背景色和文本色在指定位置写入字符串。
BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cchString);
函数的第一个参数是设备上下文的句柄。 接下来的两个参数是系统用于对齐字符串的参考点的 x 和 y 坐标。 第三个参数是指向要绘制的字符串的指针。 最后一个参数是字符串的长度。
sonnet55.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg ;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Sonnet 55";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Sonnet 55",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 390, 350, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
DWORD color;
HFONT hFont, holdFont;
static wchar_t *ver1 = L"Not marble, nor the gilded monuments";
static wchar_t *ver2 = L"Of princes, shall outlive this powerful rhyme;";
static wchar_t *ver3 = L"But you shall shine more bright in these contents";
static wchar_t *ver4 = L"Than unswept stone, besmear'd with sluttish time.";
static wchar_t *ver5 = L"When wasteful war shall statues overturn,";
static wchar_t *ver6 = L"And broils root out the work of masonry,";
static wchar_t *ver7 = L"Nor Mars his sword, nor war's quick fire shall burn";
static wchar_t *ver8 = L"The living record of your memory.";
static wchar_t *ver9 = L"'Gainst death, and all oblivious enmity";
static wchar_t *ver10 = L"Shall you pace forth; your praise shall still find room";
static wchar_t *ver11 = L"Even in the eyes of all posterity";
static wchar_t *ver12 = L"That wear this world out to the ending doom.";
static wchar_t *ver13 = L"So, till the judgment that yourself arise,";
static wchar_t *ver14 = L"You live in this, and dwell in lovers' eyes.";
switch(msg) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
color = GetSysColor(COLOR_BTNFACE);
SetBkColor(hdc, color);
hFont = CreateFontW(15, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0,
0, 0, 0, 0, L"Georgia");
holdFont = SelectObject(hdc, hFont);
TextOutW(hdc, 50, 20, ver1, lstrlenW(ver1));
TextOutW(hdc, 50, 40, ver2, lstrlenW(ver2));
TextOutW(hdc, 50, 60, ver3, lstrlenW(ver3));
TextOutW(hdc, 50, 80, ver4, lstrlenW(ver4));
TextOutW(hdc, 50, 100, ver5, lstrlenW(ver5));
TextOutW(hdc, 50, 120, ver6, lstrlenW(ver6));
TextOutW(hdc, 50, 140, ver7, lstrlenW(ver7));
TextOutW(hdc, 50, 160, ver8, lstrlenW(ver8));
TextOutW(hdc, 50, 180, ver9, lstrlenW(ver9));
TextOutW(hdc, 50, 200, ver10, lstrlenW(ver10));
TextOutW(hdc, 50, 220, ver11, lstrlenW(ver11));
TextOutW(hdc, 50, 240, ver12, lstrlenW(ver12));
TextOutW(hdc, 50, 260, ver13, lstrlenW(ver13));
TextOutW(hdc, 50, 280, ver14, lstrlenW(ver14));
SelectObject(hdc, holdFont);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
我们使用TextOutW()
函数在窗口上绘制几节经文。
color = GetSysColor(COLOR_BTNFACE);
SetBkColor(hdc, color);
默认情况下,如果我们在窗口的工作区上绘制一些文本,则背景设置为白色。 我们可以通过使用SetBkColor()
函数设置背景颜色来更改此设置。 我们使用了典型的 Windows 灰色。 GetSysColor()
函数用于获取按钮,标题或窗口控件背景中使用的系统颜色。
hFont = CreateFontW(15, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0,
0, 0, 0, 0, L"Georgia");
holdFont = SelectObject(hdc, hFont);
在这里,我们使用CreateFontW()
函数创建一个字体对象。 该函数有 14 个参数; 我们不必全部指定。 我们仅指定字体大小,字体粗细和 fontface 参数。
TextOutW(hdc, 50, 20, verse1, lstrlenW(verse1));
使用TextOutW()
函数将文本绘制到窗口上。 字符串的长度由lstrlenW()
函数确定。
图:文本
绘制位图
位图是一个图形对象,用于创建,处理图像并将其作为文件存储在磁盘上。 BMP 是 Windows 的本机位图格式,实际上用于存储任何类型的位图数据。
drawbitmap.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASSW wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"Draw Bitmap";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassW(&wc);
CreateWindowW(wc.lpszClassName, L"Draw Bitmap",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 280, 220, NULL, NULL, hInstance, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
static HBITMAP hBitmap;
HDC hdc;
PAINTSTRUCT ps;
BITMAP bitmap;
HDC hdcMem;
HGDIOBJ oldBitmap;
switch(msg) {
case WM_CREATE:
hBitmap = (HBITMAP) LoadImageW(NULL, L"C:\\prog\\slovakia.bmp",
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (hBitmap == NULL) {
MessageBoxW(hwnd, L"Failed to load image", L"Error", MB_OK);
}
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
oldBitmap = SelectObject(hdcMem, hBitmap);
GetObject(hBitmap, sizeof(bitmap), &bitmap);
BitBlt(hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight,
hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
DeleteObject(hBitmap);
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
该示例在窗口上绘制了斯洛伐克国旗。 图片为 BMP 文件格式。
static HBITMAP hBitmap;
HBITMAP
是位图对象的句柄。
BITMAP bitmap;
BITMAP
结构定义位图的类型,宽度,高度,颜色格式和位值。
hBitmap = (HBITMAP) LoadImageW(NULL, L"C:\\prog\\slovakia.bmp",
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
LoadImageW()
函数从磁盘加载 BMP 图像。 它返回位图的句柄。
GetObject(hBitmap, sizeof(bitmap), &bitmap);
GetObject()
函数在提供的BITMAP
结构中存储有关位图的信息。
hdcMem = CreateCompatibleDC(hdc);
CreateCompatibleDC()
函数创建与应用当前屏幕兼容的存储设备上下文。
oldBitmap = SelectObject(hdcMem, hBitmap);
SelectObject()
函数将一个对象选择到存储设备上下文中。 在将位图用于任何事物之前,必须先将其选择到存储设备上下文中。
BitBlt(hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
BitBlt()
函数执行与像素矩形相对应的颜色数据从指定的源设备上下文到目标设备上下文的位块传输。
SelectObject(hdcMem, oldBitmap);
应用在完成使用新对象的绘制之后,应始终将其替换为原始的默认对象。
DeleteDC(hdcMem);
与存储设备上下文关联的资源被释放。
图:绘制位图
在 Windows API 教程的这一部分中,我们进行了一些绘制。