PyQt之佈局&無邊框&訊號
這個例子相對綜合一些,包括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) fromPyQt5.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_())
執行效果: