PyQt5 入门

换了VSCODE开发,感觉比sublime好点,可能是由于第三版老弹框烦人吧。VSCODE看着也挺好看的。

学习 PyQt5 中文教程

0. 安装完之后错误

pip 安装了 pyqt5

from PyQt5.QtWidgets import QApplication, QWidget
这句错误:E0611:No name 'QApplication' in module 'PyQt5.QtWidgets'

搜到是要sip,卸载python5重新安装了sip后再试还是错误,但是直接在命令行运行是好的。说明现在是外部环境的问题,把当前Anaconda路径换为python3路径,还是有错误。
最后换google一次解决,在VSCODE用户配置里加

"python.linting.pylintArgs": [
        "--extension-pkg-whitelist=PyQt5"
    ]

ok.

当前版本

Python==3.7.0
PyQt5==5.11.2
PyQt5-sip==4.19.12
sip==4.19.8
操作系统:win10 64位

1. Hello World

加图标的时候,没有反应,根据原作者overflow的链接看的更模糊了。
图片目录在F:\py\py-snippet\hominid\music.png
,换了个绝对路径就好了。
因为

如果你在C:\test目录下执行python getpath\getpath.py,那么os.getcwd()会输出“C:\test”,sys.path[0]会输出“C:\test\getpath”。

我工作目录是在F:\py\py-snippet,python执行时候的目录下没有图片,所以下面两种方式都可以:

self.setWindowIcon(QIcon(sys.path[0]+'/music.png'))
self.setWindowIcon(QIcon('hominid/music.png'))

