You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt中QComboBox、QSpinBox与QGraphicsScene联动实现动态甘特图问题

QComboBox+QSpinBox+QGraphicsScene动态甘特图实现方案

前置准备

首先给Dialog类添加必要成员,替换原有硬编码逻辑:

  • 定义坐标适配常量,和你现有绘制的坐标轴完全对齐,不需要修改已有的坐标轴代码:
    • 单个执行周期对应像素宽度:const int CYCLE_WIDTH = 30
    • 相邻进程行的垂直间距:const int ROW_HEIGHT = 45
    • 第一个进程标签的Y轴起始坐标:const int PROCESS_START_Y = -260
    • 甘特图矩形的X轴起始位置(和Y轴重合):const int GANTT_START_X = -435
  • 添加容器存动态生成的元素,避免内存泄漏和重复绘制:
    • QList<QWidget*> m_inputWidgetList:存每个进程对应的标签、周期输入QSpinBox
    • QList<QGraphicsItem*> m_ganttItemList:存动态生成的进程标签、甘特矩形、刻度等图元
    • int m_currentProcessCount:记录当前选中的进程总数
  • 删除构造函数里无用的QPainter painter(this)相关代码,QGraphicsScene图元通过自身接口绘制即可,不需要在Dialog构造中创建QPainter,否则会触发运行警告。

需求1:进程数量动态联动

针对QComboBox(ui->comboBox_2)的选择逻辑做如下实现:

  • 先给comboBox添加1~7的进程数选项,绑定currentIndexChanged(int)信号到自定义槽函数
  • 槽函数逻辑:
    1. 根据选中的序号计算当前需要展示的进程数,更新m_currentProcessCount变量
    2. 先把m_inputWidgetList里所有已创建的输入控件全部隐藏,不需要每次删除重建
    3. 循环遍历到当前需要的进程数:如果对应序号的输入控件还没创建,就动态生成P序号标签、执行周期输入QSpinBox(设置取值范围1~20即可),添加到对话框的输入区布局中,把控件指针存入m_inputWidgetList;如果已经创建过,直接设置为可见即可
    4. 调用公共的场景刷新函数,清理场景中旧的进程标签、甘特图块,只绘制当前选中数量的P1~Pn进程标签,位置和你原有硬编码的坐标完全一致

需求2:甘特图动态绘制

把原有on_pushButton_clicked里硬编码7个P标签的代码全部删除,替换为如下逻辑:

  1. 先调用公共场景刷新函数,清空旧图元,绘制当前数量的进程标签
  2. 遍历每个进程对应的QSpinBox,读取录入的执行周期值,存入顺序列表
  3. 根据当前选中的调度算法计算每个执行段的所属进程、起始周期、持续长度:FCFS就按进程顺序累计偏移,SJF按执行时间排序后计算,RR时间片轮转按时间片拆分进程执行段即可
  4. 按计算结果逐个绘制甘特矩形:
    • 矩形X坐标:GANTT_START_X + 起始周期 * CYCLE_WIDTH
    • 矩形Y坐标:PROCESS_START_Y + 进程序号 * ROW_HEIGHT - 15(矩形高度设30,和行标签居中对齐)
    • 矩形宽度:持续周期数 * CYCLE_WIDTH
    • 矩形高度:固定30
    • 给不同进程的矩形设置不同填充色,加1像素黑色描边,生成的矩形指针存入m_ganttItemList
  5. 可选优化:在每个矩形中心添加进程名、周期数文本,在X轴下方按CYCLE_WIDTH间隔绘制刻度和数字,和X轴“Broj ciklusa”标签对应。

核心代码示例

// Dialog头文件类内声明
private:
    const int CYCLE_WIDTH = 30;
    const int ROW_HEIGHT = 45;
    const int PROCESS_START_Y = -260;
    const int GANTT_START_X = -435;
    QList<QWidget*> m_inputWidgetList;
    QList<QGraphicsItem*> m_ganttItemList;
    int m_currentProcessCount = 1;

    void refreshProcessLabels();

