pyqt5 QMdiArea 多个活动子窗口
pyqt5 QMdiArea multiple active subwindows
在QMdiArea中,我们可以select(指向)激活子window。在我的应用程序中,我想 select 多个 subwindows (可能使用“Ctrl”按钮)并将它们设置为活动 windows (>=2 subwindows) 和为他们创建一个列表指针。我正在尝试同时获取多个 subwindow 的指针。是的,activeSubWindow() 只给出一个window。但我想知道我是否可以使用键盘中的“Ctrl”按钮之类的东西来 select 两个子windows 并打印指向这些子windows 的指针。这个想法是同时获取每个子window(例如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_()
就像正常的 window 处理一样,即使在 MDI 区域中也不可能有多个活动子 windows。
为了实现“多选”系统,您需要跟踪子windows的激活状态,这可能很棘手。
Subwindows 可以通过不同的方式激活:
- 通过点击他们的标题栏(包括任何按钮);
- 通过单击其包含的小部件;
- 通过使用
setActiveSubWindow()
以编程方式激活它(类似于从任务栏中选择普通 window);
虽然 Qt 提供了 aboutToActivate
信号,但它并不总是可靠的:它 总是 即使在顶层 window 获得焦点时也会发出,所以有没有直接的方法知道激活的原因。
windowStateChanged
信号也是如此(在 状态发生变化后 发出)。
对于你的情况,最好的办法主要是根据mousePressEvent
实现subwindow,还要考虑window状态变化,因为你需要保持跟踪每当以任何其他方式更改激活时(通过单击小部件或使用 setActiveSubWindow()
.
,当前激活的 windows
由于鼠标事件是在 window 激活更改后处理的,正确的解决方案是创建一个发射将被延迟(预定)的信号,为了知道激活是否实际上是通过在 subwindow 上按下鼠标按钮实现的(而不是在 child 小部件上),最后检查 Ctrl同时按下了键。
请注意以下代码非常基础,您可能需要做一些调整。例如,它不考虑最小化 windows 的激活(与普通 windows 不同,subwindow 即使被最小化也可能处于活动状态),也不考虑单击任何window 个按钮。
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_()
请注意,我必须对您的代码进行一些进一步的更改:
- 动作检查应该永远通过字符串比较来完成:样式或本地化可能会向动作文本添加助记符或文本变体,而且您永远不会触发您的操作:创建适当的实例属性并通过 object 比较来验证操作。
count
必须是 instance 属性,而不是 class 属性:如果出于任何原因,您必须创建多个实例main window,你会得到一个不一致的计数;您还应该考虑当前存在的 windows;
- 如果根本没有重载(
QMenu.triggered
就是这种情况),则不应指定信号重载;如果只使用一次(并且名称不长,则不应创建局部变量,喜欢 self.menuBar()
);
在QMdiArea中,我们可以select(指向)激活子window。在我的应用程序中,我想 select 多个 subwindows (可能使用“Ctrl”按钮)并将它们设置为活动 windows (>=2 subwindows) 和为他们创建一个列表指针。我正在尝试同时获取多个 subwindow 的指针。是的,activeSubWindow() 只给出一个window。但我想知道我是否可以使用键盘中的“Ctrl”按钮之类的东西来 select 两个子windows 并打印指向这些子windows 的指针。这个想法是同时获取每个子window(例如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_()
就像正常的 window 处理一样,即使在 MDI 区域中也不可能有多个活动子 windows。
为了实现“多选”系统,您需要跟踪子windows的激活状态,这可能很棘手。
Subwindows 可以通过不同的方式激活:
- 通过点击他们的标题栏(包括任何按钮);
- 通过单击其包含的小部件;
- 通过使用
setActiveSubWindow()
以编程方式激活它(类似于从任务栏中选择普通 window);
虽然 Qt 提供了 aboutToActivate
信号,但它并不总是可靠的:它 总是 即使在顶层 window 获得焦点时也会发出,所以有没有直接的方法知道激活的原因。
windowStateChanged
信号也是如此(在 状态发生变化后 发出)。
对于你的情况,最好的办法主要是根据mousePressEvent
实现subwindow,还要考虑window状态变化,因为你需要保持跟踪每当以任何其他方式更改激活时(通过单击小部件或使用 setActiveSubWindow()
.
由于鼠标事件是在 window 激活更改后处理的,正确的解决方案是创建一个发射将被延迟(预定)的信号,为了知道激活是否实际上是通过在 subwindow 上按下鼠标按钮实现的(而不是在 child 小部件上),最后检查 Ctrl同时按下了键。
请注意以下代码非常基础,您可能需要做一些调整。例如,它不考虑最小化 windows 的激活(与普通 windows 不同,subwindow 即使被最小化也可能处于活动状态),也不考虑单击任何window 个按钮。
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_()
请注意,我必须对您的代码进行一些进一步的更改:
- 动作检查应该永远通过字符串比较来完成:样式或本地化可能会向动作文本添加助记符或文本变体,而且您永远不会触发您的操作:创建适当的实例属性并通过 object 比较来验证操作。
count
必须是 instance 属性,而不是 class 属性:如果出于任何原因,您必须创建多个实例main window,你会得到一个不一致的计数;您还应该考虑当前存在的 windows;- 如果根本没有重载(
QMenu.triggered
就是这种情况),则不应指定信号重载;如果只使用一次(并且名称不长,则不应创建局部变量,喜欢self.menuBar()
);