找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2006

积分

0

好友

277

主题
发表于 2025-12-25 06:40:22 | 查看: 30| 回复: 0

实现控件加载与拖曳后,构建一个功能完备的控件属性设计器的下一个关键挑战,是实现类似Qt Designer的交互体验:允许用户自由地拉伸控件大小和移动其位置。为此,我们专门开发了一个名为SelectWidget(描点跟随窗体)的辅助控件来实现这一核心功能。

实现原理与功能特点

SelectWidget的核心原理是为每个需要编辑的控件安装事件过滤器。当控件被选中时,会将其包裹在一个SelectWidget实例中。该实例通过监控鼠标事件,智能判断鼠标位置是否位于控件边缘的八个“描点”(手柄)区域,并根据鼠标拖动距离实时计算并调整被包裹控件的大小或位置。

该控件提供丰富的自定义选项:

  • 可定制外观:支持设置是否绘制描点、描点边距、颜色、尺寸及样式(正方形或圆形),以及选中时的边框宽度。
  • 高效交互:支持键盘上下左右方向键对控件进行像素级微调。
  • 便捷操作:支持Delete键快速删除选中的控件。
  • 八向拉伸:通过八个描点,支持从各个方向改变控件尺寸。

核心代码解析:事件过滤与描点计算

SelectWidget的功能主要通过重写eventFilterresizeEventmouseMoveEvent来实现。以下为关键代码段:

1. 事件过滤器 (eventFilter)
此函数处理来自被跟踪控件(widget)的各类事件,以及SelectWidget自身的键盘和鼠标事件,是实现移动和拉伸逻辑的中枢。

bool SelectWidget::eventFilter(QObject *watched, QEvent *event){
    if (watched == widget) {
        // 同步被跟踪控件的移动和大小变化
        if (event->type() == QEvent::Resize) {
            this->resize(this->widget->size() + QSize(padding * 2, padding * 2));
        } else if (event->type() == QEvent::Move) {
            this->move(this->widget->pos() - QPoint(padding, padding));
        }
    } else {
        // 处理键盘事件:方向键微移,Delete键删除
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Left) {
                this->move(this->pos() - QPoint(1, 0));
            } else if (keyEvent->key() == Qt::Key_Right) {
                this->move(this->pos() + QPoint(1, 0));
            } else if (keyEvent->key() == Qt::Key_Up) {
                this->move(this->pos() - QPoint(0, 1));
            } else if (keyEvent->key() == Qt::Key_Down) {
                this->move(this->pos() + QPoint(0, 1));
            } else if (keyEvent->key() == Qt::Key_Delete) {
                emit widgetDelete(widget);
                widget->deleteLater();
                this->deleteLater();
                widget = 0;
            }
            // 同步更新被包裹控件的位置和大小
            if (widget != 0) {
                widget->setGeometry(this->x() + padding, this->y() + padding, this->width() - padding * 2, this->height() - padding * 2);
            }
            return QWidget::eventFilter(watched, event);
        }

        // 处理鼠标事件:按下、移动、释放,实现拖拽和拉伸
        QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
        if (mouseEvent->type() == QEvent::MouseButtonPress) {
            // 记录初始状态
            rectX = this->x();
            rectY = this->y();
            rectW = this->width();
            rectH = this->height();
            lastPos = mouseEvent->pos();

            // 判断鼠标按下位置属于哪个描点区域
            if (rectLeft.contains(lastPos)) {
                pressedLeft = true;
            } else if (rectRight.contains(lastPos)) {
                pressedRight = true;
            } else if (rectTop.contains(lastPos)) {
                pressedTop = true;
            } else if (rectBottom.contains(lastPos)) {
                pressedBottom = true;
            } else if (rectLeftTop.contains(lastPos)) {
                pressedLeftTop = true;
            } else if (rectRightTop.contains(lastPos)) {
                pressedRightTop = true;
            } else if (rectLeftBottom.contains(lastPos)) {
                pressedLeftBottom = true;
            } else if (rectRightBottom.contains(lastPos)) {
                pressedRightBottom = true;
            } else {
                // 非描点区域,视为移动整个控件
                pressed = true;
            }
            if (widget != 0) {
                emit widgetPressed(widget);
            }
        } else if (mouseEvent->type() == QEvent::MouseMove) {
            QPoint pos = mouseEvent->pos();
            int dx = pos.x() - lastPos.x();
            int dy = pos.y() - lastPos.y();

            // 根据按下的区域标志,计算新的位置和大小
            if (pressed) {
                this->move(this->x() + dx, this->y() + dy);
            } else if (pressedLeft) {
                int resizeW = this->width() - dx;
                if (this->minimumWidth() <= resizeW) {
                    this->setGeometry(this->x() + dx, rectY, resizeW, rectH);
                }
            } else if (pressedRight) {
                this->setGeometry(rectX, rectY, rectW + dx, rectH);
            } // ... 其他六个方向的处理逻辑类似,此处省略以保持简洁

            // 同步更新被包裹控件
            if (widget != 0) {
                widget->setGeometry(this->x() + padding, this->y() + padding, this->width() - padding * 2, this->height() - padding * 2);
            }
        } else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
            // 重置所有按下状态标志
            pressed = false;
            pressedLeft = false;
            // ... 重置其他标志
            if (widget != 0) {
                emit widgetRelease(widget);
            }
        }
    }
    return QWidget::eventFilter(watched, event);
}

2. 描点区域计算 (resizeEvent)
SelectWidget大小变化时,需要重新计算八个描点的矩形区域,这些区域用于后续的鼠标命中测试。

void SelectWidget::resizeEvent(QResizeEvent *){
    int width = this->width();
    int height = this->height();
    int halfPoint = pointSize / 2;

    rectLeft = QRectF(0, height / 2 - halfPoint, pointSize, pointSize);
    rectTop = QRectF(width / 2 - halfPoint, 0, pointSize, pointSize);
    rectRight = QRectF(width - pointSize, height / 2 - halfPoint, pointSize, pointSize);
    rectBottom = QRectF(width / 2 - halfPoint, height - pointSize, pointSize, pointSize);

    rectLeftTop = QRectF(0, 0, pointSize, pointSize);
    rectRightTop = QRectF(width - pointSize, 0, pointSize, pointSize);
    rectLeftBottom = QRectF(0, height - pointSize, pointSize, pointSize);
    rectRightBottom = QRectF(width - pointSize, height - pointSize, pointSize, pointSize);
}

3. 鼠标形状更新 (mouseMoveEvent)
根据鼠标当前位置所处的描点区域,动态改变光标形状,为用户提供直观的操作提示。

void SelectWidget::mouseMoveEvent(QMouseEvent *e){
    QPoint p = e->pos();
    if (rectLeft.contains(p) || rectRight.contains(p)) {
        this->setCursor(Qt::SizeHorCursor); // 左右箭头
    } else if (rectTop.contains(p) || rectBottom.contains(p)) {
        this->setCursor(Qt::SizeVerCursor); // 上下箭头
    } else if (rectLeftTop.contains(p) || rectRightBottom.contains(p)) {
        this->setCursor(Qt::SizeFDiagCursor); // 左上-右下箭头
    } else if (rectRightTop.contains(p) || rectLeftBottom.contains(p)) {
        this->setCursor(Qt::SizeBDiagCursor); // 右上-左下箭头
    } else {
        this->setCursor(Qt::ArrowCursor); // 普通箭头
    }
}

效果展示

集成SelectWidget后,设计器实现了对画布上控件的精细操控。下图为控件拉伸与移动的演示效果:
控件拉伸移动演示

设计器完整功能概览

结合此前实现的插件加载与拖曳功能,本控件属性设计器已具备以下核心特性:

  1. 自动插件扫描:加载插件文件中的所有控件(内置超120个)并生成列表。
  2. 拖曳生成控件:从列表拖曳控件至画布,所见即所得。
  3. 动态属性编辑:右侧属性栏实时反映选中控件的属性,修改后立即生效。
  4. 国际化属性栏:独创的文字翻译映射机制,便于扩展多语言支持。
  5. 数据模拟与接入:支持滑动条、复选框、文本框三种方式模拟数据,并打通串口、网络、数据库多种真实数据源。
  6. 布局持久化:支持将当前画布布局导出为XML文件,并可重新导入加载。
  7. 跨平台兼容:纯Qt/C++编写,支持任意Qt版本、编译器及操作系统。

SelectWidget的引入,标志着该自定义控件设计器在交互体验上达到了实用化水平,为构建复杂的图形化配置工具奠定了坚实的基础。




上一篇:面试如何识别高绩效开发者?IT精英的6个核心特质
下一篇:FPGA+DAC实现可调信号源:基于DDS原理与Verilog的波形发生器设计实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:32 , Processed in 0.238397 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表