python3+PyQt5图形项的自定义和交互 python3实现page Designer应用程序
作者:basisworker 时间:2022-04-23 02:28:25
本文通过Python3+PyQt5实现《python Qt Gui 快速编程》这本书的page Designer应用程序,采用QGraphicsView,QGraphicsScene,QGraphicsItem,这个程序包含有多个文本,图片和框的页面。有些图形类在PyQt5已过时,所以本代码改动幅度比较大。主要的类或方法的改变如下:
QMatrix==>QTransform
setMatrix==>setTransform
rotate ==> setRotation
本例中,由于event.delta()已过时,还重写了wheelEvent方法:
def wheelEvent(self, event):
#factor = 1.41 ** (-event.delta() / 240.0)
#factor = 1.41 ** (-abs(event.startX()-event.y()) / 240.0)
factor = event.angleDelta().y()/120.0
if event.angleDelta().y()/120.0 > 0:
factor=2
else:
factor=0.5
self.scale(factor, factor)
为了保持代码可读行,增加了一个类:
class GraphicsPixmapItem(QGraphicsPixmapItem): #add by yangrongdong
def __init__(self,pixmap):
super(QGraphicsPixmapItem, self).__init__(pixmap)
本例中还有包含菜单的按钮:
if text == "&Align":
menu = QMenu(self)
for text, arg in (
("Align &Left", Qt.AlignLeft),
("Align &Right", Qt.AlignRight),
("Align &Top", Qt.AlignTop),
("Align &Bottom", Qt.AlignBottom)):
wrapper = functools.partial(self.setAlignment, arg)
self.wrapped.append(wrapper)
menu.addAction(text, wrapper)
button.setMenu(menu)
本例中还针对QStyleOptionGraphicsItem.levelOfDetail已过时,改写如下:
option.levelOfDetailFromTransform(self.transform())
下面为完整的代码:
#!/usr/bin/env python3
import functools
import random
import sys
from PyQt5.QtCore import (QByteArray, QDataStream, QFile, QFileInfo,
QIODevice, QPoint, QPointF, QRectF, Qt)
from PyQt5.QtWidgets import (QApplication, QDialog,
QDialogButtonBox, QFileDialog, QFontComboBox,
QGraphicsItem, QGraphicsPixmapItem,
QGraphicsScene, QGraphicsTextItem, QGraphicsView, QGridLayout,
QHBoxLayout, QLabel, QMenu, QMessageBox,QPushButton, QSpinBox,
QStyle, QTextEdit, QVBoxLayout)
from PyQt5.QtGui import QFont,QCursor,QFontMetrics,QTransform,QPainter,QPen,QPixmap
from PyQt5.QtPrintSupport import QPrinter,QPrintDialog
MAC = True
try:
from PyQt5.QtGui import qt_mac_set_native_menubar
except ImportError:
MAC = False
#PageSize = (595, 842) # A4 in points
PageSize = (612, 792) # US Letter in points
PointSize = 10
MagicNumber = 0x70616765
FileVersion = 1
Dirty = False
class TextItemDlg(QDialog):
def __init__(self, item=None, position=None, scene=None, parent=None):
super(QDialog, self).__init__(parent)
self.item = item
self.position = position
self.scene = scene
self.editor = QTextEdit()
self.editor.setAcceptRichText(False)
self.editor.setTabChangesFocus(True)
editorLabel = QLabel("&Text:")
editorLabel.setBuddy(self.editor)
self.fontComboBox = QFontComboBox()
self.fontComboBox.setCurrentFont(QFont("Times", PointSize))
fontLabel = QLabel("&Font:")
fontLabel.setBuddy(self.fontComboBox)
self.fontSpinBox = QSpinBox()
self.fontSpinBox.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
self.fontSpinBox.setRange(6, 280)
self.fontSpinBox.setValue(PointSize)
fontSizeLabel = QLabel("&Size:")
fontSizeLabel.setBuddy(self.fontSpinBox)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok|
QDialogButtonBox.Cancel)
self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
if self.item is not None:
self.editor.setPlainText(self.item.toPlainText())
self.fontComboBox.setCurrentFont(self.item.font())
self.fontSpinBox.setValue(self.item.font().pointSize())
layout = QGridLayout()
layout.addWidget(editorLabel, 0, 0)
layout.addWidget(self.editor, 1, 0, 1, 6)
layout.addWidget(fontLabel, 2, 0)
layout.addWidget(self.fontComboBox, 2, 1, 1, 2)
layout.addWidget(fontSizeLabel, 2, 3)
layout.addWidget(self.fontSpinBox, 2, 4, 1, 2)
layout.addWidget(self.buttonBox, 3, 0, 1, 6)
self.setLayout(layout)
self.fontComboBox.currentFontChanged.connect(self.updateUi)
self.fontSpinBox.valueChanged.connect(self.updateUi)
self.editor.textChanged.connect(self.updateUi)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.setWindowTitle("Page Designer - {0} Text Item".format(
"Add" if self.item is None else "Edit"))
self.updateUi()
def updateUi(self):
font = self.fontComboBox.currentFont()
font.setPointSize(self.fontSpinBox.value())
self.editor.document().setDefaultFont(font)
self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(
bool(self.editor.toPlainText()))
def accept(self):
if self.item is None:
self.item = TextItem("", self.position, self.scene)
font = self.fontComboBox.currentFont()
font.setPointSize(self.fontSpinBox.value())
self.item.setFont(font)
self.item.setPlainText(self.editor.toPlainText())
self.item.update()
global Dirty
Dirty = True
QDialog.accept(self)
class TextItem(QGraphicsTextItem):
def __init__(self, text, position, scene,
font=QFont("Times", PointSize), matrix=QTransform()):
super(TextItem, self).__init__(text)
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable)
self.setFont(font)
self.setPos(position)
self.setTransform(matrix)
scene.clearSelection()
scene.addItem(self)
self.setSelected(True)
global Dirty
Dirty = True
def parentWidget(self):
return self.scene().views()[0]
def itemChange(self, change, variant):
if change != QGraphicsItem.ItemSelectedChange:
global Dirty
Dirty = True
return QGraphicsTextItem.itemChange(self, change, variant)
def mouseDoubleClickEvent(self, event):
dialog = TextItemDlg(self, self.parentWidget())
dialog.exec_()
class GraphicsPixmapItem(QGraphicsPixmapItem): #add by yangrongdong
def __init__(self,pixmap):
super(QGraphicsPixmapItem, self).__init__(pixmap)
class BoxItem(QGraphicsItem):
def __init__(self, position, scene, style=Qt.SolidLine,
rect=None, matrix=QTransform()):
super(BoxItem, self).__init__()
self.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable|
QGraphicsItem.ItemIsFocusable)
if rect is None:
rect = QRectF(-10 * PointSize, -PointSize, 20 * PointSize,
2 * PointSize)
self.rect = rect
self.style = style
self.setPos(position)
self.setTransform(matrix)
scene.clearSelection()
scene.addItem(self)
self.setSelected(True)
self.setFocus()
global Dirty
Dirty = True
def parentWidget(self):
return self.scene().views()[0]
def boundingRect(self):
return self.rect.adjusted(-2, -2, 2, 2)
def paint(self, painter, option, widget):
pen = QPen(self.style)
pen.setColor(Qt.black)
pen.setWidth(1)
if option.state & QStyle.State_Selected:
pen.setColor(Qt.blue)
painter.setPen(pen)
painter.drawRect(self.rect)
def itemChange(self, change, variant):
if change != QGraphicsItem.ItemSelectedChange:
global Dirty
Dirty = True
return QGraphicsItem.itemChange(self, change, variant)
def contextMenuEvent(self, event):
wrapped = []
menu = QMenu(self.parentWidget())
for text, param in (
("&Solid", Qt.SolidLine),
("&Dashed", Qt.DashLine),
("D&otted", Qt.DotLine),
("D&ashDotted", Qt.DashDotLine),
("DashDo&tDotted", Qt.DashDotDotLine)):
wrapper = functools.partial(self.setStyle, param)
wrapped.append(wrapper)
menu.addAction(text, wrapper)
menu.exec_(event.screenPos())
def setStyle(self, style):
self.style = style
self.update()
global Dirty
Dirty = True
def keyPressEvent(self, event):
factor = PointSize / 4
changed = False
if event.modifiers() & Qt.ShiftModifier:
if event.key() == Qt.Key_Left:
self.rect.setRight(self.rect.right() - factor)
changed = True
elif event.key() == Qt.Key_Right:
self.rect.setRight(self.rect.right() + factor)
changed = True
elif event.key() == Qt.Key_Up:
self.rect.setBottom(self.rect.bottom() - factor)
changed = True
elif event.key() == Qt.Key_Down:
self.rect.setBottom(self.rect.bottom() + factor)
changed = True
if changed:
self.update()
global Dirty
Dirty = True
else:
QGraphicsItem.keyPressEvent(self, event)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
def wheelEvent(self, event):
#factor = 1.41 ** (-event.delta() / 240.0)
factor = event.angleDelta().y()/120.0
if event.angleDelta().y()/120.0 > 0:
factor=2
else:
factor=0.5
self.scale(factor, factor)
class MainForm(QDialog):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.filename = ""
self.copiedItem = QByteArray()
self.pasteOffset = 5
self.prevPoint = QPoint()
self.addOffset = 5
self.borders = []
self.printer = QPrinter(QPrinter.HighResolution)
self.printer.setPageSize(QPrinter.Letter)
self.view = GraphicsView()
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, PageSize[0], PageSize[1])
self.addBorders()
self.view.setScene(self.scene)
self.wrapped = [] # Needed to keep wrappers alive
buttonLayout = QVBoxLayout()
for text, slot in (
("Add &Text", self.addText),
("Add &Box", self.addBox),
("Add Pi&xmap", self.addPixmap),
("&Align", None),
("&Copy", self.copy),
("C&ut", self.cut),
("&Paste", self.paste),
("&Delete...", self.delete),
("&Rotate", self.rotate),
("Pri&nt...", self.print_),
("&Open...", self.open),
("&Save", self.save),
("&Quit", self.accept)):
button = QPushButton(text)
if not MAC:
button.setFocusPolicy(Qt.NoFocus)
if slot is not None:
button.clicked.connect(slot)
if text == "&Align":
menu = QMenu(self)
for text, arg in (
("Align &Left", Qt.AlignLeft),
("Align &Right", Qt.AlignRight),
("Align &Top", Qt.AlignTop),
("Align &Bottom", Qt.AlignBottom)):
wrapper = functools.partial(self.setAlignment, arg)
self.wrapped.append(wrapper)
menu.addAction(text, wrapper)
button.setMenu(menu)
if text == "Pri&nt...":
buttonLayout.addStretch(5)
if text == "&Quit":
buttonLayout.addStretch(1)
buttonLayout.addWidget(button)
buttonLayout.addStretch()
layout = QHBoxLayout()
layout.addWidget(self.view, 1)
layout.addLayout(buttonLayout)
self.setLayout(layout)
fm = QFontMetrics(self.font())
self.resize(self.scene.width() + fm.width(" Delete... ") + 50,
self.scene.height() + 50)
self.setWindowTitle("Page Designer")
def addBorders(self):
self.borders = []
rect = QRectF(0, 0, PageSize[0], PageSize[1])
self.borders.append(self.scene.addRect(rect, Qt.yellow))
margin = 5.25 * PointSize
self.borders.append(self.scene.addRect(
rect.adjusted(margin, margin, -margin, -margin),
Qt.yellow))
def removeBorders(self):
while self.borders:
item = self.borders.pop()
self.scene.removeItem(item)
del item
def reject(self):
self.accept()
def accept(self):
self.offerSave()
QDialog.accept(self)
def offerSave(self):
if (Dirty and QMessageBox.question(self,
"Page Designer - Unsaved Changes",
"Save unsaved changes?",
QMessageBox.Yes|QMessageBox.No) ==
QMessageBox.Yes):
self.save()
def position(self):
point = self.mapFromGlobal(QCursor.pos())
if not self.view.geometry().contains(point):
coord = random.randint(36, 144)
point = QPoint(coord, coord)
else:
if point == self.prevPoint:
point += QPoint(self.addOffset, self.addOffset)
self.addOffset += 5
else:
self.addOffset = 5
self.prevPoint = point
return self.view.mapToScene(point)
def addText(self):
dialog = TextItemDlg(position=self.position(),
scene=self.scene, parent=self)
dialog.exec_()
def addBox(self):
BoxItem(self.position(), self.scene)
def addPixmap(self):
path = (QFileInfo(self.filename).path()
if self.filename else ".")
fname,filetype = QFileDialog.getOpenFileName(self,
"Page Designer - Add Pixmap", path,
"Pixmap Files (*.bmp *.jpg *.png *.xpm)")
if not fname:
return
self.createPixmapItem(QPixmap(fname), self.position())
def createPixmapItem(self, pixmap, position, matrix=QTransform()):
item = GraphicsPixmapItem(pixmap)
item.setFlags(QGraphicsItem.ItemIsSelectable|
QGraphicsItem.ItemIsMovable)
item.setPos(position)
item.setTransform(matrix)
self.scene.clearSelection()
self.scene.addItem(item)
item.setSelected(True)
global Dirty
Dirty = True
return item
def selectedItem(self):
items = self.scene.selectedItems()
if len(items) == 1:
return items[0]
return None
def copy(self):
item = self.selectedItem()
if item is None:
return
self.copiedItem.clear()
self.pasteOffset = 5
stream = QDataStream(self.copiedItem, QIODevice.WriteOnly)
self.writeItemToStream(stream, item)
def cut(self):
item = self.selectedItem()
if item is None:
return
self.copy()
self.scene.removeItem(item)
del item
def paste(self):
if self.copiedItem.isEmpty():
return
stream = QDataStream(self.copiedItem, QIODevice.ReadOnly)
self.readItemFromStream(stream, self.pasteOffset)
self.pasteOffset += 5
def setAlignment(self, alignment):
# Items are returned in arbitrary order
items = self.scene.selectedItems()
if len(items) <= 1:
return
# Gather coordinate data
leftXs, rightXs, topYs, bottomYs = [], [], [], []
for item in items:
rect = item.sceneBoundingRect()
leftXs.append(rect.x())
rightXs.append(rect.x() + rect.width())
topYs.append(rect.y())
bottomYs.append(rect.y() + rect.height())
# Perform alignment
if alignment == Qt.AlignLeft:
xAlignment = min(leftXs)
for i, item in enumerate(items):
item.moveBy(xAlignment - leftXs[i], 0)
elif alignment == Qt.AlignRight:
xAlignment = max(rightXs)
for i, item in enumerate(items):
item.moveBy(xAlignment - rightXs[i], 0)
elif alignment == Qt.AlignTop:
yAlignment = min(topYs)
for i, item in enumerate(items):
item.moveBy(0, yAlignment - topYs[i])
elif alignment == Qt.AlignBottom:
yAlignment = max(bottomYs)
for i, item in enumerate(items):
item.moveBy(0, yAlignment - bottomYs[i])
global Dirty
Dirty = True
def rotate(self):
for item in self.scene.selectedItems():
item.setRotation(item.rotation()+30)
def delete(self):
items = self.scene.selectedItems()
if (len(items) and QMessageBox.question(self,
"Page Designer - Delete",
"Delete {0} item{1}?".format(len(items),
"s" if len(items) != 1 else ""),
QMessageBox.Yes|QMessageBox.No) ==
QMessageBox.Yes):
while items:
item = items.pop()
self.scene.removeItem(item)
del item
global Dirty
Dirty = True
def print_(self):
dialog = QPrintDialog(self.printer)
if dialog.exec_():
painter = QPainter(self.printer)
painter.setRenderHint(QPainter.Antialiasing)
painter.setRenderHint(QPainter.TextAntialiasing)
self.scene.clearSelection()
self.removeBorders()
self.scene.render(painter)
self.addBorders()
def open(self):
self.offerSave()
path = (QFileInfo(self.filename).path()
if self.filename else ".")
fname,filetype = QFileDialog.getOpenFileName(self,
"Page Designer - Open", path,
"Page Designer Files (*.pgd)")
if not fname:
return
self.filename = fname
fh = None
try:
fh = QFile(self.filename)
if not fh.open(QIODevice.ReadOnly):
raise IOError(str(fh.errorString()))
items = self.scene.items()
while items:
item = items.pop()
self.scene.removeItem(item)
del item
self.addBorders()
stream = QDataStream(fh)
stream.setVersion(QDataStream.Qt_5_7)
magic = stream.readInt32()
if magic != MagicNumber:
raise IOError("not a valid .pgd file")
fileVersion = stream.readInt16()
if fileVersion != FileVersion:
raise IOError("unrecognised .pgd file version")
while not fh.atEnd():
self.readItemFromStream(stream)
except IOError as e:
QMessageBox.warning(self, "Page Designer -- Open Error",
"Failed to open {0}: {1}".format(self.filename, e))
finally:
if fh is not None:
fh.close()
global Dirty
Dirty = False
def save(self):
if not self.filename:
path = "."
fname,filetype = QFileDialog.getSaveFileName(self,
"Page Designer - Save As", path,
"Page Designer Files (*.pgd)")
if not fname:
return
self.filename = fname
fh = None
try:
fh = QFile(self.filename)
if not fh.open(QIODevice.WriteOnly):
raise IOError(str(fh.errorString()))
self.scene.clearSelection()
stream = QDataStream(fh)
stream.setVersion(QDataStream.Qt_5_7)
stream.writeInt32(MagicNumber)
stream.writeInt16(FileVersion)
for item in self.scene.items():
self.writeItemToStream(stream, item)
except IOError as e:
QMessageBox.warning(self, "Page Designer -- Save Error",
"Failed to save {0}: {1}".format(self.filename, e))
finally:
if fh is not None:
fh.close()
global Dirty
Dirty = False
def readItemFromStream(self, stream, offset=0):
type = ""
position = QPointF()
matrix = QTransform()
rotateangle=0#add by yangrongdong
type=stream.readQString()
stream >> position >> matrix
if offset:
position += QPointF(offset, offset)
if type == "Text":
text = ""
font = QFont()
text=stream.readQString()
stream >> font
rotateangle=stream.readFloat()
tx=TextItem(text, position, self.scene, font, matrix)
tx.setRotation(rotateangle)
elif type == "Box":
rect = QRectF()
stream >> rect
style = Qt.PenStyle(stream.readInt16())
rotateangle=stream.readFloat()
bx=BoxItem(position, self.scene, style, rect, matrix)
bx.setRotation(rotateangle)
elif type == "Pixmap":
pixmap = QPixmap()
stream >> pixmap
rotateangle=stream.readFloat()
px=self.createPixmapItem(pixmap, position, matrix)
px.setRotation(rotateangle)
def writeItemToStream(self, stream, item):
if isinstance(item, TextItem):
stream.writeQString("Text")
stream<<item.pos()<< item.transform()
stream.writeQString(item.toPlainText())
stream<< item.font()
stream.writeFloat(item.rotation())#add by yangrongdong
elif isinstance(item, GraphicsPixmapItem):
stream.writeQString("Pixmap")
stream << item.pos() << item.transform() << item.pixmap()
stream.writeFloat(item.rotation())#add by yangrongdong
elif isinstance(item, BoxItem):
stream.writeQString("Box")
stream<< item.pos() << item.transform() << item.rect
stream.writeInt16(item.style)
stream.writeFloat(item.rotation())#add by yangrongdong
app = QApplication(sys.argv)
form = MainForm()
rect = QApplication.desktop().availableGeometry()
form.resize(int(rect.width() * 0.6), int(rect.height() * 0.9))
form.show()
app.exec_()
运行结果
来源:https://blog.csdn.net/xiaoyangyang20/article/details/60571309
标签:python3,PyQt5,page,Designer
0
投稿
猜你喜欢
使用PyV8在Python爬虫中执行js代码
2022-05-09 14:33:36
python简单实现9宫格图片实例
2021-10-22 13:23:25
opencv python模糊影像检测效果
2021-07-27 04:44:13
Python jieba分词添加自定义词和去除不需要长尾词的操作方法
2023-05-05 11:20:35
Python与数据库的交互问题小结
2021-11-14 11:46:47
python获取网页中所有图片并筛选指定分辨率的方法
2021-07-28 04:57:23
详解numpy.ndarray.reshape()函数的参数问题
2022-02-06 20:22:57
python用WxPython库实现无边框窗体和透明窗体实现方法详解
2021-09-04 18:48:45
python中数据爬虫requests库使用方法详解
2022-04-01 05:47:11
Python中的数据对象持久化存储模块pickle的使用示例
2023-09-26 01:26:28
python3实现基于用户的协同过滤
2023-11-17 01:39:47
Python字典 dict几种遍历方式
2023-01-14 19:48:28
python得到qq句柄,并显示在前台的方法
2021-10-08 12:44:30
python字符串中匹配数字的正则表达式
2023-11-19 17:36:02
仿淘宝首页商品分类列表效果
2009-01-22 13:39:00
python3.5绘制随机漫步图
2022-08-12 14:16:13
如何使用Cython对python代码进行加密
2023-07-21 19:33:40
成功实现ajax,xmlhttp跨域访问(php,asp,jsp)
2008-02-13 18:40:00
详解Django 时间与时区设置问题
2021-02-19 03:08:42
Linux下C连接MySQL出现错误解决一例
2008-12-29 13:17:00