第一篇很简单,放个窗口,控制大小和位置,退出

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QToolTip, QPushButton, QMessageBox, QDesktopWidget
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import QCoreApplication

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    
    def initUI(self):
        # location and size 
        self.setGeometry(300,300,300,300)
        
        # title
        self.setWindowTitle('Icon')
        
        # icon
        self.setWindowIcon(QIcon(sys.path[0]+'/music.png'))
        
        # 提示消息字体
        QToolTip.setFont(QFont('Georgia',10))

        # 提示框
        self.setToolTip('this is <br/><i>python</i> and <i>pyqt5</i><hr/> program')

        # 按钮以及提示框
        btn=QPushButton('退出',self)
        # 退出事件
        btn.clicked.connect(QCoreApplication.instance().quit)
        btn.setToolTip('quick, quick click')
        #btn.resize(btn.sizeHint())
        btn.move(50,50)

        self.center()
        self.show()
        
    # 关闭事件
    def closeEvent(self,event):
        print('close')
        reply = QMessageBox.question(self,"answer","To be or not to be?",QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            #print(QMessageBox.Yes)
            event.accept()
        else:
            reply = QMessageBox.question(self,'😕','are you sure? must quit',QMessageBox.Yes, QMessageBox.Yes)
            #event.ignore()
            event.accept() 
            
    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        print(cp)
        qr.moveCenter(cp)
        self.move(qr.topLeft())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

2. 状态工具和菜单栏

有意思,对文本框更多操作的话,可以做记事本啊。一直期望的打字字母烟花效果,可以考虑之后做。

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, qApp, QMenu, QTextEdit
from PyQt5.QtGui import QIcon

class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 状态栏
        self.statusbar=self.statusBar()
        self.statusbar.showMessage('loading...')
        
        # 动作
        exitAct = QAction(QIcon(sys.path[0]+'/music.png'),'&Exit',self)
        exitAct.setShortcut('Ctrl+1')
        exitAct.setStatusTip('load failed')
        exitAct.triggered.connect(qApp.quit)

        # 菜单
        menubar = self.menuBar()
        exitMenu = menubar.addMenu('&Exit')
        exitMenu.addAction(exitAct)

        # 多级菜单和动作
        ## 菜单栏加一项
        fileMenu = menubar.addMenu('File')
        ## 添加Add动作
        addAct = QAction('Add',self)
        fileMenu.addAction(addAct)
        ## 添加'save as'动作和'save'菜单,并将'Save as'动作加到'Save'菜单上
        saveAsAct = QAction('Save as',self)
        saveMenu = QMenu('save',self)
        saveMenu.addAction(saveAsAct)
        fileMenu.addMenu(saveMenu)
        
        # 勾选菜单
        viewMenu = menubar.addMenu('View')
        viewStatAct = QAction('View statusbar',self,checkable=True)
        viewStatAct.setStatusTip('..check me.')
        viewStatAct.setChecked(True)
        viewStatAct.triggered.connect(self.toggleMenu)
        viewMenu.addAction(viewStatAct)

        # 工具栏,用到了上面的exitAct
        self.toolbar = self.addToolBar('Exit')
        self.toolbar.addAction(exitAct)

        # 文本框
        textEdit = QTextEdit()
        self.setCentralWidget(textEdit)

        # 窗口
        #self.setGeometry(300,300,250,150)
        self.setWindowTitle('原始人')
        self.show()

    # 勾选(取消勾选)动作处理
    def toggleMenu(self,state):
        if state:
            self.statusbar.show()
        else:
            self.statusbar.hide()

    # 右键菜单事件
    def contextMenuEvent(self,event):
        #print('right')
        cmenu = QMenu(self)
        act1 = cmenu.addAction('原')
        act2 = cmenu.addAction('生')
        act3 = cmenu.addAction('右')
        act4 = cmenu.addAction('键')
        quitAct = cmenu.addAction('X')
        action = cmenu.exec_(self.mapToGlobal(event.pos()))

        if action==quitAct:
            #print(action,quitAct)
            qApp.quit()
        else:
            self.statusbar.show()
            self.statusbar.showMessage("缘,妙不可言")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

3. 布局管理

不会重绘界面或者其他操作,布局显得略枯燥

import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QPushButton, QHBoxLayout, QVBoxLayout, QGridLayout,  QLineEdit, QTextEdit

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        select='feedback'

        if select=='box':
            # 绝对定位
            for i in range(1,20):
                lb1=QLabel('(。・∀・)ノ',self)
                lb1.move(20*i,15*i)

            # 盒布局
            okButton = QPushButton("ok")
            cancelButton = QPushButton("cancel")

            hbox = QHBoxLayout()
            hbox.addStretch(1)
            hbox.addWidget(okButton)
            hbox.addWidget(cancelButton)
            
            vbox = QVBoxLayout()
            vbox.addStretch(1)
            vbox.addLayout(hbox)

            self.setLayout(vbox)

            self.setWindowTitle('绝对定位与盒布局')
        elif select=='grid':
            # 栅格布局
            grid = QGridLayout()
            self.setLayout(grid)
            names = ['Cls', 'Bck', '', 'Close',
                    '7', '8', '9', '/',
                    '4', '5', '6', '*',
                    '1', '2', '3', '-',
                    '0', '.', '=', '+']
            positions=[(i,j) for i in range(5) for j in range(4)]

            for position,name in zip(positions,names):
                if name=='':
                    continue
                button = QPushButton(name)
                grid.addWidget(button,*position)
                #print(position,*position)
            
            self.setWindowTitle('栅格布局')
        elif select=='feedback':
            title = QLabel('Title')
            author = QLabel('author')
            review = QLabel('review')

            titleEdit = QLineEdit()
            authorEdit = QLineEdit()
            reviewEdit = QTextEdit()

            grid = QGridLayout()
            grid.setSpacing(15)

            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.setWindowTitle('基于栅格布局的反馈界面')
        # 窗口    
        self.setGeometry(300,300,500,300)
        
        self.show()
if __name__=='__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

4. 事件和信号

sender是信号的发送者,receiver是信号的接收者,slot是对这个信号应该做出的反应

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QLCDNumber, QSlider, QVBoxLayout, QApplication, QGridLayout, QLabel

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        # 基础
        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal,self)
        vbox = QVBoxLayout()
        vbox.addStretch(1)
        vbox.addWidget(lcd)
        vbox.addWidget(sld)
        self.setLayout(vbox)
        sld.valueChanged.connect(lcd.display)

        # 添加一个label组件,用来显示(通过鼠标移动事件对象产生的)坐标
        grid = QGridLayout()
        grid.setSpacing(10)
        x=0
        y=0
        self.text="({0},{1})".format(x,y)
        self.label=QLabel(self.text,self)
        grid.addWidget(self.label,0,0,Qt.AlignTop)
        ## 开启鼠标追踪
        self.setMouseTracking(True)
        self.setLayout(grid)
       
        #窗口
        self.setGeometry(300,300,250,350)
        self.setWindowTitle('Signal and slot')
        self.show()
    
    # 重构事件处理器,按键事件
    def keyPressEvent(self,e):
        # print(e.key())
        # wasd 87 65 83 68
        if e.key()==Qt.Key_Escape:
            self.close()

    # 鼠标移动事件
    def mouseMoveEvent(self,e):
        x=e.x()
        y=e.y()
        text="({0},{1})".format(x,y)
        self.label.setText(text)
    
    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text()+"was pressed")

