前言
前面的文章介绍过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,接下来重新编辑动态库即可。
当前暂时涉及到以上部分的修改,后续若有新增会及时更新。