Qt自定义DockLayout

文章介绍了如何使用自定义的KwDockLayout组件和DockManager来管理和布局Qt中的QDockWidget,以实现类似VisualStudio的文档窗口布局,包括创建tabpanel和dock窗口,以及层级管理和关闭逻辑。
摘要由CSDN通过智能技术生成

前言

        Qt自带的QDockWidget可实现停靠在主窗口中的小部件,可以独立地停靠在主窗口的上、下、左、右四个边缘位置,也可以相互拖拽合并成一个tab组,但是想要将dock窗体停留在中间区域,模拟VS那种中间为DocumentHost的布局就不太可行,尽管可以使用splitDockWidget()方法将dock窗体停留在中间区域,但是效果还是不太理想。

        参考了其他平台关于dock布局,最终决定使用QTabWidget+QDockWidget来实现,效果如图所示。

一、自定义KwDockLayout组件

其中根据DockPanelParam类中的DockWidgetArea属性,自动转化创建对应的dock窗体或tab页签

,其中levelCode表示窗体的层级父子关系,在关闭窗体时自动关闭其子窗体。

class DockPanelParam : public QObject {
    Q_OBJECT
public:
    Qt::DockWidgetArea area;
    KwWidget* widget;
    QString title;
    QString toolTip;
    QString levelCode;
};
Q_DECLARE_METATYPE(DockPanelParam*)

KwDockLayout里设置了QTab与QDock的样式,及窗体的销毁

class KwDockLayout : public QTabWidget {
    Q_OBJECT
public:
    explicit KwDockLayout(QMainWindow* parent);
    friend class DockManager;
public slots:

    void closeTab(int index);
    void closeOtherTabs(int index);
    void closeAllTabs();
    void nextTab();
    void previousTab();
    void createLayoutPanel(DockPanelParam* panel);

    void closeChildDockWidget(QString levelCode);
private slots:
    void handleCurrentChanged(int index);
    void handleContextMenuRequested(const QPoint& pos);
    void destroyDockWidget(QString name);

private:
    QMainWindow* mainWindow;
    QHash<QString, DockPanelModel*> panels;
};
#include "kwdocklayout.h"
#include <QMenu>
#include <QTabBar>

KwDockLayout::KwDockLayout(QMainWindow* parent)
    : QTabWidget(parent)
{
    mainWindow = parent;
    QTabBar* tabBar = this->tabBar();
    tabBar->setTabsClosable(true);
    tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
    tabBar->setMovable(true);
    tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(tabBar, &QTabBar::customContextMenuRequested, this, &KwDockLayout::handleContextMenuRequested);
    connect(tabBar, &QTabBar::tabCloseRequested, this, &KwDockLayout::closeTab);

    setElideMode(Qt::ElideRight);
    QStringList qss;
    qss.append("QTabWidget{background: #ffffff;}");
    qss.append("QTabWidget::pane {border: 1px solid #f0f0f0; border-top: 3px solid #007aec;padding-top:2px; }");
    qss.append("QTabBar::tab{background: #f0f0f0; color: #000000; font-size: 12px; text-indent: 2; text-align: left top; min-width: 50px; min-height:25px; max-width: 200px;}");

    qss.append("QTabBar::tab:hover:!selected {background-color: #007aec; color: #ffffff;}");
    qss.append("QTabBar::tab:selected {background-color: #007aec; color: #ffffff;}");
    qss.append("QTabBar::tab:!selected {background-color: #ffffff; color: #000000;}");
    this->setStyleSheet(qss.join(""));
    DockManager::Ins->setDockLayout(this);
    connect(this, &QTabWidget::currentChanged, this, &KwDockLayout::handleCurrentChanged);
}
void KwDockLayout::handleCurrentChanged(int index)
{
    if (index != -1) {
        QWidget* view = widget(index);
        view->setFocus();
    }
}

void KwDockLayout::handleContextMenuRequested(const QPoint& pos)
{
    QMenu menu;
    int index = tabBar()->tabAt(pos);
    if (index != -1) {
        QAction* action = menu.addAction(tr("关闭"));
        action->setShortcut(QKeySequence::Close);
        connect(action, &QAction::triggered, this, [this, index]() {
            closeTab(index);
        });
        action = menu.addAction(tr("除此之外全部关闭"));
        connect(action, &QAction::triggered, this, [this, index]() {
            closeOtherTabs(index);
        });
    } else {
        menu.addSeparator();
    }
    menu.exec(QCursor::pos());
}

