QDockWidget tabify / splitDockWidget奇怪的行为/ bug?

时间:2015-01-26 14:13:16

标签: c++ qt qt5 qt4.8

我有一个MdiChilds应用程序,它应该包含多个QDockWidgets。然而,我在分割/标记窗口小部件时遇到麻烦,以便它们生成所需的默认布局。 我基本上想要这样的布局:

QDockWidgetTest desired layout

小部件4最后创建,需要转到列表小部件2& 3.但是,插入它会导致自身和另一个小部件丢失:

QtDockWidgetTest screenshot

以下是生成第二个屏幕截图的代码:

在主窗口的构造函数(或mdi孩子,并不重要)中,我执行以下操作:

QDockWidgetTest::QDockWidgetTest(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    setCentralWidget(0); // only QDockWidgets
    QWidget* testWidget1 = new TestWidget("1", QColor("red"));
    QWidget* testWidget2 = new TestWidget("2", QColor("green"));
    QWidget* testWidget3 = new TestWidget("3", QColor("blue"));
    QWidget* testWidget4 = new TestWidget("4", QColor("yellow"));
    DockWidgetWrapper* testQWidget1 = new DockWidgetWrapper(testWidget1, "Test Widget 1", "TestWidget1");
    DockWidgetWrapper* testQWidget2 = new DockWidgetWrapper(testWidget2, "Test Widget 2", "TestWidget2");
    DockWidgetWrapper* testQWidget3 = new DockWidgetWrapper(testWidget3, "Test Widget 3", "TestWidget3");
    DockWidgetWrapper* testQWidget4 = new DockWidgetWrapper(testWidget4, "Test Widget 4", "TestWidget4");
    addDockWidget(Qt::LeftDockWidgetArea, testQWidget1);
    splitDockWidget(testQWidget1, testQWidget2, Qt::Vertical);
    tabifyDockWidget(testQWidget2, testQWidget3);
    splitDockWidget(testQWidget3, testQWidget4, Qt::Horizontal);
}

其中TestWidget被定义为一个简单的Widget,只是用给定的颜色绘制自己,而中间的标题是:

#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QFontMetrics>

class TestWidget: public QWidget
{
private:
    QString m_content;
    QColor m_fillColor;
public:
    TestWidget(QString content, QColor color):
        m_content(content),
        m_fillColor(color)
    {
        m_fillColor.setAlpha(50);
    }
protected:
    void paintEvent(QPaintEvent* e)
    {
        QPainter p(this);
        QFontMetrics fm(p.font());
        QRect g(geometry());
        p.fillRect(g, m_fillColor);
        p.drawText(g.width()/2 - fm.width(m_content), g.height()/2 + fm.height(), m_content);
    }
};

和DockWidgetWrapper是任何QWidget的QDockWidget的简单包装器:

#include <QDockWidget>
#include <QVBoxLayout>

class DockWidgetWrapper: public QDockWidget
{
public:
    DockWidgetWrapper(QWidget* widget, QString const & windowTitle, QString const & objectName)
    {
        setWindowTitle(windowTitle);
        setFeatures(DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable);
        setWidget(widget);
        setObjectName(objectName);
    }
};

小部件3和4在最后一次splitDockWidget调用之后完全消失,因此在列表小部件上调用splitDockWidget会使插入和桌面小部件都消失。如果我在QDockWidget标题上调出上下文菜单,并让Widget2通过它消失,该应用程序看起来更奇怪:

QtDockWidgetTest after disabling Widget 2

所以有一个空白区域,其中小部件3和4应该是(根据上下文菜单,Qt似乎仍然显示它们仍然显示!)。只有在禁用这两个时,Widget1下面的空白区域才会消失。

如果我先拆分然后再列表,那就没事了。问题是我想在软件的一般部分中进行跳转,然后在加载其特殊部分时进行拆分。所以重新排序这两个对我来说不是一个真正的选择。 In the documentation我只能找到以下内容:

  

注意:如果第一个当前位于选项卡式停靠区域,则第二个将添加为新选项卡,而不是第一个的邻居。这是因为单个选项卡只能包含一个停靠窗口小部件。

绝对不会发生这种情况。此外,这个描述还有待改进:在我的例子中,这意味着一旦我有选项卡式窗口小部件2和3,就没有办法(以编程方式)将窗口小部件4放在两者的右边,或者是有另一种方法来实现这一目标吗?

对我来说,Qt 4.8和5.3一直出现这个问题。我想这可能是Qt中的一个错误?这是一个已知的错误(到目前为止,我的搜索结果是空的)?或者是否有任何其他合理的解释或者回归&#34;两个丢失的小部件?

2 个答案:

答案 0 :(得分:5)

我相信您对错误的评估是正确的,因此这是一种可能的解决方法。

我发现通过将停靠窗口小部件显式添加到窗体并指定其停靠区域,然后列表,我可以获得所需的结果。我知道你想要相反的顺序,但至少这可以摆脱那些奇怪的文物。

首先,确保通过设计器或编程方式启用停靠嵌套和标签(您可以选择在此处包含动画):

setDockOptions(DockOption::AllowNestedDocks | DockOption::AllowTabbedDocks);

然后明确添加每个停靠点。你只是加了一个。

