這個例子相對綜合一些,包括qt的布局,實現無邊框效果,無邊框也就是沒有了窗口的title欄,沒有title欄就不能拖動了,
所以我們進一步講如何實現拖動。通過這邊文章你可以掌握qt的布局,窗口定制,重寫qt方法,QSS樣式,事件信號
先上例子,后面在分析代碼
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import (QApplication, QWidget,QPushButton, QLineEdit,QHBoxLayout, QVBoxLayout,QColorDialog,QInputDialog,QFileDialog) from PyQt5.QtCore import Qt from PyQt5.QtGui import QCursor,QColor class Example(QWidget): def __init__(self): super().__init__() self.style = """ QPushButton{background-color:grey;color:white;} #window{ background:pink; } #test{ background-color:black;color:white; } """ self.setStyleSheet(self.style) self.setWindowFlags(Qt.FramelessWindowHint) self.initUI() def initUI(self): self.setGeometry(300, 300, 300, 220) self.setWindowTitle('test') self.setObjectName("window") btn1 = QPushButton("關閉",self) btn1.clicked.connect(self.close) btn1.setObjectName('test') btn2 = QPushButton("最小化",self) btn2.clicked.connect(self.showMinimized) btn3 = QPushButton("最大化",self) btn3.clicked.connect(self.showMaximized) btn11 = QPushButton("改變背景",self) btn11.clicked.connect(self.showColor) btn22 = QPushButton("選擇文件",self) btn22.clicked.connect(self.showFile) btn33 = QPushButton("對話框",self) btn33.clicked.connect(self.showDialog) self.linet1 = QLineEdit("111111",self) self.linet2 = QLineEdit("ssssssss",self) hbox1 = QHBoxLayout() #水平布局 hbox1.addWidget(btn1) hbox1.addWidget(btn2) hbox1.addWidget(btn3) hbox2 = QHBoxLayout() #水平布局 hbox2.addWidget(btn11) hbox2.addWidget(btn22) hbox2.addWidget(btn33) vbox = QVBoxLayout() #垂直布局 vbox.addLayout(hbox1) vbox.addLayout(hbox2) vbox.addWidget(self.linet1) vbox.addWidget(self.linet2) self.setLayout(vbox) self.show() #重寫三個方法使我們的Example窗口支持拖動,上面參數window就是拖動對象 def mousePressEvent(self, event): if event.button()==Qt.LeftButton: self.m_drag=True self.m_DragPosition=event.globalPos()-self.pos() event.accept() self.setCursor(QCursor(Qt.OpenHandCursor)) def mouseMoveEvent(self, QMouseEvent): if Qt.LeftButton and self.m_drag: self.move(QMouseEvent.globalPos()-self.m_DragPosition) QMouseEvent.accept() def mouseReleaseEvent(self, QMouseEvent): self.m_drag=False self.setCursor(QCursor(Qt.ArrowCursor)) def showColor(self): col = QColorDialog.getColor() if col.isValid(): self.setStyleSheet(self.style+"#window{background:%s}" % col.name()) def showDialog(self): text, ok = QInputDialog.getText(self, '對話框', '請輸入你的名字:') if ok: self.linet1.setText(str(text)) def showFile(self): fname = QFileDialog.getOpenFileName(self, 'Open file', '/home') if fname[0]: f = open(fname[0], 'r') with f: data = f.readline() self.linet1.setText(data) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
運行效果:

上面導入的一大堆類我就不細說了,都是下面需要用到的,在我的上一篇文章中介紹了相關知識,這里只說一下,不要這樣導入
from PyQt5.QtWidgets import *
這樣子會把大量你沒有用到的類都導入進來,占內存,所以盡量按需導入
self.style = """ QPushButton{background-color:grey;color:white;} #window{ background:pink; } #test{ background-color:black;color:white; } """
self.setStyleSheet(self.style)
這就是qt的css樣式,和css基本是一樣的寫法,調用setStyleSheet方法把樣式應用到當前self對象,也就是Example窗口
樣式是可以繼承的,和html/css是一個道理,我這個地方把樣式綁定到頂層窗口,所以下面我添加的button也會去這個style里面去繼承樣式
和css一個道理,你在下面的button上可以再次調用setStyleSheet方法來覆蓋繼承的方法,
每一個窗口元素(比如按鈕,輸入框等等都算)都可以使用setObjectName(idname)來為一個對象取別名,然后我們就可以在樣式里面單獨指定樣式,
比如我們有兩個按鈕,第一個按鈕指定了別名test,看一下上面的寫法是不是和css的id寫法一樣。就像個html中div加了一個id一樣,然后在css文件里面用id來唯一標識樣式
后面的按鈕都沒有指定別名,所以就默認繼承 QPushButton{background-color:grey;color:white;} 樣式,當然你還可以在button上調用setStyleSheet方法覆蓋之前的樣式
比如你在btn2下面加一行 btn2.setStyleSheet("QPushButton{background:green }") 就可以覆蓋以前的樣式了
怎么寫樣式就和css里面的一樣,比如有這些: background,color,border,background-image,width,height等等
掌握了qss就可以寫出各種花式界面了。
self.setWindowFlags(Qt.FramelessWindowHint)
這句話就是去掉窗口的頂部title欄了一級邊框那些,對比一下去掉邊框和沒有去掉的區別


