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:存每个进程对应的标签、周期输入QSpinBoxQList<QGraphicsItem*> m_ganttItemList:存动态生成的进程标签、甘特矩形、刻度等图元int m_currentProcessCount:记录当前选中的进程总数
- 删除构造函数里无用的
QPainter painter(this)相关代码,QGraphicsScene图元通过自身接口绘制即可,不需要在Dialog构造中创建QPainter,否则会触发运行警告。
需求1:进程数量动态联动
针对QComboBox(ui->comboBox_2)的选择逻辑做如下实现:
- 先给comboBox添加1~7的进程数选项,绑定
currentIndexChanged(int)信号到自定义槽函数 - 槽函数逻辑:
- 根据选中的序号计算当前需要展示的进程数,更新
m_currentProcessCount变量 - 先把
m_inputWidgetList里所有已创建的输入控件全部隐藏,不需要每次删除重建 - 循环遍历到当前需要的进程数:如果对应序号的输入控件还没创建,就动态生成P序号标签、执行周期输入QSpinBox(设置取值范围1~20即可),添加到对话框的输入区布局中,把控件指针存入
m_inputWidgetList;如果已经创建过,直接设置为可见即可 - 调用公共的场景刷新函数,清理场景中旧的进程标签、甘特图块,只绘制当前选中数量的P1~Pn进程标签,位置和你原有硬编码的坐标完全一致
- 根据选中的序号计算当前需要展示的进程数,更新
需求2:甘特图动态绘制
把原有on_pushButton_clicked里硬编码7个P标签的代码全部删除,替换为如下逻辑:
- 先调用公共场景刷新函数,清空旧图元,绘制当前数量的进程标签
- 遍历每个进程对应的QSpinBox,读取录入的执行周期值,存入顺序列表
- 根据当前选中的调度算法计算每个执行段的所属进程、起始周期、持续长度:FCFS就按进程顺序累计偏移,SJF按执行时间排序后计算,RR时间片轮转按时间片拆分进程执行段即可
- 按计算结果逐个绘制甘特矩形:
- 矩形X坐标:
GANTT_START_X + 起始周期 * CYCLE_WIDTH - 矩形Y坐标:
PROCESS_START_Y + 进程序号 * ROW_HEIGHT - 15(矩形高度设30,和行标签居中对齐) - 矩形宽度:
持续周期数 * CYCLE_WIDTH - 矩形高度:固定30
- 给不同进程的矩形设置不同填充色,加1像素黑色描边,生成的矩形指针存入
m_ganttItemList
- 矩形X坐标:
- 可选优化:在每个矩形中心添加进程名、周期数文本,在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
相关产品推荐
相关产品推荐