if __name__=='__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication

# 自定义closeApp信号
class Communicate(QObject):
    closeApp = pyqtSignal()

class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 事件发送
        btn1 = QPushButton("Button 1",self)
        btn1.move(30,50)
        btn2 = QPushButton("Button 2",self)
        btn2.move(150,50)
        btn1.clicked.connect(self.buttonClicked)
        btn2.clicked.connect(self.buttonClicked)
        ## 显示状态栏
        self.statusBar()

        # 自定义信号发送
        self.c = Communicate()
        self.c.closeApp.connect(self.close)
        
        #窗口
        self.setGeometry(300,300,250,350)
        self.setWindowTitle('Signal and slot')
        self.show()

    # slot,事件发送:发送者为button,接收者为状态栏statusBar
    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text()+" was pressed")

    # 发送closeApp信号
    def mousePressEvent(self,event):
        self.c.closeApp.emit()

if __name__=='__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

5. 对话框

有意思的一节,简直都可以停下来做音乐播放器了。

注:打开中文文件时错误,加编码就行了 f=open(fname[0],'r',encoding="UTF-8")

文字颜色字体对话框,并联系到一起
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit, QInputDialog, QApplication, QFrame, QColorDialog, QHBoxLayout, QSizePolicy, QLabel, QFontDialog
from PyQt5.QtGui import QColor

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 输入文字
        self.btn = QPushButton('NAME',self)
        self.btn.move(20,20)
        self.btn.clicked.connect(self.showDialog)
        ## 文本框
        self.le=QLineEdit(self)
        self.le.move(130,22)

        # 选取颜色
        col = QColor(0,0,0)
        self.btn=QPushButton('Color',self)
        self.btn.move(20,60)
        self.btn.clicked.connect(self.showColorWheelDialog)
        ## QFrame
        self.frm = QFrame(self)
        self.frm.setStyleSheet("QWidget{background-color:%s}" % col.name())
        self.frm.setGeometry(130,60,100,100)

        # 选择字体
        btn=QPushButton("Font",self)
        btn.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)
        btn.move(20,175)
        btn.clicked.connect(self.showFontDialog)

        self.lbl=QLabel("Remembrance of things past is <br/>not necessarily the remembrance of things as <br/>they were. <br/> --Marcel Proust",self)
        self.lbl.setGeometry(130,160,300,100)

        # 窗口
        self.setGeometry(300,300,500,400)
        self.setWindowTitle('对话框')
        self.show()

    # 文本对话框
    def showDialog(self):
        text,ok = QInputDialog.getText(self,'input dialog','enter your name:')
        if ok:
            self.le.setText(str(text))
    
    # 颜色盘对话框
    def showColorWheelDialog(self):
        col = QColorDialog.getColor()
        if col.isValid():
            # 用 background也可以
            self.frm.setStyleSheet("background:{0}".format(col.name()))
            # 试着给字体加颜色,bingo!
            self.lbl.setStyleSheet("color:{0}".format(col.name()))
            self.le.setStyleSheet("color:{0}".format(col.name()))
    # 字体对话框
    def showFontDialog(self):
        font,ok=QFontDialog.getFont()
        if ok:
            self.lbl.setFont(font)
            self.le.setFont(font)


if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

文件对话框
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QFileDialog, QTextEdit
from PyQt5.QtGui import QIcon