// Dialog源文件实现
void Dialog::refreshProcessLabels()
{
    // 清空旧图元
    for(auto item : m_ganttItemList){
        scene->removeItem(item);
        delete item;
    }
    m_ganttItemList.clear();

    // 绘制对应数量的进程标签
    for(int i=0; i<m_currentProcessCount; i++){
        QGraphicsTextItem* proText = scene->addText(QString("P%1").arg(i+1), QFont("Arial",9));
        proText->setDefaultTextColor(Qt::blue);
        proText->setPos(-467, PROCESS_START_Y + i*ROW_HEIGHT);
        m_ganttItemList.append(proText);
    }
}

void Dialog::on_comboBox_2_currentIndexChanged(int index)
{
    m_currentProcessCount = index + 1;
    // 隐藏所有输入控件
    for(auto w : m_inputWidgetList){
        w->setVisible(false);
    }
    // 显示/创建对应数量的输入控件
    for(int i=0; i<m_currentProcessCount; i++){
        if(i >= m_inputWidgetList.size()/2){
            QLabel* label = new QLabel(QString("P%1 执行周期").arg(i+1), this);
            QSpinBox* burstSpin = new QSpinBox(this);
            burstSpin->setRange(1, 20);
            burstSpin->setObjectName(QString("burstSpin_%1").arg(i+1));
            ui->inputLayout->addWidget(label); // 替换为你自己的输入区布局名
            ui->inputLayout->addWidget(burstSpin);
            m_inputWidgetList.append(label);
            m_inputWidgetList.append(burstSpin);
        }
        m_inputWidgetList[2*i]->setVisible(true);
        m_inputWidgetList[2*i+1]->setVisible(true);
    }
    refreshProcessLabels();
}

void Dialog::on_pushButton_clicked()
{
    refreshProcessLabels();
    QList<int> burstTimes;
    for(int i=0; i<m_currentProcessCount; i++){
        QSpinBox* spin = findChild<QSpinBox*>(QString("burstSpin_%1").arg(i+1));
        burstTimes.append(spin->value());
    }

    // 示例为FCFS调度逻辑,其他算法替换这部分的偏移计算即可
    int currentOffset = 0;
    QColor fillColors[] = {Qt::green, Qt::yellow, Qt::cyan, QColor(255,128,0), Qt::magenta, Qt::gray, Qt::darkGreen};
    for(int i=0; i<m_currentProcessCount; i++){
        int burst = burstTimes[i];
        QGraphicsRectItem* rect = new QGraphicsRectItem(
            GANTT_START_X + currentOffset,
            PROCESS_START_Y + i*ROW_HEIGHT -15,
            burst * CYCLE_WIDTH,
            30
        );
        rect->setBrush(fillColors[i%7]);
        rect->setPen(QPen(Qt::black,1));
        scene->addItem(rect);
        m_ganttItemList.append(rect);

        // 矩形内标注文本
        QGraphicsTextItem* text = scene->addText(QString("P%1").arg(i+1));
        text->setPos(rect->rect().center().x() - text->boundingRect().width()/2,
                    rect->rect().center().y() - text->boundingRect().height()/2);
        m_ganttItemList.append(text);

        currentOffset += burst * CYCLE_WIDTH;
    }

    // 绘制X轴刻度
    for(int c=0; c<=currentOffset/CYCLE_WIDTH; c++){
        QGraphicsLineItem* tick = scene->addLine(
            GANTT_START_X + c*CYCLE_WIDTH, 50,
            GANTT_START_X + c*CYCLE_WIDTH, 55,
            QPen(Qt::blue,2)
        );
        QGraphicsTextItem* tickText = scene->addText(QString::number(c), QFont("Arial",9));
        tickText->setDefaultTextColor(Qt::blue);
        tickText->setPos(GANTT_START_X + c*CYCLE_WIDTH - 5, 58);
        m_ganttItemList.append(tick);
        m_ganttItemList.append(tickText);
    }
}

注意事项

  • 代码中的ui->inputLayout需要替换为你自己UI中放置进程输入控件的布局,提前在UI设计器中创建对应布局即可
  • 其他调度算法只需要替换按钮点击函数中执行段的计算逻辑,不需要修改绘制部分的代码,RR这类会拆分同一进程多段执行的算法,只需要保证每个矩形的Y坐标和所属进程的行对齐,X坐标按执行顺序累计偏移即可
  • 每次刷新场景时必须清理之前存在m_ganttItemList里的旧图元,否则会出现重复绘制的残留内容。

内容的提问来源于stack exchange,提问作者Don Marquez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 08:24:12