尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt自定义抽屉控件开发:从QToolBox局限到灵活动画实现

Qt自定义抽屉控件开发:从QToolBox局限到灵活动画实现 1. 项目概述为什么我们需要一个自定义的抽屉盒子控件在桌面应用开发中尤其是使用Qt框架时我们经常会遇到需要组织大量信息或功能模块的场景。比如一个复杂的配置窗口里面有“网络设置”、“显示设置”、“高级选项”等十几个分类。如果把这些选项全部平铺开来界面会变得极其臃肿用户也难以找到目标。这时候一个类似Windows资源管理器左侧导航栏的“抽屉”控件就派上用场了平时只显示分类标题点击后展开显示详细内容再点击则收起既节省了宝贵的屏幕空间又让界面逻辑清晰。Qt框架本身提供了一个名为QToolBox的控件它正是这种“抽屉盒子”的官方实现。那为什么我们还要费劲去“自定义”一个呢在实际项目中我遇到过不少QToolBox的局限性。首先它的样式定制非常困难默认的外观可能和你的应用设计语言格格不入。其次功能上比较固定比如你想在抽屉标题栏上加一个状态指示灯、一个额外的操作按钮或者实现动画展开效果用原生的QToolBox要么做不到要么需要非常复杂的子类化和样式表QSS技巧代码维护起来很头疼。最后QToolBox在某些平台上的表现也不尽一致有时会出现绘制上的小瑕疵。因此从零开始构建一个自定义的抽屉盒子控件就成了一次“把控制权完全掌握在自己手里”的实践。这不仅能让我们获得一个外观、行为都完全符合项目需求的控件更是一次深入理解Qt控件绘制、布局、事件处理和动画机制的绝佳机会。无论是刚接触Qt不久的新手还是想提升控件开发能力的老手这个项目都能带来实实在在的收获。接下来我将详细拆解如何一步步打造一个功能强大、样式灵活的自定义抽屉控件。2. 核心设计思路与架构拆解在动手写代码之前我们先要明确这个自定义控件的设计目标。一个好的设计应该遵循“高内聚、低耦合”的原则同时提供足够的扩展性。2.1 控件功能定义与需求分析我们的目标是创建一个名为CustomDrawerBox的控件它需要具备以下核心功能抽屉项管理能够动态添加、插入、删除抽屉项。每个抽屉项由两部分组成一个可点击的标题栏Header和一个用于承载具体内容的内容区域Content Widget。展开/收起状态控制每个抽屉项可以独立控制其展开或收起状态。初始状态可配置。灵活的样式定制标题栏的背景、文字颜色、图标、边框等应能通过属性或样式表方便地修改。内容区域的样式也应能独立控制。平滑的动画效果展开和收起的过程应该是平滑的、有动画的提升用户体验。动画的速度、缓动曲线Easing Curve应可配置。丰富的交互信号需要提供足够的信号Signals如drawerToggled(int index, bool expanded)以便上层业务逻辑能够响应状态变化。良好的键盘导航支持支持通过键盘如上下箭头、空格键、回车键来切换焦点和操作抽屉。基于这些需求我们决定采用组合Composition而非继承QToolBox的方式来实现。这样我们可以从最基础的QWidget开始完全掌控内部布局和逻辑。2.2 整体架构设计整个控件的架构可以看作一个垂直的“管理器”它管理着多个“抽屉项”单元。每个“抽屉项”单元本身也是一个复合控件。顶层控件 (CustomDrawerBox)继承自QWidget。它内部使用一个QVBoxLayout作为主布局用于垂直排列所有的抽屉项。它负责管理抽屉项列表、处理全局状态如是否允许多个同时展开以及对外提供统一的API。抽屉项单元 (DrawerItem)这是我们自定义的核心部件同样继承自QWidget。每个DrawerItem内部包含标题栏 (HeaderWidget)一个可点击的区域通常是一个QPushButton或自定义的QWidget用于显示标题、图标和展开状态指示器如一个三角形箭头。内容区域 (ContentArea)一个QWidget用于放置用户提供的具体内容控件如QLabel、QLineEdit、QGridLayout等。这个区域的高度在收起时应为0在展开时则适应其内部控件的大小。动画控制器 (QPropertyAnimation)用于动态改变ContentArea的maximumHeight或minimumHeight属性从而实现展开/收起的动画效果。这种分层架构的好处是职责清晰。CustomDrawerBox负责宏观管理DrawerItem负责单个抽屉的完整生命周期和交互而HeaderWidget和ContentArea则专注于自身的展示和功能。当我们需要修改标题栏的样式时只需改动HeaderWidget不会影响到内容区域或管理逻辑。3. 核心类实现详解有了清晰的设计图我们就可以开始编码了。我们将按照自底向上的顺序先实现最基础的DrawerItem再构建外层的CustomDrawerBox。3.1 DrawerItem抽屉项的实现DrawerItem是整个控件的心脏它封装了单个抽屉的所有状态和行为。// draweritem.h #ifndef DRAWERITEM_H #define DRAWERITEM_H #include QWidget #include QPropertyAnimation class QPushButton; class QWidget; class DrawerItem : public QWidget { Q_OBJECT // 定义一个可动画的属性用于控制内容区域的高度 Q_PROPERTY(int contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged) public: explicit DrawerItem(const QString title, QWidget* contentWidget nullptr, QWidget* parent nullptr); ~DrawerItem(); // 设置和获取标题 void setTitle(const QString title); QString title() const; // 设置和获取内容控件 void setContentWidget(QWidget* widget); QWidget* contentWidget() const; // 展开和收起操作 void expand(); void collapse(); void toggle(); // 获取当前状态 bool isExpanded() const; // 动画时长和缓动曲线设置 void setAnimationDuration(int msec); int animationDuration() const; void setEasingCurve(const QEasingCurve curve); // 内容高度属性的访问器 int contentHeight() const; void setContentHeight(int height); signals: void toggled(bool expanded); // 状态切换信号 void contentHeightChanged(int height); // 属性变化信号供动画使用 protected: // 重写事件处理用于实现键盘交互等 void keyPressEvent(QKeyEvent* event) override; private slots: void onHeaderClicked(); // 标题栏点击槽函数 void onAnimationFinished(); // 动画完成槽函数 private: void setupUI(); // 初始化界面 void updateHeaderIndicator(); // 根据状态更新标题栏指示器如箭头方向 QPushButton* m_headerButton; // 标题栏按钮 QWidget* m_contentContainer; // 内容区域的容器 QWidget* m_contentWidget; // 用户设置的内容控件 QPropertyAnimation* m_animation; // 属性动画对象 bool m_isExpanded; // 当前展开状态 int m_collapsedHeight; // 收起时的高度通常为0或一个很小的值仅容纳边框 int m_expandedHeight; // 展开时的高度内容控件的理想高度 }; #endif // DRAWERITEM_H头文件定义了类的接口和核心成员变量。关键点在于我们使用了Qt的属性系统Q_PROPERTY声明了一个contentHeight属性这将允许QPropertyAnimation平滑地改变内容容器的高度。// draweritem.cpp #include draweritem.h #include QPushButton #include QVBoxLayout #include QKeyEvent #include QStyle DrawerItem::DrawerItem(const QString title, QWidget* contentWidget, QWidget* parent) : QWidget(parent) , m_isExpanded(false) , m_collapsedHeight(0) , m_expandedHeight(0) { // 创建标题栏按钮 m_headerButton new QPushButton(title, this); m_headerButton-setObjectName(drawerHeader); // 为样式表设置对象名 m_headerButton-setCheckable(true); // 设置为可勾选方便用样式表表示按下状态 m_headerButton-setFocusPolicy(Qt::StrongFocus); // 允许键盘聚焦 // 创建内容容器初始设置为收起状态 m_contentContainer new QWidget(this); m_contentContainer-setObjectName(drawerContent); m_contentContainer-setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_contentContainer-setMaximumHeight(0); // 初始高度为0即收起 m_contentContainer-setMinimumHeight(0); // 创建垂直布局将标题栏和内容容器加入 QVBoxLayout* mainLayout new QVBoxLayout(this); mainLayout-setSpacing(0); mainLayout-setContentsMargins(0, 0, 0, 0); // 去除边距让控件更紧凑 mainLayout-addWidget(m_headerButton); mainLayout-addWidget(m_contentContainer); // 创建属性动画绑定到内容容器的maximumHeight属性 m_animation new QPropertyAnimation(m_contentContainer, maximumHeight, this); m_animation-setDuration(300); // 默认动画时长300毫秒 m_animation-setEasingCurve(QEasingCurve::OutCubic); // 默认缓动曲线 // 连接信号与槽 connect(m_headerButton, QPushButton::clicked, this, DrawerItem::onHeaderClicked); connect(m_animation, QPropertyAnimation::finished, this, DrawerItem::onAnimationFinished); // 设置内容控件如果构造时提供了 if(contentWidget) { setContentWidget(contentWidget); } updateHeaderIndicator(); } void DrawerItem::setContentWidget(QWidget* widget) { if (m_contentWidget) { // 如果已有内容控件先从布局中移除并删除 m_contentWidget-setParent(nullptr); delete m_contentWidget; } m_contentWidget widget; if (m_contentWidget) { // 将内容控件放入内容容器 QVBoxLayout* contentLayout new QVBoxLayout(m_contentContainer); contentLayout-setContentsMargins(5, 5, 5, 5); // 给内容一点内边距 contentLayout-addWidget(m_contentWidget); m_contentContainer-setLayout(contentLayout); // 计算内容控件的理想高度作为展开后的目标高度 // 注意需要确保内容控件的大小策略和最小/最大尺寸设置合理 m_expandedHeight m_contentWidget-sizeHint().height() contentLayout-contentsMargins().top() contentLayout-contentsMargins().bottom(); // 也可以考虑使用 minimumSizeHint() 或根据布局计算 } else { m_expandedHeight 0; } // 如果当前是展开状态立即将高度设置为展开高度 if (m_isExpanded) { m_contentContainer-setMaximumHeight(m_expandedHeight); } }在构造函数和setContentWidget函数中我们完成了控件的初始组装。这里有几个关键细节布局管理使用QVBoxLayout并设置边距和间距为0确保抽屉项之间紧密相连外观像一个整体。内容高度计算m_expandedHeight的计算至关重要。我们使用sizeHint()来获取内容控件的理想大小。但这里有个坑sizeHint()可能返回无效值-1特别是对于复杂的、动态变化的控件。更稳健的做法是在内容控件显示后例如在showEvent中或者通过QTimer::singleShot延迟调用使用layout()-sizeHint().height()来获取内容容器布局的合适高度。初始状态通过setMaximumHeight(0)将内容容器完全收起。void DrawerItem::onHeaderClicked() { toggle(); } void DrawerItem::toggle() { if (m_isExpanded) { collapse(); } else { expand(); } } void DrawerItem::expand() { if (m_isExpanded || m_animation-state() QAbstractAnimation::Running) { return; // 已经是展开状态或动画正在进行中则忽略 } m_isExpanded true; m_headerButton-setChecked(true); updateHeaderIndicator(); // 设置动画的起始值和结束值 m_animation-setStartValue(m_contentContainer-maximumHeight()); m_animation-setEndValue(m_expandedHeight); m_animation-start(); emit toggled(true); } void DrawerItem::collapse() { if (!m_isExpanded || m_animation-state() QAbstractAnimation::Running) { return; } m_isExpanded false; m_headerButton-setChecked(false); updateHeaderIndicator(); m_animation-setStartValue(m_contentContainer-maximumHeight()); m_animation-setEndValue(m_collapsedHeight); m_animation-start(); emit toggled(false); }展开和收起是核心逻辑。我们通过改变m_contentContainer的maximumHeight属性来实现。使用QPropertyAnimation的好处是Qt会自动在设定的持续时间内平滑地改变这个属性的值从而产生动画效果。注意我们在开始动画前检查了当前状态和动画状态防止重复触发。void DrawerItem::updateHeaderIndicator() { // 这里可以更新标题栏的图标例如用三角形表示状态 // 一种简单的方法是使用Unicode字符 QString indicator m_isExpanded ? QString::fromUtf8(▼ ) : QString::fromUtf8(▶ ); m_headerButton-setText(indicator m_headerButton-text().mid(2)); // 去掉旧的指示符 // 更推荐的方式是通过样式表设置背景图片或者使用QSS的伪状态(:checked)来切换图标 }updateHeaderIndicator函数展示了如何动态更新标题栏的视觉提示。使用Unicode字符是最简单的方式但样式可能受字体影响。更专业的做法是在样式表中为QPushButton设置border-image或image并通过:checked伪状态来切换这样能获得更一致的视觉效果。3.2 CustomDrawerBox抽屉盒子的组装与管理DrawerItem实现后CustomDrawerBox的工作就相对简单了它主要是一个容器和管理器。// customdrawerbox.h #ifndef CUSTOMDRAWERBOX_H #define CUSTOMDRAWERBOX_H #include QWidget #include QVector class DrawerItem; class QVBoxLayout; class CustomDrawerBox : public QWidget { Q_OBJECT public: explicit CustomDrawerBox(QWidget *parent nullptr); ~CustomDrawerBox(); // 添加抽屉项 int addItem(const QString title, QWidget* contentWidget); int insertItem(int index, const QString title, QWidget* contentWidget); // 移除抽屉项 void removeItem(int index); void clear(); // 获取抽屉项 DrawerItem* itemAt(int index) const; int count() const; // 展开/收起控制 void setExpanded(int index, bool expanded); bool isExpanded(int index) const; // 设置是否允许多个抽屉同时展开 void setMultiExpandingEnabled(bool enabled); bool isMultiExpandingEnabled() const; signals: void drawerToggled(int index, bool expanded); private slots: void onDrawerToggledInternal(bool expanded); private: QVBoxLayout* m_mainLayout; QVectorDrawerItem* m_drawerItems; bool m_multiExpandingEnabled; }; #endif // CUSTOMDRAWERBOX_H// customdrawerbox.cpp #include customdrawerbox.h #include draweritem.h #include QVBoxLayout CustomDrawerBox::CustomDrawerBox(QWidget *parent) : QWidget(parent) , m_multiExpandingEnabled(true) // 默认允许多个同时展开 { m_mainLayout new QVBoxLayout(this); m_mainLayout-setSpacing(1); // 设置一个很小的间距让抽屉之间有一条细线分隔 m_mainLayout-setContentsMargins(0, 0, 0, 0); } int CustomDrawerBox::addItem(const QString title, QWidget* contentWidget) { return insertItem(m_drawerItems.size(), title, contentWidget); } int CustomDrawerBox::insertItem(int index, const QString title, QWidget* contentWidget) { DrawerItem* newItem new DrawerItem(title, contentWidget, this); connect(newItem, DrawerItem::toggled, this, CustomDrawerBox::onDrawerToggledInternal); index qBound(0, index, m_drawerItems.size()); m_mainLayout-insertWidget(index, newItem); m_drawerItems.insert(index, newItem); // 发出信号通知外部有新的抽屉项被插入并返回其索引 // 这里可以添加一个 itemInserted 信号为了简洁省略 return index; } void CustomDrawerBox::onDrawerToggledInternal(bool expanded) { DrawerItem* senderDrawer qobject_castDrawerItem*(sender()); if (!senderDrawer) return; int index m_drawerItems.indexOf(senderDrawer); if (index -1) return; // 如果不允许多个同时展开且当前是展开操作 if (!m_multiExpandingEnabled expanded) { // 遍历所有抽屉项将其他已展开的项收起 for (int i 0; i m_drawerItems.size(); i) { if (i ! index m_drawerItems.at(i)-isExpanded()) { m_drawerItems.at(i)-collapse(); } } } emit drawerToggled(index, expanded); }CustomDrawerBox的核心逻辑在onDrawerToggledInternal槽函数中。当任何一个DrawerItem的状态发生变化时都会触发这个槽。如果设置了m_multiExpandingEnabled为false即类似手风琴模式一次只展开一个那么当有新的抽屉被展开时它会自动收起其他所有已展开的抽屉。这个功能是原生QToolBox也具备的我们通过简单的逻辑就实现了。4. 样式定制与美化实战一个控件好不好用一半看功能一半看颜值。我们提供了多种方式来美化这个自定义抽屉控件。4.1 使用Qt样式表QSS进行皮肤定制Qt样式表是定制控件外观最强大、最常用的工具。我们为DrawerItem的内部部件设置了对象名drawerHeader,drawerContent就是为了方便用QSS进行精准选择。/* 示例QSS可以设置在 CustomDrawerBox 或父窗口上 */ CustomDrawerBox { background-color: #f0f0f0; border: 1px solid #cccccc; border-radius: 4px; } /* 抽屉标题栏的默认样式 */ CustomDrawerBox QPushButton#drawerHeader { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #f6f7fa, stop:1 #dadbde); border: none; border-bottom: 1px solid #cccccc; /* 底部边框作为分隔线 */ text-align: left; padding: 8px 15px; font-weight: bold; color: #333333; } /* 抽屉标题栏的鼠标悬停样式 */ CustomDrawerBox QPushButton#drawerHeader:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e3e4e8, stop:1 #cbccd0); } /* 抽屉标题栏的按下/展开状态样式 */ CustomDrawerBox QPushButton#drawerHeader:checked { background-color: #d0e3ff; border-bottom: 1px solid #a0c0ff; color: #0066cc; } /* 内容区域的样式 */ CustomDrawerBox QWidget#drawerContent { background-color: white; border: none; /* 注意不要在这里设置固定高度高度由动画控制 */ }通过QSS我们可以轻松实现渐变背景、圆角、状态变化等效果让控件完美融入应用的整体设计。4.2 通过属性接口进行动态样式调整除了QSS我们也可以在代码中动态修改样式。例如我们可以在DrawerItem中增加一些方法// 在 draweritem.h 中增加 void setHeaderStyleSheet(const QString styleSheet); void setContentStyleSheet(const QString styleSheet); void setHeaderIcon(const QIcon icon, QSize size QSize(16, 16));然后在实现中将这些样式或图标应用到m_headerButton和m_contentContainer上。这种方式适合需要根据程序逻辑动态改变样式的场景。实操心得QSS的优先级与性能使用QSS时要注意样式规则的优先级。ID选择器如#drawerHeader的优先级高于类选择器如QPushButton。如果样式不生效很可能是被更高优先级的规则覆盖了。另外过于复杂或频繁切换的QSS可能会对性能有轻微影响在需要极致性能的场景下如每秒刷新数十次的图表控件应优先考虑在paintEvent中直接绘制。但对于抽屉控件这种交互频率不高的组件QSS是完全胜任的。5. 高级功能扩展与性能优化基础功能实现后我们可以考虑为控件添加一些“锦上添花”的高级特性让它更专业、更好用。5.1 添加拖拽排序功能让用户可以通过拖拽标题栏来改变抽屉的顺序这是一个提升用户体验的亮点功能。实现思路如下在DrawerItem的HeaderWidget上监听鼠标的pressEvent、moveEvent和releaseEvent。鼠标按下时记录起始位置并可以绘制一个半透明的拖拽预览图使用QDrag。鼠标移动超过一定阈值时开始拖拽操作。在CustomDrawerBox中需要计算鼠标当前位置对应的插入索引。拖拽结束时在CustomDrawerBox中重新排列m_drawerItems向量并调用QLayout的removeWidget和insertWidget方法来更新界面。这个功能实现起来稍复杂涉及到坐标转换和布局更新但能极大增强控件的交互性。5.2 优化动画与性能动画时长自适应固定的300毫秒动画可能不适合所有内容高度。我们可以根据m_expandedHeight来动态计算动画时长例如duration 200 m_expandedHeight / 5让展开大内容区域时动画稍慢小内容区域时动画更快。避免布局抖动在动画过程中内容区域的高度不断变化可能会引起父布局的频繁重算recalc。如果抽屉盒子放在一个复杂的布局中可能会造成界面卡顿。一个优化技巧是在动画开始前暂时将父控件的更新策略设为UpdatesDisabled动画结束后再恢复。但使用要小心避免影响其他必要的绘制。void DrawerItem::expand() { ... // 优化在动画期间暂时禁止父控件更新可选需谨慎 // parentWidget()-setUpdatesEnabled(false); m_animation-start(); // 连接动画的 finished 信号在结束后恢复更新 // connect(m_animation, QPropertyAnimation::finished, parentWidget(), [this](){ parentWidget()-setUpdatesEnabled(true); }); ... }内存管理确保在CustomDrawerBox的析构函数或removeItem函数中正确删除DrawerItem对象防止内存泄漏。5.3 响应式设计考虑我们的控件应该能适应不同尺寸的窗口。例如当窗口变窄时标题栏的文字如果太长应该显示为省略号...。这可以通过在HeaderWidget即QPushButton上设置样式表实现CustomDrawerBox QPushButton#drawerHeader { ... /* 关键属性让文本在空间不足时显示为省略号 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }同时内容区域内部的控件也应该合理设置大小策略sizePolicy例如使用QGridLayout并设置列拉伸让内容能随着抽屉宽度的变化而自适应。6. 常见问题排查与调试技巧在实际使用和开发自定义控件的过程中你肯定会遇到各种各样的问题。这里记录了几个我踩过的坑和解决方法。6.1 内容高度计算不准动画效果生硬这是最常见的问题。现象是展开时内容区域“跳”一下而不是平滑展开。原因在设置内容控件后立即调用sizeHint()此时控件可能尚未完成布局和尺寸计算返回的是无效值-1或默认值。解决方案延迟计算在DrawerItem的showEvent中计算高度或者使用QTimer::singleShot(0, this, [this](){ calculateExpandedHeight(); });。单次计时器为0表示在事件循环空闲时立即执行此时布局通常已完成。使用布局尺寸不要直接使用m_contentWidget-sizeHint()而是使用m_contentContainer-layout()-sizeHint().height()。布局计算的高度更准确。手动设置高度如果内容固定可以直接在添加内容控件后调用m_contentWidget-setFixedHeight(value)然后将这个值加上边距作为m_expandedHeight。6.2 样式表QSS不生效你写了QSS但控件外观毫无变化。排查步骤检查对象名确保在代码中为控件设置了正确的objectName如m_headerButton-setObjectName(drawerHeader)并且QSS选择器中的名字匹配。检查作用域QSS设置在父控件上才能被子控件继承。通常将样式表设置在CustomDrawerBox或它的父窗口上。直接设置在DrawerItem上可能无法影响其内部的子控件。检查优先级如果其他地方的QSS规则更具体如同时使用了类名和ID可能会覆盖你的规则。尝试在规则前加上CustomDrawerBox进行限定。使用调试工具Qt Creator的“设计”模式可以实时编辑和预览QSS是调试样式的利器。6.3 抽屉项之间的边框或间距显示异常期望是紧密相连结果中间有奇怪的间隙或双边框。原因CustomDrawerBox的主布局m_mainLayout和DrawerItem自身的布局mainLayout的边距contentsMargins和间距spacing叠加导致的。解决方案如我们在代码中做的将所有这些布局的边距和间距都设为0或一个非常小的值如1用于显示分隔线。然后通过为HeaderWidget设置border-bottom来模拟分隔线这样更容易控制。6.4 键盘导航失效无法用Tab键切换焦点到抽屉标题栏或者无法用空格键触发展开/收起。原因QPushButton默认支持键盘交互但需要获得焦点。如果父控件或兄弟控件拦截了焦点或者焦点策略设置不当就会失效。解决方案确保HeaderWidgetQPushButton设置了setFocusPolicy(Qt::StrongFocus)。在CustomDrawerBox的keyPressEvent中可以手动处理上下箭头键用于在多个抽屉项之间移动焦点。void CustomDrawerBox::keyPressEvent(QKeyEvent* event) { int currentIndex -1; // 找到当前拥有焦点的抽屉项索引 for (int i 0; i m_drawerItems.size(); i) { if (m_drawerItems.at(i)-headerWidget()-hasFocus()) { currentIndex i; break; } } switch (event-key()) { case Qt::Key_Up: if (currentIndex 0) { m_drawerItems.at(currentIndex - 1)-headerWidget()-setFocus(); } break; case Qt::Key_Down: if (currentIndex 0 currentIndex m_drawerItems.size() - 1) { m_drawerItems.at(currentIndex 1)-headerWidget()-setFocus(); } break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: if (currentIndex 0) { m_drawerItems.at(currentIndex)-toggle(); } break; default: QWidget::keyPressEvent(event); } }开发自定义控件是一个不断迭代和调试的过程。最好的调试方式就是写一个简单的测试程序把控件放进去实时观察它的行为和外观变化。多利用Qt Creator的调试器和“设计”模式能帮你快速定位问题所在。
返回列表