void KwDockLayout::createLayoutPanel(DockPanelParam* param)
{
    DockPanelModel* panel = nullptr;
    for (QString key : panels.keys()) {
        if (key == param->widget->Key) {
            if (panels[key]) {
                destroyDockWidget(key);
            }
        }
    }
    panel = new DockPanelModel();
    if (param->area == Qt::NoDockWidgetArea) {
        param->widget->setParent(this);
        int index = addTab(param->widget, param->title);
        setCurrentIndex(index);
        panel->isDock = false;
        panel->widget = param->widget;
        setTabToolTip(index, param->toolTip);
        setTabShape(QTabWidget::Rounded);
        //setTabIcon(index, webView->favIcon());
        panel->widget->layout()->setMargin(0);
    } else {
        QDockWidget* dw = new QDockWidget(param->title, this);
        QStringList qss;
        qss.append("QDockWidget{font-size: 20px; font-weight: 500; color: #007aec; border: 1px solid gray; padding:5px}");
        qss.append("QDockWidget::title{ background: #ffffff; padding-left: 5px;}");
        dw->setStyleSheet(qss.join(""));
        dw->setObjectName(param->widget->Key);
        dw->setToolTip(param->toolTip);
        dw->setWidget(param->widget);
        param->widget->layout()->setMargin(2);
        param->widget->setParent(dw);
        panel->isDock = true;
        panel->dock = dw;
        mainWindow->addDockWidget(param->area, dw);
    }

    panel->widget = param->widget;
    panel->key = param->widget->Key;
    panel->levelCode = param->levelCode;
    panels.insert(panel->key, panel);
    delete param;
}

void KwDockLayout::closeOtherTabs(int index)
{
    for (int i = count() - 1; i > index; --i)
        closeTab(i);
    for (int i = index - 1; i >= 0; --i)
        closeTab(i);
}
void KwDockLayout::closeAllTabs()
{
    for (int i = count() - 1; i >= 0; --i)
        closeTab(i);
}
void KwDockLayout::closeTab(int index)
{
    if (QWidget* view = widget(index)) {
        KwWidget* wd = qobject_cast<KwWidget*>(view);
        if (wd) {
            destroyDockWidget(wd->Key);
        }
    }
}

void KwDockLayout::nextTab()
{
    int next = currentIndex() + 1;
    if (next == count())
        next = 0;
    setCurrentIndex(next);
}

void KwDockLayout::previousTab()
{
    int next = currentIndex() - 1;
    if (next < 0)
        next = count() - 1;
    setCurrentIndex(next);
}
void KwDockLayout::destroyDockWidget(QString name)
{
    if (panels[name]) {
        closeChildDockWidget(panels[name]->levelCode);
        DockPanelModel* p = panels[name];
        if (p) {
            if (p->isDock) {
                if (p->dock) {
                    mainWindow->removeDockWidget(p->dock);
                    if (p->dock) {
                        p->dock->close();
                        delete p->dock;
                        p->dock = NULL;
                    }
                } else {
                    if (p->widget) {
                        delete p->widget;
                        p->widget = NULL;
                    }
                }
            } else {
                removeTab(indexOf(p->widget));
                if (p->widget) {
                    delete p->widget;
                    p->widget = NULL;
                }
            }
            delete p;
        }
    }
    panels.remove(name);
}
void KwDockLayout::closeChildDockWidget(QString levelCode)
{
    for (QString key : panels.keys()) {
        if (panels[key]) {
            if (panels[key]->levelCode.startsWith(levelCode) && panels[key]->levelCode != levelCode) {
                if (panels[key]) {
                    destroyDockWidget(key);
                }
            }
        }
    }
}

二、自定义DockManager

用于全局管理窗体,获取窗体对象、校验、激活等功能
class DockPanelModel : public QObject {
    Q_OBJECT
public:
    bool isDock;
    KwWidget* widget;
    QDockWidget* dock;
    QString key;
    QString levelCode;
};

class KwDockLayout;
class DockManager : public QObject {
public:
    DockManager();
    static DockManager* Ins;
    friend class KwDockLayout;
    DockPanelModel* getPanel(QString key);
    bool containsKey(QString key);
    void closeChild(QString levelCode);
    QString defaultLevelCode(QString levelCode);
    void active(DockPanelModel* panel);

private:
    void setDockLayout(KwDockLayout* dock)
    {
        _dock = dock;
    }
    KwDockLayout* _dock;
};
#include "dockmanager.h"
#include "kwdocklayout.h"

DockManager* DockManager::Ins = new DockManager();
DockManager::DockManager()
{
}
DockPanelModel* DockManager::getPanel(QString key)
{
    if (_dock) {
        if (_dock->panels.contains(key)) {
            return _dock->panels[key];
        }
    }
    return 0;
}
void DockManager::active(DockPanelModel* panel)
{
    if (!panel) {
        return;
    }
    if (panel->isDock) {
        panel->dock->activateWindow();
    } else {
        _dock->setCurrentWidget(panel->widget);
    }
}
bool DockManager::containsKey(QString key)
{
    if (_dock) {
        if (_dock->panels.contains(key)) {
            return true;
        }
    }
    return false;
}
void DockManager::closeChild(QString levelCode)
{
    if (_dock) {
        _dock->closeChildDockWidget(levelCode);
    }
}
QString DockManager::defaultLevelCode(QString levelCode)
{
    return QString("%1-%2").arg(levelCode).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
}

三、使用方式

构建UiEx扩展类用于创建窗体,通过信号槽传递至KwDockLayout中