這個圖片好像還看不出無邊框的優越性,假設qq如果加上邊框會是怎么的畫面呢。。。
通常一些工具軟件有無邊框都無所謂,但是對於一些要求界面美觀的軟件來說帶一個操作系統提供的外殼會顯得略丑。。
加上上面那句代碼后邊框已經不見了,等等,以前移動都是鼠標按住頂部的title欄拖動的,現在title欄不見了還怎么拖動呢?
def mousePressEvent(self, event): if event.button()==Qt.LeftButton: self.m_drag=True self.m_DragPosition=event.globalPos()-self.pos() event.accept() self.setCursor(QCursor(Qt.OpenHandCursor)) def mouseMoveEvent(self, QMouseEvent): if Qt.LeftButton and self.m_drag: self.move(QMouseEvent.globalPos()-self.m_DragPosition) QMouseEvent.accept() def mouseReleaseEvent(self, QMouseEvent): self.m_drag=False self.setCursor(QCursor(Qt.ArrowCursor))
這三個方法是窗口對象自帶的,默認情況下他好像什么都沒干,所以我們可以大膽的重寫這三個方法來實現拖動,
我相信看函數名大概也知道什么意思了吧,如果你寫過js的拖動效果其實就不難理解了,
mousePressEvent流程:鼠標左鍵按下,設置拖動標識true,記錄點擊坐標,設置鼠標樣式
mouseMoveEvent流程:判斷當前數遍仍處於按下狀態 ,調用move函數設置當前對標位置,
mouseReleaseEvent流程:釋放拖動標示false
這下可以正常拖動了,等等,以前的最小化,最大化,關閉都在title欄上,現在也不見了,沒有我們就自己實現唄
btn1 = QPushButton("關閉",self) btn1.clicked.connect(self.close) btn1.setObjectName('test') btn2 = QPushButton("最小化",self) btn2.clicked.connect(self.showMinimized) btn3 = QPushButton("最大化",self) btn3.clicked.connect(self.showMaximized)
第一句:btn1 = QPushButton("關閉",self) 定義一個按鈕對象,並指定他的容器是self,也就是放到Example窗口里面,
第二句是關鍵:當btn1被點擊后會產生一個點擊信號,然后把這個信號連接到self.close這個處理函數來處理這次事件
這個三個按鈕連接的處理函數都是內建的函數,分別實現了關閉,最小最大化,
除了連接到qt提供的函數外,也可以連接到我們自己的函數,就像下面這樣
btn11 = QPushButton("改變背景",self) btn11.clicked.connect(self.showColor) btn22 = QPushButton("選擇文件",self) btn22.clicked.connect(self.showFile) btn33 = QPushButton("對話框",self) btn33.clicked.connect(self.showDialog) def showColor(self): col = QColorDialog.getColor() if col.isValid(): self.setStyleSheet(self.style+"#window{background:%s}" % col.name()) def showDialog(self): text, ok = QInputDialog.getText(self, '對話框', '請輸入你的名字:') if ok: self.linet1.setText(str(text)) def showFile(self): fname = QFileDialog.getOpenFileName(self, 'Open file', '/home') if fname[0]: f = open(fname[0], 'r') with f: data = f.readline() self.linet1.setText(data)
事件的作用就是用來發射信號,有些信號默認已經連接到處理函數,現在我們有這樣的需求,就是我們要手動發出一個信號,換句話說就是我們自定義一個事件,
系統本身內部提供了很多事件,比如,click,mousemove,valuechange等等,除了這些我們仍然可以自定義事件,比如我把 鼠標向上移動接着向右移動 定義為一個事件,
這樣我們可以做一些手勢功能,這個要說起來又是一篇文章,就是提到這里吧,后面的文章在專門分析,初學者基本上用不到,
下面還是拋一個簡單的模板列子
import sys from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QMainWindow, QApplication class Communicate(QObject): closeApp = pyqtSignal() class Example(QMainWindow): def __init__(self): super().__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() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
提示:發射信號的對象必須繼承QObject類
說到這里突然發現漏了什么,對,就是布局
hbox1 = QHBoxLayout() #水平布局 hbox1.addWidget(btn1) hbox1.addWidget(btn2) hbox1.addWidget(btn3) hbox2 = QHBoxLayout() #水平布局 hbox2.addWidget(btn11) hbox2.addWidget(btn22) hbox2.addWidget(btn33) vbox = QVBoxLayout() #垂直布局 vbox.addLayout(hbox1) vbox.addLayout(hbox2) vbox.addWidget(self.linet1) vbox.addWidget(self.linet2) self.setLayout(vbox)
在qt中布局有三種,絕對定位,水平布局, 垂直布局, 網格布局
我上面使用的水平布局加垂直布局。通常一個軟件會多種布局混合使用,布局是可以嵌套的。絕對定位是使用moveto函數來確定控件的位置,這種方式就把位置定死了,正常窗口下(非無邊框),用戶可以拖動邊角進行放大窗口,或者點擊最大化,
假設你的按鈕的位置是(100,100),不管窗口變多大,按鈕都是相對於窗口的左上角(0.0)位置,
絕對定位例子:
import sys from PyQt5.QtWidgets import QWidget, QPushButton, QApplication app = QApplication(sys.argv) wg = QWidget() lbl1 = QPushButton('test', wg) lbl1.move(15, 10) wg.setGeometry(300, 300, 250, 150) wg.setWindowTitle('Absolute') wg.show() sys.exit(app.exec_())
如果你的軟件采用無邊框,用戶就無法改變窗口大小了,這種情況下采用絕對定位還是不錯的。
如果你的軟件允許用戶改變大小,那么推薦使用流式布局,也就是水平布局和垂直布局以及網格布局都是流式布局
水平布局 就不需要例子了,最上面的綜合例子就是采用水平布局加垂直布局
水平布局就是把所有的元素按照水平方向平鋪,占滿軟件整個寬度,垂直布局一樣的理解
說一下網格布局:
網格布局就是把你的軟件花分成x*x的表格,比如5*5或者5x8等,然后你把控件插到相應位置就可以了,
網格布局例子:
import sys from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): 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) self.move(300, 150) self.setWindowTitle('Calculator') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
運行效果:

