前言
前面的文章介绍过KDDockWidget的基本使用及示例,文章在这里:
 KDDockWidgets源码编译及安装
 qml dockwidget窗口停靠
 QML + KDDockWidget 实现 tabwidget效果( 窗口可独立浮动和缩放)
今天主要记录一些在KDDockWidget源码中的修改,修改源码的目的是为了根据自己的项目来实现相关风格,KDDockWidget是第三方开源代码, 不是Qt官方的,提供的可修改的接口不一定非常完善,而且部分功能还会有bug。总之,要想实现自有风格的样式,原有接口不满足的情况下,就得去改源码了。
 本文主要是记录之前在使用KDDockWidget过程中的修改记录,不一定非常完善,但是大致思路是没问题的。
首先来看看KDDockWidget提供的原有风格样式,自带的示例:
 
源码修改
1.编译QtQuick模式
KDDockWidget源码是支持QWidget和QtQuick两种实现方式,由于QWidget体系Qt官方提供了QDockWidget可以使用,而QtQuick却没有,所以用KDDockWidget更多是看中它能支持QML下的dock实现,那么,我们在编译KDDockWidget时需要修改配置才能编译出QtQuick使用的动态库。
如果要使用Quick ,Qt需要5.15版本以上,而且编译时默认关闭了编译QtQuick,所以如果需要编译运行Quick示例的话,需要修改编译文件,在根目录下找到CMakeLists.txt文件并打开,然后找到
option(${PROJECT_NAME}_QTQUICK "Build for QtQuick instead of QtWidgets" OFF)
将OFF改成ON即可
option(${PROJECT_NAME}_QTQUICK "Build for QtQuick instead of QtWidgets" ON)
接下来重新编译就可以生成QtQuick使用的动态库,并且bin目录下会编译生成quick的示例exe
 
2.自定义标题栏和窗口
在源码目录下提供的示例中,有一个叫customtitlebar,就是介绍如何实现自定义工具栏样式,
 customtitlebar 中部分代码
class CustomFrameworkWidgetFactory : public KDDockWidgets::DefaultWidgetFactory
{
public:
    ~CustomFrameworkWidgetFactory() override;
    QUrl titleBarFilename() const override
    {
        return QUrl("qrc:/MyTitleBar.qml");
    }
};
通过以上方式可以设置自定义的工具栏。KDDockWidgets::DefaultWidgetFactory类中提供了四个可以自定义的样式,包括标题栏、frame窗口、dockwidget、浮动窗口
 
 所以我们可以根据以上示例,自定义其他几个窗口样式:
 
 然后通过设置config应用
auto flags = KDDockWidgets::Config::self().flags();
auto &config = KDDockWidgets::Config::self();
config.setFlags(flags);
config.setFrameworkWidgetFactory(new CustomFrameworkWidgetFactory());
而这四个qml自定义可以参考源码目录下原有实现:
 
 也就是说,这四个我们可以在自己项目单独实现然后来实现自定义,这样会比较灵活一些,当然也可以直接在源码中修改这几个文件来达到想要的效果。
 文件说明:
- DockWidget.qml 是对应每一个可移动的子窗口
- Frame.qml 是对应dock窗口的上层外窗口
- FloatingWindow.qml 是对应浮动窗口
- TitleBar.qml 是对应标题栏
3.修改导航图标
当拖动一个dock窗口时,会在出现一个导航图标,指示需要将窗口拖动到哪个位置去,如下图:
 
 图标目录在src/img/classic_indicators/下
 
 可以直接替换成新的图标,重新编译源码即可。
 若要修改导航图标显示逻辑,可以在src\private\quick\qml\ClassicIndicatorsOverlay.qml中修改
4.修改dock之间的分隔线样式
修改dock之间的分隔线,文件在 src\private\multisplitter\qml\Separator.qml, 比如修改分隔线颜色:
import QtQuick 2.6
Rectangle {
    id: root
    anchors.fill: parent
    color: "#171717"
    readonly property QtObject kddwSeparator: parent
    MouseArea {
        cursorShape: kddwSeparator ? (kddwSeparator.isVertical ? Qt.SizeVerCursor : Qt.SizeHorCursor)
                                   : Qt.SizeHorCursor
        anchors.fill: parent
        onPressed: {
            kddwSeparator.onMousePressed();
        }
        onReleased: {
            kddwSeparator.onMouseReleased();
        }
        onPositionChanged: (mouse) => {
            kddwSeparator.onMouseMoved(Qt.point(mouse.x, mouse.y));
        }
        onDoubleClicked: {
            kddwSeparator.onMouseDoubleClicked();
        }
    }
}
5.修改分隔符宽度和dock最小最大尺寸
文件位置:src\private\multisplitter\Item.cpp
int Layouting::Item::separatorThickness = 5;
// There are the defaults. They can be changed by the user via Config.h API.
QSize Layouting::Item::hardcodedMinimumSize = QSize(80, 90);
QSize Layouting::Item::hardcodedMaximumSize = QSize(16777215, 16777215);
以上是默认值,可以对应修改。dock最小和最大尺寸是对应窗口能拖动最小和最大的尺寸。
6.在标题栏上添加按钮
标题栏上默认有floating和close按钮,如果想再添加一个,关键步骤是要将按钮点击事件传出来让好在外层中响应,可以按照以下流程操作。
 首先,如果我们有自定义标题栏,如上面第二点提到的,重新实现了标题栏,那就在自定义的标题栏qml中添加按钮,如果没有自定义,那就在源码中进行修改,找到 src\private\quick\qml\TitleBar.qml, 可以看到这里面有floating和close按钮的定义,直接在这里添加即可:
 TitleBarButton {
     id: floatButton
     visible: root.floatButtonVisible
     imageSource: "qrc:/img/dock-float.png"
     anchors {
         verticalCenter: parent ? parent.verticalCenter : undefined
         right: closeButton.left
         topMargin: 5
         bottomMargin: 5
         rightMargin: 2
     }
     onClicked: {
         root.floatButtonClicked();
     }
 }
 TitleBarButton {
     id: closeButton
     enabled: root.closeButtonEnabled
     imageSource: "qrc:/img/close.png"
     visible:true
     anchors {
         verticalCenter: parent ? parent.verticalCenter : undefined
         right: parent ? parent.right : undefined
         topMargin: 5
         bottomMargin: 5
         leftMargin: 5
         rightMargin: 2
     }
     onClicked: {
         root.closeButtonClicked();
     }
 }
加入要添加一个菜单按钮:
TitleBarButton {
     id: menuButton
     imageSource: "qrc:/img/dock-menu.png"
     anchors {
         verticalCenter: parent ? parent.verticalCenter : undefined
         right: floatButton.left
         topMargin: 5
         bottomMargin: 5
         rightMargin: 2
     }
     onClicked: {
         root.sigMenuButtonClicked();
     }
 }
这里点击按钮发送信号sigMenuButtonClicked(),我们需要在 TitleBarBase.qml 中定义该信号
 
然后在本文件内添加信号连接,并调用cpp中的接口
onSigMenuButtonClicked:{
    titleBarCpp.onMenuClicked();
}
接着在TitleBar_p.h中定义onMenuClicked()接口:
 
 TitleBar.cpp中添加接口实现
void TitleBar::onMenuClicked()
{
    if(m_frame){
        if (DockWidgetBase *dw = m_frame->currentDockWidget()) {
            qDebug() << __FUNCTION__ << "dock dw->sigMenuClicked()";
            Q_EMIT dw->sigMenuClicked();
        }
    }
    else if(m_floatingWindow){
        if (Frame *f = m_floatingWindow->singleFrame()) {
            if (DockWidgetBase *dw = f->currentDockWidget()) {
                qDebug() << __FUNCTION__ << "floating dw->sigMenuClicked()";
                Q_EMIT dw->sigMenuClicked();
            }
            else{
                qWarning() << Q_FUNC_INFO << "Frame with no dock widgets";
            }
        }
        else{
            qWarning() << Q_FUNC_INFO << "m_floatingWindow singleFrame() is null";
        }
    }
}
这里 dw->sigMenuClicked() 继续向上层传递信号,所以需要在 DockWidgetBase.h中定义这个信号:

最后,在DockWidgetInstantiator_p.h中也定义信号,然后在DockWidgetInstantiator.cpp中将DockWidgetBase发出的信号关联起来:

 
connect(m_dockWidget, &DockWidgetQuick::sigMenuClicked, this,
            &DockWidgetInstantiator::sigMenuClicked);
ok,这样就可以在qml中使用DockWidget的时候直接关联菜单按钮点击事件了,示例如下:
KDDW.MainWindowLayout {
	id: dockWidgetArea
    anchors.fill: parent
    uniqueName: "MyMainLayout"
    KDDW.DockWidget {
        id: dock3
        uniqueName: "dock3" // Each dock widget needs a unique id
        Rectangle{
            color: "yellow"
        }
        onSigMenuClicked:{
            console.log("---------onSigMenuClicked-------------")
        }
    }
  Component.onCompleted: {
      console.log("--------MainWindowLayout onCompleted------")
        addDockWidget(dock3, KDDW.KDDockWidgets.Location_OnTop);
  } 
}
7.修改dock被均分宽度的问题
如果当前MainWindowLayout 中有两个dock窗口,浮动一个窗口后恢复添加到MainWindowLayout 中去,会发现原本两个dock宽度不同的窗口会被均分宽度,这个在实际项目中 每个窗口都有对应的显示宽度,如果直接被layout均分显示,可能会倒是界面显示异常,所以要修改不让在恢复时均分宽度的问题。
 修改也很简单,找到src\private\multisplitter\Item_p.h 找到以下两个接口,修改其默认参数:
Item类中:
virtual void setSize_recursive(QSize newSize, ChildrenResizeStrategy strategy = ChildrenResizeStrategy::Percentage);
改为
virtual void setSize_recursive(QSize newSize, ChildrenResizeStrategy strategy = ChildrenResizeStrategy::Side2SeparatorMove);
ItemContainer类继承于Item,所以对应的虚函数也要修改
virtual void setSize_recursive(QSize newSize, ChildrenResizeStrategy strategy = ChildrenResizeStrategy::Percentage);
改为
virtual void setSize_recursive(QSize newSize, ChildrenResizeStrategy strategy = ChildrenResizeStrategy::Side2SeparatorMove);
还有:
void growItem(Item *, int amount, GrowthStrategy,
                  NeighbourSqueezeStrategy neighbourSqueezeStrategy,
                  bool accountForNewSeparator = false,
                  ChildrenResizeStrategy = ChildrenResizeStrategy::Percentage);
                  
void applyGeometries(const SizingInfo::List &sizes, ChildrenResizeStrategy = ChildrenResizeStrategy::Percentage);
这两个的默认参数都要修改
void growItem(Item *, int amount, GrowthStrategy,
                  NeighbourSqueezeStrategy neighbourSqueezeStrategy,
                  bool accountForNewSeparator = false,
                  ChildrenResizeStrategy = ChildrenResizeStrategy::Side2SeparatorMove);
                  
void applyGeometries(const SizingInfo::List &sizes, ChildrenResizeStrategy = ChildrenResizeStrategy::Side2SeparatorMove);
ok,接下来重新编辑动态库即可。
当前暂时涉及到以上部分的修改,后续若有新增会及时更新。









![[数据结构基础]链式二叉树及其前序、中序和后序遍历](https://img-blog.csdnimg.cn/40764d5adb9f464ca7b02e9816ede04c.png)









