七、顶层窗口标题、图标及状态设置
前面小节已经学习过了,QWidget及其子类在没有设置父控件时都将作为顶层窗口使用。下列的API即是对顶层窗口的相关设置,即当控件作为子控件时,这些设置不起作用。
API函数 | 参数说明 | 返回值 | 功能作用 |
setWindowOpacity(self, level) | Level:float | None | 设置窗口透明度 |
windowOpacity(self) | None | float | 获取窗口透明度 |
setWindowIcon(self, icon) | Icon:QIcon | None | 设置窗口图标 |
windowIcon(self) | None | QIcon | 获取窗口图标 |
setWindowTitle(self, a0) | a0:str | None | 设置窗口标题 |
windowTitle(self) | None | Str | 获取窗口标题 |
setWindowState(self,state) | State:windowstate state是Qt的枚举类 | None | 设置窗口状态 |
windowState(self) | None | windowstate | 获取窗口状态 |
showFullScreen(self) | None | None | 设置窗口为全屏状态 |
showMaximized(self) | None | None | 设置窗口为最大化状态 |
showMinimized(self) | None | None | 设置窗口为最小化状态 |
showNormal(self) | None | None | 设置窗口为正常状态 |
isFullSreen(self) | None | bool | 返回窗口是否处于全屏状态 |
isMaximized(self) | None | bool | 返回窗口是否处于最大化状态 |
isminimized(self) | None | bool | 返回窗口是否处于最小化状态 |
这其中几个设置方法在前面键盘事件中我们已经用过,这里再学习一下setWindowState( )方法,实际上它的作用是通过不同参数显示全屏、最大、最小及初始态,showFullScreen()等几个方法是他的方便函数。其参数Qt.Windowstate是一个枚举类,具体如下:
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
Qt.WindowState | Qt.WindowNoState | 0 | 窗口未设置状态(正常状态) |
Qt.WindowMinimized | 1 | 窗口最小化(即图标化)。 | |
Qt.WindowMaximized | 2 | 窗口最大化,周围有一个框架 | |
Qt.WindowFullScreen | 3 | 窗口填满整个屏幕,周围没有任何框架。 | |
Qt.WindowActive | 4 | 窗口是活动窗口,即它具有键盘焦点。 |
窗口图标、标题和透明度设置见下图:(下图窗口透明度设置为0.5,透明度值0-1之间)
其中,setWindowIcon()的参数QIcon是图标类,图标类通常使用QPixmap或者图片的路径字符串作为参数。QIcon将在图片处理类里学习,初期可以直接使用图片路径作字符串。
八、顶层类型及标志设置
顶层窗口的类型是当控件作为顶层窗口时,其被指示为那种窗口,并且按照指示窗口的类型显示相应的组件。如,边框、最大、最小化按钮及标题栏等。它是一个枚举类:
枚举常量 | 枚举值 | 功能描述 |
Qt.Widget | 0x00000000 | 这是QWidget的默认类型。这种类型的控件如果有父控件则为子控件,如果它们没有父控件则为独立窗口。 |
Qt.Window | 0x00000001 | 指示该控件是一个窗口,通常带有一个窗口系统框架和一个标题栏,而不管该控件是否有父控件。请注意,如果小部件没有父级,则无法取消设置此标志。 |
Qt.Dialog | 0x00000002 | Window | 大小在给定矩形之外缩放为尽可能小的矩形,同时保持纵横比。 |
Qt.Sheet | 0x00000004 | Window | 指示窗口是 macOS 上的工作表。由于使用工作表意味着窗口模态,因此推荐的方法是使用 QWidget::setWindowModality() 或 QDialog::open() |
Qt.Popup | 0x00000008 | Window | 指示控件是弹出式顶层窗口,即它是模式窗口,但具有适用于弹出菜单的窗口系统框架。 |
Qt.Tool | Popup | Dialog | 指示微件是工具窗口。工具窗口通常是一个小窗口,其标题栏和装饰比平常小,通常用于工具按钮的集合。如果存在父级,则工具窗口将始终位于其顶部。如果没有父级,您也可以考虑使用Qt::WindowStaysOnTopHint。如果窗口系统支持它,则可以用更轻的框架装饰工具窗口。它也可以与Qt::FramelessWindowHint结合使用。 |
Qt.ToolTip | Popup | Sheet | 指示控件为工具提示。 |
Qt.SplashScreen | ToolTip | Dialog | 指示窗口为初始屏幕。这是 QSplashScreen 的默认类型。 |
Qt.SubWindow | 0x00000012 | 指示此小组件是一个子窗口,例如 QMdiSubWindow 小组件。 |
Qt.MSWindowsFixedSizeDialogHint | 0x00000100 | 在 Windows 上边框变为细线条,用于固定大小的对话框。多监视器环境中不能使用此标志 |
Qt.FramelessWindowHint | 0x00000800 | 生成无边框窗口。用户无法通过窗口系统移动无边框窗口或调整其大小。 |
Qt.CustomizeWindowHint | 0x02000000 | 生成的窗口有边框但无标题栏和按钮,不能移动和拖动 |
Qt.WindowTitleHint | 0x00001000 | 为窗口添加标题栏和按钮 |
Qt.WindowSystemMenuHint | 0x00002000 | 添加一个系统目录和关闭按钮 |
Qt.WindowMinimizeButtonHint | 0x00004000 | 添加最小化按钮。 |
Qt.WindowMaximizeButtonHint | 0x00008000 | 添加最大化按钮。 |
Qt.WindowMinMaxButtonsHint | WindowMinimizeButtonHint | WindowMaximizeButtonHint | 添加最小化和最大化按钮 |
Qt.WindowCloseButtonHint | 0x08000000 | 添加关闭按钮。 |
Qt.WindowContextHelpButtonHint | 0x00010000 | 向对话框添加问号按钮 |
Qt.MacWindowToolBarButtonHint | 0x10000000 | 在 macOS 上,添加了一个工具栏按钮(即,位于具有工具栏的窗口右上角的长方形按钮) |
Qt.WindowFullscreenButtonHint | 0x80000000 | 在 macOS 上,添加一个全屏按钮。 |
Qt.BypassGraphicsProxyWidget | 0x20000000 | 防止窗口及其子窗口自动嵌入到 QGraphicsProxyWidget 中(如果父窗口小部件已嵌入)。如果您希望微件始终是桌面上的顶级微件,则无论父微件是否嵌入在场景中,都可以设置此标志。 |
Qt.WindowShadeButtonHint | 0x00020000 | 添加一个阴影按钮来代替最小化按钮(如果基础窗口管理器支持)。 |
这个属性都是通过setWindowFlags( )方法进行设置的。
API函数 | 参数说明 | 返回值 | 功能作用 |
setWindowFlags(type) | type:QtCore.Qt.WindowFlags, QtCore.Qt.WindowType | None | 设置窗口样式和标志 |
windowFlags(self) | None | type:QtCore.Qt.WindowFlags, QtCore.Qt.WindowType | 获取窗口样式和标志 |
example:这个例子是从pyqt的官网转过来的,暂时看不明白没关系。将程序运行起来,主要是看Qt.WindowTypes在组合情况的显示样式
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,
QHBoxLayout, QPushButton, QRadioButton, QTextEdit, QVBoxLayout,
QWidget)
import sys
class PreviewWindow(QWidget):
def __init__(self, parent=None):
super(PreviewWindow, self).__init__(parent)
self.textEdit = QTextEdit()
self.textEdit.setReadOnly(True)
self.textEdit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
closeButton = QPushButton("&Close")
closeButton.clicked.connect(self.close)
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(closeButton)
self.setLayout(layout)
self.setWindowTitle("Preview")
def setWindowFlags(self, flags):
super(PreviewWindow, self).setWindowFlags(flags)
flag_type = (flags & Qt.WindowType_Mask)
if flag_type == Qt.Window:
text = "Qt.Window"
elif flag_type == Qt.Dialog:
text = "Qt.Dialog"
elif flag_type == Qt.Sheet:
text = "Qt.Sheet"
elif flag_type == Qt.Drawer:
text = "Qt.Drawer"
elif flag_type == Qt.Popup:
text = "Qt.Popup"
elif flag_type == Qt.Tool:
text = "Qt.Tool"
elif flag_type == Qt.ToolTip:
text = "Qt.ToolTip"
elif flag_type == Qt.SplashScreen:
text = "Qt.SplashScreen"
else:
text = ""
if flags & Qt.MSWindowsFixedSizeDialogHint:
text += "\n| Qt.MSWindowsFixedSizeDialogHint"
if flags & Qt.X11BypassWindowManagerHint:
text += "\n| Qt.X11BypassWindowManagerHint"
if flags & Qt.FramelessWindowHint:
text += "\n| Qt.FramelessWindowHint"
if flags & Qt.WindowTitleHint:
text += "\n| Qt.WindowTitleHint"
if flags & Qt.WindowSystemMenuHint:
text += "\n| Qt.WindowSystemMenuHint"
if flags & Qt.WindowMinimizeButtonHint:
text += "\n| Qt.WindowMinimizeButtonHint"
if flags & Qt.WindowMaximizeButtonHint:
text += "\n| Qt.WindowMaximizeButtonHint"
if flags & Qt.WindowCloseButtonHint:
text += "\n| Qt.WindowCloseButtonHint"
if flags & Qt.WindowContextHelpButtonHint:
text += "\n| Qt.WindowContextHelpButtonHint"
if flags & Qt.WindowShadeButtonHint:
text += "\n| Qt.WindowShadeButtonHint"
if flags & Qt.WindowStaysOnTopHint:
text += "\n| Qt.WindowStaysOnTopHint"
if flags & Qt.WindowStaysOnBottomHint:
text += "\n| Qt.WindowStaysOnBottomHint"
if flags & Qt.CustomizeWindowHint:
text += "\n| Qt.CustomizeWindowHint"
self.textEdit.setPlainText(text)
class ControllerWindow(QWidget):
def __init__(self):
super(ControllerWindow, self).__init__()
self.previewWindow = PreviewWindow(self)
self.createTypeGroupBox()
self.createHintsGroupBox()
quitButton = QPushButton("&Quit")
quitButton.clicked.connect(self.close)
bottomLayout = QHBoxLayout()
bottomLayout.addStretch()
bottomLayout.addWidget(quitButton)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.typeGroupBox)
mainLayout.addWidget(self.hintsGroupBox)
mainLayout.addLayout(bottomLayout)
self.setLayout(mainLayout)
self.setWindowTitle("Window Flags")
self.updatePreview()
def updatePreview(self):
flags = self.windowFlags()
if self.windowRadioButton.isChecked():
flags = Qt.Window
elif self.dialogRadioButton.isChecked():
flags = Qt.Dialog
elif self.sheetRadioButton.isChecked():
flags = Qt.Sheet
elif self.drawerRadioButton.isChecked():
flags = Qt.Drawer
elif self.popupRadioButton.isChecked():
flags = Qt.Popup
elif self.toolRadioButton.isChecked():
flags = Qt.Tool
elif self.toolTipRadioButton.isChecked():
flags = Qt.ToolTip
elif self.splashScreenRadioButton.isChecked():
flags = Qt.SplashScreen
if self.msWindowsFixedSizeDialogCheckBox.isChecked():
flags |= Qt.MSWindowsFixedSizeDialogHint
if self.x11BypassWindowManagerCheckBox.isChecked():
flags |= Qt.X11BypassWindowManagerHint
if self.framelessWindowCheckBox.isChecked():
flags |= Qt.FramelessWindowHint
if self.windowTitleCheckBox.isChecked():
flags |= Qt.WindowTitleHint
if self.windowSystemMenuCheckBox.isChecked():
flags |= Qt.WindowSystemMenuHint
if self.windowMinimizeButtonCheckBox.isChecked():
flags |= Qt.WindowMinimizeButtonHint
if self.windowMaximizeButtonCheckBox.isChecked():
flags |= Qt.WindowMaximizeButtonHint
if self.windowCloseButtonCheckBox.isChecked():
flags |= Qt.WindowCloseButtonHint
if self.windowContextHelpButtonCheckBox.isChecked():
flags |= Qt.WindowContextHelpButtonHint
if self.windowShadeButtonCheckBox.isChecked():
flags |= Qt.WindowShadeButtonHint
if self.windowStaysOnTopCheckBox.isChecked():
flags |= Qt.WindowStaysOnTopHint
if self.windowStaysOnBottomCheckBox.isChecked():
flags |= Qt.WindowStaysOnBottomHint
if self.customizeWindowHintCheckBox.isChecked():
flags |= Qt.CustomizeWindowHint
self.previewWindow.setWindowFlags(flags)
pos = self.previewWindow.pos()
if pos.x() < 0:
pos.setX(0)
if pos.y() < 0:
pos.setY(0)
self.previewWindow.move(pos)
self.previewWindow.show()
def createTypeGroupBox(self):
self.typeGroupBox = QGroupBox("Type")
self.windowRadioButton = self.createRadioButton("Window")
self.dialogRadioButton = self.createRadioButton("Dialog")
self.sheetRadioButton = self.createRadioButton("Sheet")
self.drawerRadioButton = self.createRadioButton("Drawer")
self.popupRadioButton = self.createRadioButton("Popup")
self.toolRadioButton = self.createRadioButton("Tool")
self.toolTipRadioButton = self.createRadioButton("Tooltip")
self.splashScreenRadioButton = self.createRadioButton("Splash screen")
self.windowRadioButton.setChecked(True)
layout = QGridLayout()
layout.addWidget(self.windowRadioButton, 0, 0)
layout.addWidget(self.dialogRadioButton, 1, 0)
layout.addWidget(self.sheetRadioButton, 2, 0)
layout.addWidget(self.drawerRadioButton, 3, 0)
layout.addWidget(self.popupRadioButton, 0, 1)
layout.addWidget(self.toolRadioButton, 1, 1)
layout.addWidget(self.toolTipRadioButton, 2, 1)
layout.addWidget(self.splashScreenRadioButton, 3, 1)
self.typeGroupBox.setLayout(layout)
def createHintsGroupBox(self):
self.hintsGroupBox = QGroupBox("Hints")
self.msWindowsFixedSizeDialogCheckBox = self.createCheckBox("MS Windows fixed size dialog")
self.x11BypassWindowManagerCheckBox = self.createCheckBox("X11 bypass window manager")
self.framelessWindowCheckBox = self.createCheckBox("Frameless window")
self.windowTitleCheckBox = self.createCheckBox("Window title")
self.windowSystemMenuCheckBox = self.createCheckBox("Window system menu")
self.windowMinimizeButtonCheckBox = self.createCheckBox("Window minimize button")
self.windowMaximizeButtonCheckBox = self.createCheckBox("window maximize button")
self.windowCloseButtonCheckBox = self.createCheckBox("window close button")
self.windowContextHelpButtonCheckBox = self.createCheckBox("Window context help button")
self.windowShadeButtonCheckBox = self.createCheckBox("Window shade button")
self.windowStaysOnTopCheckBox = self.createCheckBox("Window stays on top")
self.windowStaysOnBottomCheckBox = self.createCheckBox("Window stays on bottom")
self.customizeWindowHintCheckBox = self.createCheckBox("Customize window")
layout = QGridLayout()
layout.addWidget(self.msWindowsFixedSizeDialogCheckBox, 0, 0)
layout.addWidget(self.x11BypassWindowManagerCheckBox, 1, 0)
layout.addWidget(self.framelessWindowCheckBox, 2, 0)
layout.addWidget(self.windowTitleCheckBox, 3, 0)
layout.addWidget(self.windowSystemMenuCheckBox, 4, 0)
layout.addWidget(self.windowMinimizeButtonCheckBox, 0, 1)
layout.addWidget(self.windowMaximizeButtonCheckBox, 1, 1)
layout.addWidget(self.windowCloseButtonCheckBox, 2, 1)
layout.addWidget(self.windowContextHelpButtonCheckBox, 3, 1)
layout.addWidget(self.windowShadeButtonCheckBox, 4, 1)
layout.addWidget(self.windowStaysOnTopCheckBox, 5, 1)
layout.addWidget(self.windowStaysOnBottomCheckBox, 6, 1)
layout.addWidget(self.customizeWindowHintCheckBox, 5, 0)
self.hintsGroupBox.setLayout(layout)
def createCheckBox(self, text):
checkBox = QCheckBox(text)
checkBox.clicked.connect(self.updatePreview)
return checkBox
def createRadioButton(self, text):
button = QRadioButton(text)
button.clicked.connect(self.updatePreview)
return button
if __name__ == '__main__':
app = QApplication(sys.argv)
controller = ControllerWindow()
controller.show()
sys.exit(app.exec())
九、QWidget的交互状态
API函数 | 参数说明 | 返回值 | 功能作用 |
activateWindow(self) | None | None | 将包含此构件的顶级构件设置为活动窗口。 |
isActiveWindow(self) | None | None | 查看此窗口是否为活动窗口。 |
setEnabled(self, arg__1) | arg_1:bool | None | 设置控件是否激活 |
isEnabled(self) | None | bool | 查看控件是否激活 |
isEnabledTo(self, arg__1) | arg_1:QWidget | bool | 如果启用了同窗口的父控件,则此小部件将被启用,则返回true;否则返回false |
isWindow(self) | None | bool | 查看部件是否为独立窗口/顶层窗口 |
window(self) | Noen | QWidget | 返回控件所在的顶层窗口 |
setWindowModified(self, arg__1) | arg_1:bool | None | 设置窗口是否显示有未保存的更改 |
isWindowModified(self) | None | bool | 查看窗口是否是否显示有未保存的更改 |
所谓交互状态是指控件是否为活动窗口、是否可以被使用等属性。
活动状态的窗口具有获取焦点的特征,与鼠标点击窗口的效果一致。但需要注意的是,不是放在底层窗口就不活动。
setEnable()和isEnable()这两个方法主要体现在其子类控件上,如按钮的如果isEnable()为Flase将被禁用,无法点击。
isEnableTo()方法与上面两个方法有一定的区别,它主要是考察的同窗口下的父控件。
最后,setWindowModified()方法的用法是,标识窗口内容是否已被编辑,即是否进行了修改。但要使用该方法,前提是需调用setWidowTitle()方法,且其参数str中文本前要加上“[*]”。
十、QWidget的信息提示
QWidget的信息提示主要是指控件状态提示信息,控件的气泡提示以及“?”这个帮助按钮。控件的状态提示信息通常是在窗口的状态栏显示,控件的状态栏是QStatusBar的实例,将在QMainWindow章节学习。控件的气泡提示是当鼠标停留在控件上时显示,而“?”帮助按钮提示信息,需要窗口带有帮助按钮,且点击“?”后再点击相应控件。
API函数 | 参数说明 | 返回值 | 功能作用 |
setStatusTip(self, arg__1) | arg_1:str | None | 设置提示信息,再状态栏显示 |
statusTip(self) | None | str | 获取将要在状态栏显示的提示信息 |
setToolTip(self, arg__1) | arg_1:str | None | 设置气泡提示 |
toolTip(self) | None | str | 获取气泡提示 |
setToolTipDuration(self, msec) | msec:int (毫秒) | None | 设置提示气泡的存在时长 |
toolTipDuration(self) | None | int | 获取提示气泡的存在时长 |
setWhatsThis(self, arg__1) | arg_1:str | None | 设置帮助信息 |
whatsThis(self) | None | str | 获取帮助信息 |
example:
from PySide6.QtWidgets import QApplication,QWidget,QPushButton,QLabel,QStatusBar
from PySide6.QtCore import Qt
import sys
class Mytest(QWidget):
def __init__(self):
super(Mytest, self).__init__()
self.setWindowTitle("QWidget信息提示")
self.resize(500,500)
#改变窗口样式为信息提示窗口
self.setWindowFlags(Qt.WindowType.WindowContextHelpButtonHint | Qt.WindowType.WindowCloseButtonHint)
# 创建一个状态栏
self.statusBar = QStatusBar(self)
self.statusBar.setStyleSheet("background:green")
self.statusBar.resize(self.width(),40)
self.statusBar.move(0,460)
# 设置状态信息
self.setStatusTip("我被鼠标点了")
# 创建一个按钮
btn = QPushButton("测试按钮",self)
# 设置按钮提示,在旁边气泡提示
btn.setToolTip("你要点我吗?")
# 获取气泡提示信息
print(btn.toolTip())
# 设置气泡提示时长(毫秒)
btn.setToolTipDuration(2000)
# 获取气泡提示信息
print(btn.toolTipDuration())
label = QLabel(self)
label.setText("标签")
label.move(100, 100)
# 设置标签的这是啥提示,当点击窗口上方“?”时,鼠标
# 改变为带问号样式,再点击标签就会显示设置的提示信息
label.setWhatsThis("这是啥?你说这是啥!")
# 获取这是啥提示的信息内容
print(label.whatsThis())
def mousePressEvent(self, event):
super(Mytest, self).mousePressEvent(event)
# 鼠标点击时,将状态信息展示在状态栏
# 状态栏的showMessage()方法第一个参数是需要展示的信息,第二个是消息停留时间ms
self.statusBar.showMessage(self.statusTip(),2000)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Mytest()
main.show()
sys.exit(app.exec())
运行结果:
20240914-151431
十一、QWidget的尺寸及尺寸策略
因本小节的内容对初学者相对不友好,故将其放置再布局管理器章节。
十二、QWidget的焦点获取及焦点策略
1.焦点获取及焦点策略的相关API
API函数 | 参数说明 | 返回值 | 功能作用 |
setFocus(self) | None | None | 如果控件或其父窗口是活动窗口,将焦点设置给此控件。 |
setFocus(self, reason) | reason: Qt.FocusReason) 参数为Qt枚举类 | None | 如果控件或其父窗口是活动窗口,将焦点设置给此控件。 reason参数用于解释导致焦点获取的原因。 |
hasFocus(self) | None | bool | 查看控件是否拥有焦点 |
focusWidget(self) | None | QWidget | 获取拥有焦点的控件 |
setFocusPolicy(self, policy) | policy: Qt.FocusPolicy 参数为Qt枚举类 | None | 设置控件的焦点策略 |
focusPolicy(self) | None | Qt.FocusPolicy | 获取控件的焦点策略 |
setFocusProxy(self, arg__1) | arg_1:QWidget | None | 将小部件的焦点代理设置为小部件w。如果w为None,则函数会将此小部件重置为没有焦点代理。 |
focusProxy(self) | None | QWidget | 获取小部件焦点代理 |
clearFocus(self) | None | None | 清除控件焦点 |
setTabOrder(self, arg__1, arg__2) | arg_1:QWidget arg_2:QWidget | None | 设置在使用tab键时控件获取焦点顺序。将W2放在W1之后。 |
focusNextChild(self) | None | bool | 向后查找一个新的小部件,以使键盘聚焦于Tab,如果它能找到新小部件,则返回true,如果不能,则返回false。 |
focusPreviousChild(self) | None | bool | 向前查找一个新的小部件,以使键盘聚焦于Tab,如果它能找到新小部件,则返回true,如果不能,则返回false。 |
focusNextPrevChild(self, next) | next:bool | bool | 查找一个新的小部件以使键盘聚焦,适用于Tab和Shift+Tab,如果可以找到新小部件,则返回true,如果不能找到,则返回false。如果next为true,则此函数向前搜索,如果next是false,则向后搜索。 |
nextInFocusChain(self) | None | QWidget | 返回此控件焦点链中的下一个控件 |
2.焦点概念及获取焦点
所谓获取焦点就是激活控件,能够被键盘输入的控件,如单行文本框(QLineEdit)、多行文本框(QTextEdit)等激活后会光标闪烁,而对于按钮等鼠标输入的控件,获取焦点后通常会显示焦点边框或者高亮。
20240914-162649
上面的例子我们创建了两个单行文本框,两个按钮,通过Tab键切换焦点,可以直观的理解焦点的含义。如果我们需要在编程过程中让某一个控件获取焦点,则需要调用下列方法:
3.活动窗口、顶层窗口、焦点窗口的概念
所谓的顶层窗口就是在多个窗口堆叠时,最上面的窗口;而活动窗口则是可与用户交互的窗口;焦点窗口是获得焦点可以进行鼠标、键盘操作的窗口。
前面的层级关系中,我们也看到了活动窗口未必是顶层窗口,但通常来说活动窗口是自动获得键盘焦点的,也是焦点窗口。
焦点窗口的必须条件是窗口父控件或其子控件可获得焦点。比如一个QLabel做为顶层窗口,即使他是活动的,但也无法获取焦点。但,只要是QWidget对象的继承类控件都可以通过修改其焦点策略从而获取焦点。
还有就是,控件获取焦点后未必有特殊的外观表现,比如QLabel,它获取焦点后不会如QLineEdit一样有光标闪烁。而且,我们还要区分,焦点和光标并不是一个东西,QLineEdit的光标闪烁只是其获取焦点后激活了光标。
4.焦点策略与焦点代理
焦点策略理解起来比较简单,从其枚举常量的表述我们就可以看出它是控件获取焦点方式。比如,TabFocus就是通过按Tab键切换获取焦点,ClickFocus是通过鼠标点击而获取焦点。
而焦点代理的意思就是,当控件A获取焦点时,我们设置另一个控件B做为焦点代理,代替控件A处理焦点事件。比如setFocus、focusInEvent、focusOutEvent等均有代理控件B处理。
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
Qt.FocusReason | MouseFocusReason | 0 | 发生了鼠标操作 |
TabFocusReason | 1 | 按下了Tab键 | |
BacktabFocusReason | 2 | 发生了后退选择。如shift+Tab | |
ActiveWindowFocusReason | 3 | 窗口系统使此窗口处于活动或非活动状态。 | |
PopupFocusReason | 4 | 应用程序打开/关闭了一个弹出窗口,该弹出窗口抓取/释放了焦点 | |
ShortcutFocusReason | 5 | 用户键入了标签的快捷方式 | |
MenuBarFocusReason | 6 | 菜单栏成为焦点 | |
OtherFocusReason | 7 | 其他原因。 | |
Qt.FocusPolicy | TabFocus | 0 | 控件通过 Tab 接受焦点。 |
ClickFocus | 1 | 控件通过单击接受焦点。 | |
StrongFocus | 2 | 控件通过 Tab 键和单击接受焦点。在 macOS 上,这也将指示小部件在“文本/列表焦点模式”时接受选项卡焦点。 | |
WheelFocus | 3 | 控件通过 Tab 键和单击以及使用鼠标滚轮接受焦点 | |
NoFocus | 4 | 小组件不接受焦点 |
example:
from PySide6.QtWidgets import QApplication,QWidget,QLineEdit,QLabel,QVBoxLayout
from PySide6.QtCore import Qt
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.resize(500,500)
self.setWindowTitle("焦点策略与焦点代理示例")
self.line1 = QLineEdit(self)
self.line1.move(200, 150)
self.line2 = QLineEdit(self)
self.line2.move(200, 250)
self.lab = QLabel(self)
self.lab.setStyleSheet("background:red")
self.lab.resize(self.width(),100)
self.lab.move(0,350)
# 查看lab的焦点策略
print(self.lab.focusPolicy())
# 将lab的焦点策略设置为鼠标点击获取
self.lab.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
# 设置lab的焦点代理为line2
self.lab.setFocusProxy(self.line2)
if __name__ == '__main__':
App = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec())
运行结果:
20240914-172319
控制台输出结果:
FocusPolicy.NoFocus
上面的里中,对第一个于QLineEdit来说,他是自动获得焦点的。所以,我们通过将不接受焦点的lab的焦点策略改为鼠标点击,而第二个QlineEdit作为其焦点代理,形成结果为点击lab,self.line2获取焦点。
5.焦点链
Pyside除了把控件对象组织成树状结构外,还把这些控件组织成了一个双向链表结构,这个链表叫做焦点链。焦点链上每个节点代表一个控件,默认情况下,控件在焦点链上的先后位置,与用户把控件添加到窗口的先后顺序有关,越早添加到窗口上的控件,其在焦点链中的位置越靠前。
通过按Tab或者Shift+Tab,可以实现焦点在各个控件之间循环移动。焦点移动顺序与焦点链相关,它的移动规律如下:
1.点击Tab键,焦点链指针向后移动,直至碰到第一个FocusPolicy为TabFocus的窗口,并设置该控件为焦点控件;
2.点击Shift+Tab,焦点链指针向前移动,直至碰到第一个FocusPolicy为TabFocus的控件,并设置该窗口为焦点控件。
from PySide6.QtWidgets import QApplication,QWidget,QLineEdit,\
QGridLayout,QVBoxLayout,QPushButton
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.resize(500,500)
self.windowTitle("焦点链示例")
self.line1 = QLineEdit()
self.line2 = QLineEdit()
self.line3 = QLineEdit()
self.line4 = QLineEdit()
self.btn1 = QPushButton("停止计时器")
self.btn2 = QPushButton("查看焦点链节点控件")
self.btn3 = QPushButton("改变焦点链节点顺序")
self.btn4 = QPushButton("自定义焦点链")
self.line1.setObjectName("line1")
self.line2.setObjectName("line2")
self.line3.setObjectName("line3")
self.line4.setObjectName("line4")
self.btn1.setObjectName("btn1")
self.btn2.setObjectName("btn2")
self.btn3.setObjectName("btn3")
self.btn4.setObjectName("btn4")
self.setObjectName("window")
# 创建子布局,并将按钮添加到子布局中
child_lay = QGridLayout()
child_lay.addWidget(self.btn1, 0, 0)
child_lay.addWidget(self.btn2, 0, 1)
child_lay.addWidget(self.btn3, 1, 0)
child_lay.addWidget(self.btn4, 1, 1)
# 创建主布局,将单行文本框和子布局添加到主布局中
# 布局这部分还没学,不用去理解,只要知道他们将控件排列整齐就行了
lay = QVBoxLayout(self)
lay.addWidget(self.line1)
lay.addWidget(self.line2)
lay.addWidget(self.line3)
lay.addWidget(self.line4)
lay.addLayout(child_lay)
# 创建一个临时定时器
self.id = self.startTimer(2000)
self.btn1.clicked.connect(self.stop_timer)
self.btn2.clicked.connect(self.get_all_focusWidget)
self.btn3.clicked.connect(self.changeFocus)
self.btn4.clicked.connect(self.custom_focus)
def stop_timer(self):
# 停止定时器
print("------------停止计时器-----------", end="\n\n")
self.killTimer(self.id)
self.line1.setFocus()
def get_all_focusWidget(self):
# 查看程序运行后焦点链
self.line1.setFocus()
first = self.focusWidget()
print("当前窗口的焦点链为:")
self.get_current_focusOrder()
def changeFocus(self):
# 部分改变焦点链的情况
self.line3.setFocus()
self.setTabOrder(self.line3, self.line1)
self.setTabOrder(self.line1, self.line4)
self.setTabOrder(self.line4, self.line3)
print("更改后的焦点链为:")
self.get_current_focusOrder()
def custom_focus(self):
# 完整的改变焦点链的顺序
self.line3.setFocus()
self.setTabOrder(self.line3, self.line1)
self.setTabOrder(self.line1, self.line4)
self.setTabOrder(self.line4, self.line2)
self.setTabOrder(self.line2, self.btn1)
self.setTabOrder(self.btn1, self.btn3)
self.setTabOrder(self.btn3, self.btn2)
self.setTabOrder(self.btn2, self.btn4)
self.setTabOrder(self.btn4, self)
self.setTabOrder(self, self.line1)
print("自定义后的焦点链为:")
self.get_current_focusOrder()
def get_current_focusOrder(self):
# 获取焦点链方法,供上述方法调用
first = self.focusWidget()
print(first.objectName(), end="-->")
for i in range(len(self.children()) + 2):
print(first.nextInFocusChain().objectName(), end="-->")
first = first.nextInFocusChain()
print("")
def timerEvent(self, event):
super(Window, self).timerEvent(event)
# 重写计时器时间,获取计时器调用时焦点获取的情况
print("-----------计时器启动-----------")
print("此时获取焦点的控件是{}".format(self.focusWidget().objectName()))
print("此时line1是否获取焦点,{}".format(self.line1.hasFocus()))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
# 窗口显示后查看line1是否获取焦点
print("程序开始运行,获取焦点的控件是{}".format(window.focusWidget()))
print("程序开始运行,line1是否获取焦点,{}".format(window.line1.hasFocus()))
sys.exit(app.exec())
运行结果:
20240914-203243
从控制台的输出结果可以看出,虽然line1会自动获取焦点。但是在初始化状态时,并没有控件获取焦点。待窗口显示完成后,才将焦点设置给line1。
焦点链中各节点间顺序的更改通过setTabOrder()、focusNextChild(self)、focusPreviousChild(self)、focusNextPreviousChild(self)等方法进行更改。这几个方法不再举例。