class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 选择文件
        self.textEdit = QTextEdit() # 文本框
        self.setCentralWidget(self.textEdit) #置中的文本编辑框
        self.statusBar() # 状态栏
        ## 菜单action
        openFileAct = QAction('open',self)
        openFileAct.setShortcut('Ctrl+Q')
        openFileAct.setStatusTip("open new File")
        openFileAct.triggered.connect(self.showFileDialog)
        ## 菜单
        menubar = self.menuBar()
        fileMenu = menubar.addMenu("&File")
        fileMenu.addAction(openFileAct)

        # 窗口
        self.setGeometry(300,300,500,400)
        self.setWindowTitle('对话框')
        self.show()

    # 文件对话框
    def showFileDialog(self):
        fname = QFileDialog.getOpenFileName(self,"open file")
        if fname[0]:
            f=open(fname[0],'r',encoding="UTF-8")

            with f:
                data = f.read()
                self.textEdit.setText(data)

if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

6. 控件

这一节应该会比较枯燥的样子。

滑块QSlider的时候
setPixmap放图片的时候,图片没有缩放,很大,限定区域只是截取了一小块。经群里伍兄指点以及C++API(pyqt好多类没有文档啊)先初始化,再用QPixmap scaled(const QSize &size,...)即可。

self.label = QLabel(self)
qpix=QPixmap(sys.path[0]+'/music2.png').scaled(QSize(30,30))
self.label.setPixmap(qpix)
self.label.setGeometry(150,160,30,30)

懒得找喇叭变化的图标,随便用之前找的音乐图标来稍微替代一下。声音为0则反向,无关紧要的。

控件比较多,也按教程小节分两个文件写吧

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QCheckBox, QPushButton, QFrame, QSlider, QLabel, QProgressBar, QHBoxLayout, QCalendarWidget
from PyQt5.QtCore import Qt, QSize, QBasicTimer, QDate
from PyQt5.QtGui import QColor, QPixmap, QIcon

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        
        # 选择框 QCheckBox
        cb = QCheckBox("show title",self)
        cb.move(20,20)
        #cb.toggle()
        cb.stateChanged.connect(self.changeTitle)
        
        # 切换按钮
        self.col = QColor(0,0,0)
        redb=QPushButton('RED',self)
        redb.setCheckable(True)
        redb.move(20,40)
        redb.clicked[bool].connect(self.setColor) # 将点击信号转换为布尔值,并与函数关联起来

        greenb=QPushButton('GREEN',self)
        greenb.setCheckable(True)
        greenb.move(20,60)
        greenb.clicked[bool].connect(self.setColor)

        blueb=QPushButton('BLUE',self)
        blueb.setCheckable(True)
        blueb.move(20,80)
        blueb.clicked[bool].connect(self.setColor)

        self.square = QFrame(self)
        self.square.setGeometry(150,20,100,100)
        self.square.setStyleSheet("QFrame{background-color:%s}" % self.col.name())
        
        # 滑块 QSlider
        sld = QSlider(Qt.Horizontal,self)
        sld.setFocusPolicy(19)
        sld.setGeometry(20,160,100,30)
        sld.valueChanged[int].connect(self.changeSliderValue)

        self.label = QLabel(self)
        qpix=QPixmap(sys.path[0]+'/music2.png').scaled(QSize(30,30))
        self.label.setPixmap(qpix)
        self.label.setGeometry(150,160,30,30)

        self.labelText = QLabel(self)
        self.labelText.setGeometry(190,160,13,10)

        # 进度条
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(120,200,200,25)
        
        self.btn = QPushButton('Start',self)
        self.btn.move(20,200)
        self.btn.clicked.connect(self.doAction)

        self.timer = QBasicTimer()
        self.step=0

        # 日历
        hbox = QHBoxLayout()
        hbox.addStretch(1)
        
        cal=QCalendarWidget(self)
        cal.setGridVisible(True)
        cal.clicked[QDate].connect(self.showDate)
        hbox.addWidget(cal)
        
        self.lbl=QLabel(self)
        date=cal.selectedDate()
        self.lbl.setText(date.toString())
        hbox.addWidget(self.lbl)

        self.setLayout(hbox)

        # 窗口
        self.setGeometry(300,300,500,400)
        self.setWindowTitle('Control')
        self.show()

    # 通过checkbox改变标题
    def changeTitle(self,state):
        if state==Qt.Checked:
            self.setWindowTitle('QChekcBox')
        else:
            self.setWindowTitle('Control')

    # 通过button改变颜色
    def setColor(self,preesed):
        source=self.sender() # 找到事件信号发送者
        
        if preesed:
            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())

    # 通过QSlider滑动改变
    def changeSliderValue(self,value):
        # 置label为value
        self.labelText.setText(str(value))
        if value==0:
            self.label.setPixmap(QPixmap(sys.path[0]+'/music2.png').scaled(QSize(30,30)))
        elif value>0 and value <=30:
            self.label.setPixmap(QPixmap(sys.path[0]+'/music.png').scaled(QSize(30,30)))
        elif value>30 and value <80:
            self.label.setPixmap(QPixmap(sys.path[0]+'/music.png').scaled(QSize(30,30)))
        else:
            self.label.setPixmap(QPixmap(sys.path[0]+'/music.png').scaled(QSize(30,30)))
    
    # QBasicTimer的start()方法触发的事件,重载
    def timerEvent(self,e):
        print(self.step)
        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):
        print(self.timer.isActive())
        if self.timer.isActive():
            self.timer.stop()
            self.btn.setText("Start")
        else:
            self.timer.start(100,self)
            self.btn.setText("Stop")
        
    # 日期
    def showDate(self,date):
        self.lbl.setText(date.toString())

if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QLineEdit, QHBoxLayout, QFrame, QSplitter, QStyleFactory, QComboBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # QPixmap 上节已体验过,pass
        
        # 行编辑
        self.lbl=QLabel(self)
        self.lbl.move(180,40)
        qle=QLineEdit(self)
        qle.move(20,40)
        qle.textChanged[str].connect(self.onChanged)
        
        # QSplitter 拖拽分割
        hbox = QHBoxLayout()

        topLeft = QFrame(self)
        topLeft.setFrameShape(QFrame.StyledPanel)
        topRight = QFrame(self)
        topRight.setFrameShape(QFrame.StyledPanel)
        bottom = QFrame(self)
        bottom.setFrameShape(QFrame.StyledPanel)

        splitter1 = QSplitter(Qt.Horizontal)
        splitter1.addWidget(topLeft)
        splitter1.addWidget(topRight)

        splitter2 = QSplitter(Qt.Vertical)
        splitter2.addWidget(splitter1)
        splitter2.addWidget(bottom)

        hbox.addWidget(splitter2)
        self.setLayout(hbox)

        # 下拉框
        combo = QComboBox(self)
        for i in range(1,6):
            combo.addItem(str(i))
        combo.move(20,150)
        
        self.comLb = QLabel("",self)
        self.comLb.move(130,157)

        combo.activated[str].connect(self.onActivated)

        # 窗口
        self.setGeometry(300, 300, 400, 300)
        self.setWindowTitle('QLineEdit')
        self.show()

    # 行编辑改变
    def onChanged(self,text):
        self.lbl.setText(text)
        self.lbl.adjustSize()

    # 下拉触发
    def onActivated(self,text):
        self.comLb.setText(text)
        self.comLb.adjustSize()

if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

7. 拖拽

比较难理解,e.buttons()Qt.RightButton的比较都疑惑了好久。原来是枚举。感谢伍兄。

后面正式使用中肯定会在这卡更久,还需加强

import sys
from PyQt5.QtWidgets import QPushButton, QWidget, QLineEdit, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag

class Button(QPushButton):
    def __init__(self,title,parent):
        super().__init__(title,parent)
        self.setAcceptDrops(True)

    # 拖放到按钮上触发的事件
    def dragEnterEvent(self,e):
        if e.mimeData().hasFormat('text/plain'):
            e.accept()
        else:
            e.ignore()

    # 松开鼠标完成拖放时,把文字设置到button上
    def dropEvent(self,e):
        self.setText(e.mimeData().text())

    # 拖动
    def mouseMoveEvent(self,e):
        print('mouseMove')
        if e.buttons() != Qt.RightButton:
            return
        mimeData = QMimeData()
        drag=QDrag(self)
        drag.setMimeData(mimeData)
        #sdrag.setHotSpot(e.pos() - self.rect().topLeft())

        dropAction = drag.exec_(Qt.MoveAction)
        drag.exec_()
    
    # 点击
    def mousePressEvent(self,e):
        print('mousePress')
        #print('press-button ',e.button(),e.buttons())
        super().mousePressEvent(e)
        if e.button() == Qt.LeftButton:
            print('press')


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 简单拖放文字到button组件上
        edit=QLineEdit('',self)
        edit.setDragEnabled(True) # 文本框选定拖动
        edit.move(20,30)
        button=Button("BUTTON",self)
        button.move(190,30)
        
        # 拖放button组件
        self.setAcceptDrops(True)
        self.button = Button('Button',self)

        # 窗口
        self.setWindowTitle("drag")
        self.setGeometry(300,300,300,200)
    
    def dragEnterEvent(self,e):
        e.accept()
    
    def dropEvent(self,e):
        position=e.pos()
        self.button.move(position)

        e.setDropAction(Qt.MoveAction)
        e.accept()

if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    ex.show()
    sys.exit(app.exec_())

8. 绘图

import sys, random
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QFont, QPen, QBrush, QPainterPath
from PyQt5.QtCore import Qt

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 文本涂鸦
        self.text="何须剑道争锋?\n千人指,万人封,可问江湖鼎峰;\n三尺秋水尘不染,天下无双。"

        # 窗口
        self.setGeometry(300,200,400,450)
        self.setWindowTitle('Drawing')
        self.show()

    def paintEvent(self,event):
        qp=QPainter()
        qp.begin(self)

        # 文本
        self.drawTextStr(event,qp)
        # 点
        self.drawPointers(qp)
        # color 矩形
        self.drawRectangles(qp)
        # Qpen画笔,用来画直线曲线椭圆等形状(形状没有试)
        self.drawLines(qp)
        # QBrush 画刷
        self.drawBrushes(qp)
        # 贝塞尔曲线
        self.drawBezierCurve(qp)

        qp.end()

    def drawTextStr(self,event,qp):
        qp.setPen(QColor(68,24,3))
        qp.setFont(QFont('Microsoft Yahei',19))
        qp.drawText(event.rect(),Qt.AlignCenter,self.text)

    def drawPointers(self,qp):
        qp.setPen(Qt.red)
        size=self.size()
        for i in range(3000):
            # 只绘制点到左上角
            x=random.randint(1,size.width()//2) 
            y=random.randint(1,size.height()//2)
            qp.drawPoint(x,y)

    def drawRectangles(self,qp):
        col=QColor()
        col.setNamedColor('#00f')
        qp.setPen(col)

        qp.setBrush(QColor(200, 0, 0))
        qp.drawRect(10, 15, 90, 60)

        qp.setBrush(QColor(0, 200, 0, 150))
        qp.drawRect(130, 15, 90, 60)

        qp.setBrush(QColor(0, 0, 200, 100))
        qp.drawRect(250, 15, 90, 60)

    def drawLines(self,qp):
        pen = QPen(Qt.gray, 3, Qt.SolidLine)

        qp.setPen(pen)
        qp.drawLine(20, 300, 400, 0)

        pen.setStyle(Qt.DashLine)
        qp.setPen(pen)
        qp.drawLine(20, 320, 400, 0)

        pen.setStyle(Qt.DashDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 340, 400, 0)

        pen.setStyle(Qt.DotLine)
        qp.setPen(pen)
        qp.drawLine(20, 360, 400, 40)

        pen.setStyle(Qt.DashDotDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 380, 400, 40)

        pen.setStyle(Qt.CustomDashLine)
        pen.setDashPattern([1, 4, 5, 4])
        qp.setPen(pen)
        qp.drawLine(20, 400, 400, 40)

    def drawBrushes(self,qp):
        # 上面的Pen会影响这里的边框
        brush = QBrush(Qt.Dense1Pattern)
        qp.setBrush(brush)
        qp.drawRect(20,200,40,90)

        qp.setPen(QPen(Qt.red, 2, Qt.SolidLine)) # 重新设置Pen
        brush.setStyle(Qt.Dense6Pattern)
        qp.setBrush(brush)
        qp.drawRect(70,200,40,90)

    def drawBezierCurve(self,qp):
        path=QPainterPath()
        path.moveTo(120,200)
        path.cubicTo(120,200,200,350,350,120)
        path.cubicTo(350,120,200,400,300,400)
        path.cubicTo(300,400,200,400,120,200)
        qp.drawPath(path)

if __name__ == '__main__':
    app=QApplication(sys.argv)
    ex=Example()
    sys.exit(app.exec_())

比较耗费内存,还是低级绘图呢,图中这么多,都吃10M内存了

9. 自定义控件

这一小节没有敲多少,只写了一点注释

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QSlider, QApplication, \
                            QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen

class Communicate(QObject):
    '''
        自定义updateBW信号
    '''
    updateBW =pyqtSignal(int)

class BurningWidget(QWidget):
    def __init__(self):
        super().__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):
        '''
        更新窗口
        每次更改窗口大小,切换窗口,都会触发;单纯移动不会出触发;
        移动里面Slider也不会主动触发,所以Example.changeValue()里主动调用控件的repaint或者update,以重新绘制
        '''
        #print("--update show --")
        qp=QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()

    def drawWidget(self,qp):
        '''
            自定义控件主要的核心绘制
        '''
        #(700,750)区间为烧毁范围
        MAX_CAPACITY=700
        OVER_CAPACITY=750

        font = QFont('Serif',7,QFont.Light)
        qp.setFont(font)

        # 当前窗口大小,step为宽度的1/10,刻度值self.num分割这十份宽
        size=self.size()
        w=size.width()
        h=size.height()
        step=int(round(w/10))
        #print("step:",step)

        # till当前值对应的控件渲染宽度,full正常烧录能达到的最大宽
        till = int(((w / OVER_CAPACITY) * self.value))
        full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
        #print(self.value,till,full)
        if self.value >= MAX_CAPACITY:
            # 当值大于最大MAX,则将MAX部分的全部渲染(宽度为full)
            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, full, h)
            # 其他部分,为烧毁的部分,用另一个颜色渲染
            qp.setPen(QColor(255, 175, 175))
            qp.setBrush(QColor(255, 175, 175))
            qp.drawRect(full, 0, till-full, h)
        else:
            # 正常烧录
            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, till, h)

        # 画笔设置,画刷无
        pen = QPen(Qt.black, 1, Qt.SolidLine)
        qp.setPen(pen)
        qp.setBrush(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])) 
            # 使用重载函数 void QPainter::drawText(int x, int y, const QString &text) 位置(x,y)处绘制text
            # i为竖线位置,fw为字体宽度(fw/2为字体中间位置),i-fw/2则把数字的左半长度移动到了刻度左边,居中!
            
            j = j + 1