class KWWIDGETBASE_EXPORT UiEx : public QObject {
    Q_OBJECT
public:
    UiEx();
    static UiEx* MyUiEx;
    static void ShowDocumentDockWidget(KwWidget* wd, QString title, QString toolTip, Qt::DockWidgetArea area = Qt::NoDockWidgetArea){
    DockPanelParam* param = new DockPanelParam();
    param->area = area;
    param->levelCode = wd->LevelCode;
    param->widget = wd;
    param->title = title;
    param->toolTip = toolTip;
    emit MyUiEx->onShowDocumentDockWidget(param);
}
signals:
    void onShowDocumentDockWidget(DockPanelParam* dw);
};

在mainwindow中添加KwDockLayout,并建立信号关系

DockOptions opts;
opts |= AllowNestedDocks;
opts |= AllowTabbedDocks;
//AllowNestedDocks//允许内嵌
//AllowTabbedDocks//允许选项卡式
//ForceTabbedDocks//强迫选项卡式
QMainWindow::setDockOptions(opts);
this->setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); //设置dock窗体左上区域由左窗体优先使用
this->setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);    

KwDockLayout*mDockLayout = new KwDockLayout(this);
this->setCentralWidget(mDockLayout);
connect(UiEx::MyUiEx, &UiEx::onShowDocumentDockWidget, mDockLayout, &KwDockLayout::createLayoutPanel);

在任意界面即可使用UiEx创建窗体

    KwWidget* wd= new KwWidget(nullptr);
    wd->Key = "tree1";
    wd->LevelCode = "tree1";
    wd->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd, "dock1", "dock1", Qt::LeftDockWidgetArea);

    KwWidget* wd2= new KwWidget(nullptr);
    wd2->Key = "tree2";
    wd2->LevelCode = "tree2";
    wd2->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd2, "dock2", "dock2", Qt::LeftDockWidgetArea);


    KwWidget* wd3= new KwWidget(nullptr);
    wd3->Key = "docm1";
    wd3->LevelCode = "tree1-docm1";
    wd3->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd3, "docm1", "docm1", Qt::NoDockWidgetArea);

    KwWidget* wd4= new KwWidget(nullptr);
    wd4->Key = "docm2";
    wd4->LevelCode = "tree1-docm2";
    wd4->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd4, "docm2", "docm2", Qt::NoDockWidgetArea);


    KwWidget* wd5= new KwWidget(nullptr);
    wd5->Key = "prt";
    wd5->LevelCode = "tree1-doc2-prt1";
    wd5->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd5, "Right", "Right", Qt::RightDockWidgetArea);

    KwWidget* wd6= new KwWidget(nullptr);
    wd6->Key = "prt2";
    wd6->LevelCode = "tree1-doc2-prt2";
    wd6->setLayout(new QVBoxLayout());
    UiEx::ShowDocumentDockWidget(wd6, "Bottom", "Bottom", Qt::BottomDockWidgetArea);

 

通过DockManager判断窗体是否存在,关闭层级窗体

        if (DockManager::Ins->containsKey(key)) {
            return;
        }
        DockManager::Ins->closeChild(this->LevelCode);

总结

KwDockLayout提供了一个方便易用的机制来管理QDockWidget部件的布局,使得用户可以灵活地调整和重新排列部件的位置和大小。使用KwDockLayout可以实现复杂的实时界面布局。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt自定义Tree(树)是指开发者可以根据自己的需求对Qt中的Tree控件进行定制和扩展。 首先,在Qt中,Tree控件常用的有QTreeWidget和QTreeView两种。 QTreeWidget是一个直接继承自QTreeWidget类的控件,使用它可以很方便地创建一个简单的树形控件。我们可以通过addItem()方法来添加子项,通过setHeaderLabels()方法来设置表头(列标题),以及通过setExpanded()方法来设置节点的展开与折叠。 如果我们需要更复杂的树形控件,就可以使用QTreeView。QTreeView允许我们通过使用自定义的模型(QAbstractItemModel的子类)来完全自定义树形控件的数据和样式。我们可以继承QAbstractItemModel类并实现其抽象方法来创建自己的模型,通过设置setModel()方法将自定义模型与QTreeView关联起来。 在自定义模型中,我们可以通过重写data()方法来返回树形控件中的数据,重写headerData()方法来设置表头数据,重写flags()方法来设置节点的编辑和选择状态等。 此外,如果需要对树形控件中的节点进行自定义绘制,我们可以通过重写QTreeView的paintEvent()方法来实现。在该方法中,我们可以使用painter对象进行绘制,绘制每个节点的背景、文本等内容。 除了模型和绘制,我们还可以使用样式表(Qt Style Sheets)来对树形控件的样式进行自定义。样式表可以设置每个节点的背景、前景颜色,调整行高、缩进等等。 总之,Qt提供了丰富的API和机制,使开发者能够灵活自定义Tree控件。通过继承、重写方法、使用自定义模型、样式表等方式,开发者可以根据需求实现各种复杂的树形控件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值