python - pyqt5 QMdiArea 多个活动子窗口
问题描述
在 QMdiArea 中,我们可以选择(指向)激活子窗口。在我的应用程序中,我想选择多个子窗口(可能使用“Ctrl”按钮)并将它们设置为活动窗口(> = 2 个子窗口)并为它们创建一个列表指针。我正在尝试同时获取多个子窗口的指针。是的,activeSubWindow() 只提供一个窗口。但我想知道是否可以使用键盘上的“Ctrl”按钮来选择两个子窗口并打印指向这些子窗口的指针。这个想法是同时获取每个子窗口(例如TextEditor)内的小部件以执行后续任务,例如比较
from PyQt5.QtWidgets import QApplication, QMainWindow, QMdiArea, QAction, QMdiSubWindow, QTextEdit
import sys
class MDIWindow(QMainWindow):
count = 0
def __init__(self):
super().__init__()
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.addAction("cascade")
file.addAction("Tiled")
file.addAction("selected_subwindows")
file.triggered[QAction].connect(self.WindowTrig)
self.setWindowTitle("MDI Application")
def WindowTrig(self, p):
if p.text() == "New":
MDIWindow.count = MDIWindow.count + 1
sub = QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle("Sub Window" + str(MDIWindow.count))
self.mdi.addSubWindow(sub)
sub.show()
if p.text() == "cascade":
self.mdi.cascadeSubWindows()
if p.text() == "Tiled":
self.mdi.tileSubWindows()
if p.text()=="selected_subwindows":
"""I want to select multiple subwindows and and set as activate
windows with the "Ctrl" button and return a points fot all active windows"""
print("active windows: ", self.mdi.activeSubWindow())
app = QApplication(sys.argv)
mdi =MDIWindow()
mdi.show()
app.exec_()
解决方案
就像正常的窗口处理一样,即使在 MDI 区域中也不可能有多个活动的子窗口。
为了实现“多选”系统,您需要跟踪子窗口的激活状态,这可能很棘手。
子窗口可以通过不同的方式激活:
- 通过单击其标题栏(包括其任何按钮);
- 通过单击其包含的小部件;
- 通过以编程方式激活它
setActiveSubWindow()
(类似于从任务栏中选择普通窗口);
虽然 Qt 提供了aboutToActivate
信号,但它并不总是可靠的:即使顶层窗口获得焦点,它也总是会发出,因此没有直接的方法可以知道激活的原因。
信号也是如此(在状态改变后windowStateChanged
发出)。
对于您的情况,最好的方法主要是基于mousePressEvent
子窗口的实现,同时还要考虑窗口状态的变化,因为每当激活以任何其他方式发生变化时,您都需要跟踪当前的活动窗口(通过单击小部件或使用setActiveSubWindow()
.
由于鼠标事件是在窗口激活更改后处理的,因此正确的解决方案是创建一个将延迟(计划)发射的信号,以便了解激活是否实际上是通过在子窗口上按下鼠标按钮实现的(不在子小部件上),最后检查是否Ctrl同时按下了键。
请注意,以下代码非常基础,您可能需要进行一些调整。例如,它不考虑最小化窗口的激活(与普通窗口不同,子窗口即使被最小化也可能是活动的),也不考虑单击任何窗口按钮时的激活。
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class SubWindow(QMdiSubWindow):
activated = pyqtSignal(object, bool)
ctrlPressed = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
self.windowStateChanged.connect(self.delayActivated)
self.activatedTimer = QTimer(
singleShot=True, interval=1, timeout=self.emitActivated)
def delayActivated(self, oldState, newState):
# Activation could also be triggered for a previously inactive top
# level window, but the Ctrl key might still be handled by the child
# widget, so we should always assume that the key was not pressed; if
# the activation is done through a mouse press event on the subwindow
# then the variable will be properly set there.
# Also, if the window becomes inactive due to programmatic calls but
# *after* a mouse press event, the variable has to be reset anyway.
self.ctrlPressed = False
if newState & Qt.WindowActive:
self.activatedTimer.start()
elif not newState and self.activatedTimer.isActive():
self.activatedTimer.stop()
def emitActivated(self):
self.activated.emit(self, self.ctrlPressed)
self.ctrlPressed = False
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.ctrlPressed = event.modifiers() & Qt.ControlModifier
self.activatedTimer.start()
super().mousePressEvent(event)
class MDIWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("MDI Application")
self.activeWindows = []
activeContainer = QWidget()
activeLayout = QVBoxLayout(activeContainer)
activeLayout.setContentsMargins(0, 0, 0, 0)
self.activeList = QListWidget()
# Note: the following "monkey patch" is only for educational purposes
# and done in order to keep the code short, you should *not* normally
# do this unless you really know what you're doing.
self.activeList.sizeHint = lambda: QSize(150, 256)
activeLayout.addWidget(self.activeList)
self.compareBtn = QPushButton('Compare', enabled=False)
activeLayout.addWidget(self.compareBtn)
self.activeDock = QDockWidget('Selected windows')
self.activeDock.setWidget(activeContainer)
self.addDockWidget(Qt.LeftDockWidgetArea, self.activeDock)
self.activeDock.setFeatures(self.activeDock.NoDockWidgetFeatures)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
fileMenu = bar.addMenu("File")
self.newAction = fileMenu.addAction("New")
self.cascadeAction = fileMenu.addAction("Cascade")
self.tileAction = fileMenu.addAction("Tiled")
self.compareAction = fileMenu.addAction("Compare subwindows")
fileMenu.triggered.connect(self.menuTrigger)
self.compareBtn.clicked.connect(self.compare)
def menuTrigger(self, action):
if action == self.newAction:
windowList = self.mdi.subWindowList()
if windowList:
count = windowList[-1].index + 1
else:
count = 1
sub = SubWindow()
sub.index = count
sub.setWidget(QTextEdit())
sub.setWindowTitle("Sub Window " + str(count))
self.mdi.addSubWindow(sub)
sub.show()
sub.activated.connect(self.windowActivated)
elif action == self.cascadeAction:
self.mdi.cascadeSubWindows()
elif action == self.tileAction:
self.mdi.tileSubWindows()
elif action == self.compareAction:
self.compare()
def windowActivated(self, win, ctrlPressed):
if not ctrlPressed:
self.activeWindows.clear()
if win in self.activeWindows:
self.activeWindows.remove(win)
self.activeWindows.append(win)
self.activeList.clear()
self.activeList.addItems([w.windowTitle() for w in self.activeWindows])
valid = len(self.activeWindows) >= 2
self.compareBtn.setEnabled(valid)
self.compareAction.setEnabled(valid)
def compare(self):
editors = [w.widget() for w in self.activeWindows]
if len(editors) < 2:
return
it = iter(editors)
oldEditor = next(it)
while True:
try:
editor = next(it)
except:
msg = 'Documents are equal!'
break
if oldEditor.toPlainText() != editor.toPlainText():
msg = 'Documents do not match!'
break
oldEditor = editor
QMessageBox.information(self, 'Comparison result', msg, QMessageBox.Ok)
app = QApplication(sys.argv)
mdi = MDIWindow()
mdi.show()
app.exec_()
请注意,我必须对您的代码进行一些进一步的更改:
- 动作检查不应该通过字符串比较来完成:样式或本地化可能会向动作文本添加助记符或文本变体,并且您永远不会触发动作:创建适当的实例属性并通过对象比较来验证动作。
count
必须是实例属性,而不是类之一:如果出于任何原因必须创建主窗口的多个实例,则会得到不一致的计数;您还应该考虑当前存在的窗口;- 如果根本没有重载(这是 的情况),则不应指定信号重载,如果
QMenu.triggered
仅使用一次(并且它们的名称不是那么长,例如self.menuBar()
),则不应创建局部变量;
推荐阅读
- html - @Page 某些页面的空白页眉
- java - 如何使用 Spring Boot 和查询注释创建不同的对象类型?
- python - Selenium 按钮选择器并单击
- charts - 在 Google 折线图上切换 X 和 Y 轴
- server - 如何使用 Putty 在服务器计算机的 Anaconda 所需环境中连接 jupyter notebook?
- jhipster - 可以在不破坏 Jhipster 生成器功能的情况下重命名默认 java 包吗?
- html - 如何在wijmo网格单元内的字符串中换行?
- c# - 可以在代码中简化 Path.Data 创建吗?
- node.js - Express 及其 json 解析器出现“结束后写入”错误
- azure - Azure 认知服务:缺少自定义视觉性能统计信息在哪里?