pyqt5layout.replaceWidget杀死不相关QLabel的setTextInteractionFlags?

问题描述 投票:0回答:1

考虑以下示例,基于 https://www.pythonguis.com/tutorials/pyqt-dialogs/ 的示例,我想要一个自定义 QDialog 类,其中标签中的文本是可选的,并且其中我可以在实例化时控制按钮:

import sys

from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton, QDialogButtonBox, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt

class CustomDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("HELLO!")

        self.QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(self.QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.layout = QVBoxLayout()
        self.message = QLabel("Something happened, is that OK?")
        self.message.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse)
        self.layout.addWidget(self.message)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)

    @classmethod # SO:141545; call w. QtawAnimIconRszMsgBoxDialog.fromqmb(...)
    def fromstr(cls, msg=None, buttons=None, parent=None):
        instance = cls(parent)
        if buttons:
            old_button_box = instance.buttonBox
            instance.QBtn = buttons
            instance.buttonBox = QDialogButtonBox(instance.QBtn)
            instance.layout.replaceWidget(old_button_box, instance.buttonBox) # MUST have this; but this also kills setTextInteractionFlags!
            old_button_box.clear() # "Clears the button box, deleting all buttons within it"
        if msg:
            instance.message.setText(msg)
        return instance

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press me for a dialog!")
        button.clicked.connect(self.button_clicked)
        self.setCentralWidget(button)

    def button_clicked(self, s):
        print("click", s)

        #dlg = CustomDialog(self)
        #dlg = CustomDialog.fromstr("Hello there!", parent=self) # setTextInteractionFlags OK
        dlg = CustomDialog.fromstr("Hello there!", QDialogButtonBox.Cancel, self) # kills setTextInteractionFlags
        if dlg.exec():
            print("Success!")
        else:
            print("Cancel!")

app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

如果您按原样运行代码,请单击“Press me ...”:

...然后尝试用鼠标从对话框中选择“你好”,您将无法,尽管

self.message.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse)

但是,如果您通过切换这些行的注释来避免重新实例化按钮:

# ...
        dlg = CustomDialog.fromstr("Hello there!", parent=self) # setTextInteractionFlags OK
        #dlg = CustomDialog.fromstr("Hello there!", QDialogButtonBox.Cancel, self) # kills setTextInteractionFlags
# ...

...然后是“你好!”可以用鼠标选择文本。

我(希望是正确的)将这个问题简化为

instance.layout.replaceWidget(old_button_box, instance.buttonBox)
。那么,为什么布局中不相关的小部件的
replaceWidget
会杀死 QLabel 上的 textInteractionFlags ?

有没有办法允许正确替换 QDialog 的按钮,要么继续使用

.replaceWidget
但不杀死 QLabel 的 textInteractionFlags - 或者使用其他方法?


编辑:找到了一个解决方法 - 考虑到我想更改布局中的“最后”按钮框,那么我可以只使用

replaceWidget
removeWidget
:
 而不是 
addWidget

# ...
            # retitem = instance.layout.replaceWidget(old_button_box, instance.buttonBox) # MUST have this; but this also kills setTextInteractionFlags!
            instance.layout.removeWidget(old_button_box)
            instance.layout.addWidget(instance.buttonBox)
            old_button_box.clear() # "Clears the button box, deleting all buttons within it"
            del old_button_box
        if msg:
# ...

...然后

CustomDialog.fromstr
可以与按钮规范一起正常工作(因为标签文本仍然可以选择,正如它应该的那样)。

但是,我仍然想知道 - 为什么

.replaceWidget
在这种情况下不起作用,以及可以做什么来使用它(如果需要进行类似的替换,但该小部件不是“最后一个”) “在布局中,因此删除/添加不适用)?

python pyqt5
1个回答
0
投票

tl;博士

调用

replaceWidget()
是不够的,你需要正确删除,或者至少隐藏它。

实际上,更换当前按钮盒的按钮是更合适的解决方案。

说明

问题与文本交互标志(保持不变)无关,但您看到的是真正问题的症状:您没有删除旧的按钮框实例。

当你调用

replaceWidget()
时,小部件并没有被删除,它的布局项实际上与包含新布局项的另一个小部件进行了切换,但旧的小部件仍然存在。

首次创建窗口小部件时,它始终具有默认大小:当使用父窗口小部件创建时,其大小为 100x30,而无父窗口小部件(可能是顶级窗口)始终默认为 640x480。

在您的代码中,创建了实例,但原始按钮框立即被“替换”(但仅在布局中)并清除其内容。

这意味着它的内部布局永远不会被激活,因为它没有子级,并且没有父布局再管理它,因此它保留其原始大小。

现实是它仍然存在,只是你看不到它。

只需将以下内容添加到

__init__
的末尾:

        self.buttonBox.setStyleSheet('background: green;')
        self.buttonBox.setAttribute(Qt.WA_StyledBackground)

这将导致以下结果:

按钮框是可见的并且可以使用,因为它是在after添加的(因此,above),但是QLabel已经在那里,并且插入到布局before旧按钮框,所以它不仅在视觉上被隐藏,而且对于鼠标事件也被隐藏。

以下列出了可能不合适的解决方案,但展示了如何更好地理解布局管理:

    在旧按钮盒上明确
  • hide()
    (或 
    setVisible(False)
    );
  • 致电
  • message.raise_()
     
    之后 setLayout()
    ;
  • 在按钮框后面
  • 添加标签,但使用insertWidget(0, self.message)
    ,以便它在上面
    堆叠(再次,after调用setLayout()
    );
正确的解决方案通常是在旧按钮框上调用

deleteLater()

,但不幸的是,这并不总是按预期工作:问题来自 QDialog 在调用 
exec()
 时运行的 QEventLoop(原因之一)文档中通常不鼓励使用该功能)。由于嵌套事件循环内的复杂交互,删除请求将无法正确管理,因为它是在 
outer 循环(QApplication 的循环)中调用的。

一个可能的解决方案,比上面列出的更准确,可能是重新设置按钮框的父级,因为

setParent()

 始终隐藏小部件:

old_button_box.setParent(None)
实际上,所有这些并不重要:创建一个新的按钮框没有什么意义,特别是如果您忘记再次连接其信号。

只需使用新按钮集拨打

setStandardButtons()

即可:

def fromstr(cls, msg=None, buttons=None, parent=None): instance = cls(parent) if buttons: instance.buttonBox.setStandardButtons(buttons) ...
    
© www.soinside.com 2019 - 2024. All rights reserved.