class Example(QWidget):
    
    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):      

        OVER_CAPACITY = 750
        
        # 放置上侧滑块
        sld = QSlider(Qt.Horizontal, self)
        #sld.setFocusPolicy(Qt.NoFocus)
        sld.setRange(1, OVER_CAPACITY)
        sld.setValue(75)
        sld.setGeometry(30, 40, 150, 30)

        # 创建信号和控件
        self.c = Communicate()        
        self.wid = BurningWidget()

        # 将滑块改变与self.changeValue方法绑定,当滑块滑动,执行changValue方法,在方法中,手动调用信号更新,所以下面的信号也会更新
        sld.valueChanged[int].connect(self.changeValue)
        # 将信号更新与控件控件setValue方法绑定,当信号更新时,控件更新
        self.c.updateBW[int].connect(self.wid.setValue)

        # 水平布局,自定义的wid控件将显示在底部
        hbox = QHBoxLayout()
        hbox.addWidget(self.wid)
        vbox = 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):
        # 发送updateBW信号
        self.c.updateBW.emit(value)     

        # 重绘窗口,repaint update均可   
        #self.wid.repaint()
        self.wid.update()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

好啦,结束。算是懂了一点界面知识,开森。最后一节的俄罗斯方块之后再试

github

参考资料,致谢

posted @ 2018-08-07 20:58  姜小豆  阅读(1761)  评论(2编辑  收藏  举报