addDockWidget(Qt::TopDockWidgetArea, testQWidget1);
addDockWidget(Qt::LeftDockWidgetArea, testQWidget2);
addDockWidget(Qt::LeftDockWidgetArea, testQWidget3);
addDockWidget(Qt::RightDockWidgetArea, testQWidget4);

将最后一个设置为:

Qt::RightDockWidgetArea

它似乎将它定位在您想要的位置。然后致电:

tabifyDockWidget(testQWidget2, testQWidget3);

如果您希望再次调用addDockWidget(),可以稍后以编程方式将停靠点移动到新位置。 Qt很聪明,意识到你正在重新添加一个现有的,所以它不会重复它。

运行此测试代码时我唯一的问题是顶部扩展坞看起来很大并且占用了大部分空间。我相信一旦你为每个人添加控件,他们就会适当调整大小。

我很想知道你的解决方案到底是什么因为我即将开始与码头相关的工作。

希望有所帮助。

PS - 这是我潜伏多年之后的第一个SO答案,所以对我来说很容易=)

答案 1 :(得分:0)

我在pyqt中遇到了同样的问题 (所以这个答案提供了python代码)。

As said in the documentation

  

...因为单个标签只能包含一个停靠小部件。

我相信split命令需要一个真正独立的Dockwidget,因为它似乎用一个布局替换了这个Widget,然后用一个Dockwidget和新的一个填充。

快速和肮脏:

基于此的方法是删除所有标记的QDockwidgets,除了一个:

other_tabs = myMainWindow.tabifiedDockWidgets( baseDockWidget )
for tab in other_tabs:
    myMainWindow.addDockWidget( Qt.BottomDockWidgetArea, tab ) # any other area
myMainWindow.splitDockWidget( baseDockWidget, newDockWidget, Qt.Vertical )
for tab in other_tabs:
    myMainWindow.tabifyDockWidget( baseDockWidget, tab )

完整的解释和解决方案:

当使用dockWidgetArea()进行测试时,我意识到Dock即使在“浮动”到新的位置时仍保持旧的位置,例如:

someDock = QDockWidget( myMainWindow )
someDock.setFloating( True )
print( myMainWindow.dockWidgetArea( someDock ) ) # NoDockWidgetArea

myMainWindow.addDockWidget( Qt.TopDockWidgetArea, someDock )
print( myMainWindow.dockWidgetArea( someDock ) ) # TopDockWidgetArea (obviously)

someDock.setFloating( True )
print( myMainWindow.dockWidgetArea( someDock ) ) # (still) TopDockWidgetArea

myMainWindow.addDockWidget( Qt.LeftDockWidgetArea, someDock )
print( myMainWindow.dockWidgetArea( someDock ) ) # LeftDockWidgetArea (obviously)

...所以,我们需要将它分配给不同的部分,以删除(覆盖)旧的任务。 setFloating()是不够的,removeDockWidget()也是如此。

根据重新分配到其他位置并将其重新发送的技巧(见上文),但如果您处于中间位置,则会丢失标签和/或当前突出显示的位置。

要获取当前可见的标签,我在DockWidgets中添加了Visibilty属性:
(不,基本的QWidget:isVisible()即使您已经选择了基座并且这个不是选定的基座也是如此)

class dockWidget(QDockWidget):
    def __init__(self, *args, **kwargs):
        QDockWidget.__init__(self, *args, **kwargs)
        self._tabVisible
        self.visibilityChanged.connect(self._setTabVisible)

    def isTabVisible(self):
        return self._tabVisible

    def _setTabVisible(self, bool):
        self._tabVisible = bool

这将根据visibiltyChanged - 信号更改布尔值。

当前可见选项卡对于获取以下选项卡非常重要,以防您同时使用dockwidget:

def _splitWindowByOrientation(self, dockWidget, orientation):
    #the dockWidget should get splited from the rest of the tabified docks
    #first: get all other tabs (they are ordered as displayed)
    other_tabs = self.tabifiedDockWidgets(dockWidget) 
    dockWidget.hide() # hide the target tab
    qApp.processEvents() # process eventloop

    newtoplevel = None # one of the other tabs will be visible now
    for tab in other_tabs:
        if tab.isTabVisible(): # find the visible one
            newtoplevel = tab
            break

    # to maintain the same order, we use the first tab from the list as base
    for tab in other_tabs[1:] + [dockWidget]: # hide and remove all others
        tab.hide() 
        # you may could skip hide and show, because there will be 
        # no visual effect until the eventloop is processed again
        self.addDockWidget(Qt.BottomDockWidgetArea, tab)

    # do the split now, with a truely single tab (using first tab as base)
    self.splitDockWidget(other_tabs[0], dockWidget, Qt.Vertical)
    dockWidget.show()

    # now add all old tabs back in, in the rigth order
    for tab in other_tabs[1:]:
        self.tabifyDockWidget(other_tabs[0], tab)
        tab.show()
    # set focus on the real follower
    newtoplevel.raise_()

你可以通过在split-command周围添加一个这样的大小来分割窗口,例如:拆分50/50:

 height = dockWidget.rect().height() # or .width() with Qt.Horizontal
 self.splitDockWidget(other_tabs[0], dockWidget, Qt.Vertical)
 dockWidget.show()
 self.resizeDocks((other_tabs[0], dockWidget), (height/2, height/2), Qt.Vertical)


希望这可以帮助!
随意编辑答案并将C ++翻译添加到代码片段;)