使用Qt制作一个流程变更申请流程进度以及未读消息提醒

1.1加载界面:

界面要素:
成员信息
变更位置申请
接受消息列表
根据角色加载对应界面。

1.2发起变更申请:

  • 用户点击“发起变更申请”按钮。
  • 变更申请对话框可编辑,
  • 用户填写申请信息:
    申请方(自动填充)。
    目的方(选择老师或组长)。
    若为老师角色 则可选其他组(除副班长) 若为组长则固定老师
  • 提交申请:
  • 流程显示
  • 查看对应未读消息提醒
  • 处理未读信息

界面效果展示

在这里插入图片描述

  • 用户点击“提交”按钮,将申请信息发送至老师

1.3目标方(老师或组长)查看或待处理申请列表。

  • 选择某个申请查看详细信息同意或驳回申请:
    驳回 内容清空
    目标方选择操作:
    同意:
  • 更新申请状态 为“已同意”。
  • 更新流程操作1\2\3
    更新申请方的组别信息(如果目的方是老师)。
    通知申请方其申请已被同意。
    驳回:
    更新申请状态为“已驳回”。
    通知申请方其申请未被批准。
    记录变更历史:

记录所有变更申请的历史信息。
返回申请列表:

目标方返回变更申请列表,继续处理其他申请。

2 源代码展示

2.1 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>
#include <QTabWidget>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QStackedWidget>

#include "processprogressdisplaywidget.h"
#include "BubblelTipButton.h"
class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    void setupMembersListTab();  // 设置成员列表标签页
    void setupDynamicInfoTab();  // 设置动态信息标签页
    void fillTargetComboBox();    // 填充目标方ComboBox

private slots:
    void onMemberSelected(QTreeWidgetItem *item);  // 响应成员选择
    void onInitiateChangeClicked();                  // 响应发起变更按钮点击
    void onSendRequestClicked();                      // 响应发送请求按钮点击

    void OnPage1ButtonAddClicked();//列表按钮槽函数
    void OnPage2ButtonAddClicked();//消息按钮槽函数
    void confirmMessage(); // 确认消息的槽函数
    void rejectMessage(); // 驳回消息的槽函数

private:
    QStackedWidget *stackedWidget; // 堆叠窗口,用于切换不同页面
    QWidget *page1;
    QWidget *page2;
    QPushButton *page1Button;              // 发起变更按钮
    BubblelTipButton  *page2Button;              // 发起变更按钮
     QPushButton *page3Button;              // 发起变更按钮
    QTreeWidget *membersTreeWidget;                  // 成员树形控件
    QComboBox *applicantComboBox;                    // 申请方ComboBox
    QComboBox *targetComboBox;                       // 目标方ComboBox
    QPushButton *initiateChangeButton;              // 发起变更按钮
    QPushButton *sendButton;                         // 发送按钮
    QTabWidget *tabWidget;                           // 标签控件
    QTableWidget *dynamicInfoTable;                 // 动态信息显示表格

    ProcessProgressDisplayWidget* DisplayW;
     int messageCount; // 记录当前消息数量
};

#endif // MAINWINDOW_H


2.2 mainwindow.cpp

#include "mainwindow.h"
#include <QApplication>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QTreeWidget>
#include <QTableWidget>
#include <QHeaderView>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),messageCount(0)
{
    setWindowTitle("人员分配管理系统");

    QVBoxLayout *verticalLayout = new QVBoxLayout();
    verticalLayout->setSpacing(0);
    verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
    verticalLayout->setContentsMargins(0, 0, 0, 0);
    page1Button = new QPushButton(this);
    page1Button->setStyleSheet("QPushButton { "
                               "background-image: url(../Data/list.png); "
                               "border: none; "
                               "text-align: center; "
                               "color: white; "
                               "padding: 0px; "
                               "}");
    //page1Button->setText("链表");
    page1Button->setMinimumSize(QSize(30, 30));
    page1Button->setMaximumSize(QSize(30, 30));
    page2Button = new BubblelTipButton(this);
    page2Button->setText("消息");
    page2Button->setMinimumSize(QSize(40, 30));
    page2Button->setMaximumSize(QSize(40, 30));
    // page3Button = new QPushButton(this);
    QSpacerItem *verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
    verticalLayout->addWidget(page1Button);
    verticalLayout->addWidget(page2Button);
    // verticalLayout->addWidget(page3Button);
    verticalLayout->addItem(verticalSpacer);
    verticalLayout->setSpacing(2);

    QVBoxLayout* Vlayout = new QVBoxLayout();
    QHBoxLayout *layout = new QHBoxLayout();
    layout->addLayout(verticalLayout);
    layout->addLayout(Vlayout);
    layout->setStretch(1, 10);
    QWidget* CurrentWidget = new QWidget(this);
    CurrentWidget->setLayout(layout);
    setCentralWidget(CurrentWidget);

    stackedWidget = new QStackedWidget(this);
    page1 = new QWidget();
    page2 = new QWidget();

    setupMembersListTab();
    setupDynamicInfoTab();
    stackedWidget->addWidget(page1);
    stackedWidget->addWidget(page2);
    Vlayout->addWidget(stackedWidget);
    connect(page1Button, SIGNAL(clicked()), this, SLOT(OnPage1ButtonAddClicked()));
    connect(page2Button, SIGNAL(clicked()), this, SLOT(OnPage2ButtonAddClicked()));
}

MainWindow::~MainWindow() {
    // 析构函数
}

void MainWindow::setupMembersListTab() {
    QHBoxLayout *layout = new QHBoxLayout();

    // 左侧树形控件显示成员信息
    membersTreeWidget = new QTreeWidget(this);
    membersTreeWidget->setHeaderLabels(QStringList() << "成员信息");

    // 添加成员信息
    QTreeWidgetItem *teacherA = new QTreeWidgetItem(membersTreeWidget, QStringList() << "老师A");
    QTreeWidgetItem *groupLeaderB = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长B");
    QTreeWidgetItem *deputyB1 = new QTreeWidgetItem(groupLeaderB, QStringList() << "副组长B1");
    new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H2");
    new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H3");
    new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H4");

    QTreeWidgetItem *groupLeaderC = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长C");
    QTreeWidgetItem *deputyC1 = new QTreeWidgetItem(groupLeaderC, QStringList() << "副组长C1");
    new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H7");
    new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H8");
    new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H9");

    QTreeWidgetItem *groupLeaderD = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长D");
    QTreeWidgetItem *deputyD1 = new QTreeWidgetItem(groupLeaderD, QStringList() << "副组长D1");
    new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H10");
    new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H11");
    new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H12");

    layout->addWidget(membersTreeWidget, 1);

    // 右侧变更申请信息区域
    QWidget *requestWidget = new QWidget(this);
    QVBoxLayout *requestLayout = new QVBoxLayout(requestWidget);

    applicantComboBox = new QComboBox(this);
    applicantComboBox->setEditable(true);
    targetComboBox = new QComboBox(this);
    sendButton = new QPushButton("发送申请", this);
    initiateChangeButton = new QPushButton("发起变更", this);
    initiateChangeButton->setEnabled(false); // 禁用发起变更按钮
    applicantComboBox->setEnabled(false);
    targetComboBox->setEnabled(false);
    sendButton->setEnabled(false);  // 初始禁用发送按钮

    // 添加布局
    QHBoxLayout *initiateLayout = new QHBoxLayout();
    initiateLayout->addWidget(new QLabel("变更位置申请信息", this));
    initiateLayout->addStretch();
    initiateLayout->addWidget(initiateChangeButton);

    requestLayout->addLayout(initiateLayout);
    DisplayW = new ProcessProgressDisplayWidget(this);
    DisplayW->setProcessList(QStringList()<<"编辑信息"<<"选择源"<<"待反馈");
    DisplayW->setCurrentStep(0);
    requestLayout->addWidget(DisplayW);

    requestLayout->addWidget(applicantComboBox);
    requestLayout->addWidget(targetComboBox);
    requestLayout->addWidget(sendButton);

    layout->addWidget(requestWidget, 3);

    connect(membersTreeWidget, &QTreeWidget::itemClicked, this, &MainWindow::onMemberSelected);
    connect(initiateChangeButton, &QPushButton::clicked, this, &MainWindow::onInitiateChangeClicked);
    connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendRequestClicked);

    page1->setLayout(layout);
}

void MainWindow::setupDynamicInfoTab() {
    QVBoxLayout *layout = new QVBoxLayout();

    dynamicInfoTable = new QTableWidget(this);
    dynamicInfoTable->setColumnCount(4);
    dynamicInfoTable->setHorizontalHeaderLabels(QStringList() << "申请方" << "目标方" << "申请信息" << "状态");
    // 设置最后一列自动填充
    dynamicInfoTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    layout->addWidget(dynamicInfoTable);
    page2->setLayout(layout);
}

void MainWindow::fillTargetComboBox() {
    targetComboBox->clear();
    for (int i = 0; i < membersTreeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = membersTreeWidget->topLevelItem(i);
        targetComboBox->addItem(item->text(0));
        for (int j = 0; j < item->childCount(); ++j) {
            QTreeWidgetItem *child = item->child(j);
            QString memberName = child->text(0);
            // 检查成员是否为副组长
            if (!memberName.contains("副组长")) { // 过滤副组长
                targetComboBox->addItem(memberName); // 添加到目标组合框
            }
        }
    }
}

void MainWindow::onMemberSelected(QTreeWidgetItem *item) {
    if (item) {
        applicantComboBox->setCurrentText(item->text(0));  // 设置申请方
    }
    DisplayW->setCurrentStep(0);
    applicantComboBox->setEnabled(false);
    targetComboBox->setEnabled(false);
    sendButton->setEnabled(false);  // 启用发送按钮
    // 判断是否选择了副组长
    QString selectedItemText = item->text(0);
    if (selectedItemText.contains("副组长")) { // 假设副组长的名称包含“副组长”
        initiateChangeButton->setEnabled(false); // 禁用发起变更按钮
    } else {
        initiateChangeButton->setEnabled(true); // 启用发起变更按钮
    }

}

void MainWindow::onInitiateChangeClicked() {
    applicantComboBox->setEnabled(true);
    targetComboBox->setEnabled(true);
    sendButton->setEnabled(true);  // 启用发送按钮
    fillTargetComboBox();  // 填充目标方下拉框
    DisplayW->setCurrentStep(1);
}

void MainWindow::onSendRequestClicked() {
    int rowCount = dynamicInfoTable->rowCount();
    QString sender = "H2";
    QString destination = "D";
    QString reason = "位置变更申请";

    // Update Table Widget
    int row = dynamicInfoTable->rowCount();
    dynamicInfoTable->insertRow(row);
    dynamicInfoTable->setItem(row, 0, new QTableWidgetItem(sender));
    dynamicInfoTable->setItem(row, 1, new QTableWidgetItem(destination));
    dynamicInfoTable->setItem(row, 2, new QTableWidgetItem(reason));
    dynamicInfoTable->setRowHeight(row, 50); // 设置其他行高度为50
    // Status with buttons
    QWidget *statusWidget = new QWidget(this);
    QHBoxLayout *statusLayout = new QHBoxLayout(statusWidget);
    QPushButton *confirmButton = new QPushButton("确认");
    QPushButton *rejectButton = new QPushButton("驳回");

    // Set row and column data using properties
    confirmButton->setProperty("row", row);
    confirmButton->setProperty("col", 3);
    rejectButton->setProperty("row", row);
    rejectButton->setProperty("col", 3);
    // Add buttons to the layout
    statusLayout->addWidget(confirmButton);
    statusLayout->addWidget(rejectButton);
    statusWidget->setLayout(statusLayout);
    dynamicInfoTable->setCellWidget(row, 3, statusWidget);


    connect(confirmButton, SIGNAL(clicked()), this, SLOT(confirmMessage()));
    connect(rejectButton, SIGNAL(clicked()), this, SLOT(rejectMessage()));

    // Update message count
    messageCount++;
    page2Button->setMsgNumber(messageCount);

    applicantComboBox->clear();
    targetComboBox->clear();
    sendButton->setEnabled(false);  // 发送后禁用按钮
    DisplayW->setCurrentStep(2);
}

void MainWindow::OnPage1ButtonAddClicked()
{
    stackedWidget->setCurrentIndex(0);
}

void MainWindow::OnPage2ButtonAddClicked()
{
    stackedWidget->setCurrentIndex(1);
}

void MainWindow::confirmMessage()
{
    QPushButton *button = qobject_cast<QPushButton *>(sender());
    int row = button->property("row").toInt(); // 获取行索引
    int col = 3; // 状态列

    dynamicInfoTable->removeCellWidget(row, 3); // 删除旧的按钮 widget
    dynamicInfoTable->setItem(row, 3, new QTableWidgetItem("已确认"));
    messageCount--;
    page2Button->setMsgNumber(messageCount);
    stackedWidget->setCurrentIndex(0);
}

void MainWindow::rejectMessage()
{
    QPushButton *button = qobject_cast<QPushButton *>(sender());
    int row = button->property("row").toInt(); // 获取行索引
    int col = 3; // 状态列
    dynamicInfoTable->removeCellWidget(row, 3); // 删除旧的按钮 widget
    dynamicInfoTable->setItem(row, 3, new QTableWidgetItem("已驳回"));
    messageCount--;
    page2Button->setMsgNumber(messageCount);
    stackedWidget->setCurrentIndex(1);
}

2.3 processprogressdisplaywidget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

//流程进度展示控件
class ProcessProgressDisplayWidget : public QWidget
{
    Q_OBJECT

public:
    ProcessProgressDisplayWidget(QWidget *parent = nullptr);
    ~ProcessProgressDisplayWidget()override;
    void setProcessList(const QStringList &value);
    void setCurrentStep(int value);

protected:
    void paintEvent(QPaintEvent *event)override;

private:
    QStringList processList;
    int currentStep{1};
    QColor lineColor{"#C0C4CC"};
    QColor stepsCompletedColor{"#41CD52"};//已完成步骤的颜色
    QColor stepUncompleteColor{"#333333"};//未完成步骤的颜色
    QColor currentStepColor{"#FC5531"};//当前步骤颜色
};
#endif // WIDGET_H


2.4 processprogressdisplaywidget.cpp

实现了一个自定义的小部件,用于展示一个过程进度条,并显示各步骤的圆形节点

  • drawSmallCircle 是一个辅助函数,用于绘制带渐变颜色的圆形,以及圆形内居中的文本
    函数通过 QPainter::save() 和 QPainter::restore() 来确保绘制时的状态不影响外部环境。
    使用了 QLinearGradient 创建圆的渐变效果,增加视觉层次感
    根据控件的宽高、步骤数量,计算出每个节点圆圈的半径、间距和位置。
  • 循环遍历每个步骤节点,根据 currentStep 来设置不同状态(已完成、当前步骤、未完成)的颜色,并绘制连接线和文本。
    每个步骤使用 drawSmallCircle 函数绘制,并在圆圈下方显示步骤名称
#include "processprogressdisplaywidget.h"
#include <QPainter>
#include <QPaintEvent>
#include <QDebug>

ProcessProgressDisplayWidget::ProcessProgressDisplayWidget(QWidget *parent)
    : QWidget(parent)
{
    setPalette(Qt::white); // 设置背景为白色
}

ProcessProgressDisplayWidget::~ProcessProgressDisplayWidget()
{
}

//drawSmallCircle 是一个辅助函数,用于绘制带渐变颜色的圆形,以及圆形内居中的文本
void drawSmallCircle(QPainter * painter, int x, int y,int radius,const QColor & color,const QString & text)
{
    QRect boundingRect = QRect(-radius,-radius,radius * 2,radius * 2);// 定义圆的边界矩形

    painter->save();// 保存当前的 QPainter 状态
    painter->translate(x,y);// 将绘制中心平移到 (x, y)

     // 创建线性渐变效果,从矩形的左上角到右下角
    QLinearGradient linearGradient(boundingRect.topLeft(),boundingRect.bottomRight());
    linearGradient.setColorAt(0.0,color); // 渐变起始颜色
    linearGradient.setColorAt(1.0,color.lighter()); // 渐变结束颜色

    painter->setBrush(linearGradient);// 设置画刷为线性渐变
    painter->setPen(QPen(QBrush(linearGradient), 3,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));// 设置画笔,使用渐变的笔刷和圆角

    painter->drawEllipse(boundingRect);// 绘制圆形

    painter->setBrush(Qt::white);// 设置画刷为白色,用于绘制圆内部
    painter->drawEllipse(boundingRect.adjusted(1,1,-1,-1));// 绘制稍微缩小的白色圆,制造圆边效果

    painter->setPen(color);// 设置画笔颜色
    painter->drawText(boundingRect,Qt::AlignCenter,text);// 在圆形内居中绘制文字

    painter->restore();// 恢复 QPainter 状态
}

void ProcessProgressDisplayWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);// 创建 QPainter 对象,用于绘制控件
    painter.setRenderHint(QPainter::Antialiasing,true);// 开启反锯齿
    auto font = painter.font();
    font.setPixelSize(20);// 设置字体大小为20像素
    font.setBold(true);// 设置字体为粗体
    painter.setFont(font);

    auto rect = event->rect(); // 获取控件的矩形区域
    auto width = rect.width(); // 获取控件的宽度
    auto height = rect.height();// 获取控件的高度

    int lineY = static_cast<int>(height * 0.3) ;// 进度条的纵坐标位置
    int startX = 80;// 起始的横坐标
    int size = processList.size();// 获取步骤数量

    int radius = 20;// 圆的半径
    int spacerLenth = (width - 2 * startX - (radius * 2 * size)) / (size - 1);// 计算每两个圆之间的间距
    for(int i = 0;i < size;++i)
    {
        // 计算每个圆心的横坐标
        int x = startX + radius + (spacerLenth * i) + (radius * 2 * i);
        QColor * color;
        if(i < (size - 1))
        {
            painter.save();
            if(i < currentStep)
            {
                color = &stepsCompletedColor;// 已完成步骤的颜色
            }
            else
            {
                color = &lineColor;// 还未完成步骤的颜色
            }
            painter.setPen(QPen(*color,3));// 设置画笔颜色
            // 绘制连接两个圆的线条
            painter.drawLine(QPoint(x + radius,lineY),QPoint(x + radius + spacerLenth,lineY));
            painter.restore();
        }
        // 设置当前步骤、已完成步骤和未完成步骤的颜色
        if(i < currentStep)
        {
            color = &stepsCompletedColor; //已完成步骤
        }
        else if(i == currentStep)
        {
            color = &currentStepColor; //当前步骤
        }
        else
        {
            color = &stepUncompleteColor; //未完成步骤
        }
        // 绘制每个步骤的圆圈和数字
        drawSmallCircle(&painter,x,lineY+1,radius,*color,QString::number(i+1));

         // 计算文本框的宽度
        int textRectW;
        if(i == 0 || i == (size - 1))
        {
            textRectW = 2 * (startX - 20) + radius * 2;
        }
        else
        {
            textRectW = spacerLenth + radius * 2;
        }

        // 计算文本框的起始横坐标
        int rextRectX;
        if(i == 0)
        {
            rextRectX = 20;
        }
        else if(i == (size - 1))
        {
            rextRectX = startX + i * (radius * 2) + spacerLenth * (i - 1) + (spacerLenth - (textRectW / 2) + radius);
        }
        else
        {
            rextRectX = startX + spacerLenth/2 + spacerLenth * (i - 1) + i * 2 * radius;
        }

         // 计算文本框的位置和大小
        QRect textRect = QRect(rextRectX,
                               lineY + radius * 2,
                               textRectW,
                               height - lineY - radius * 2);
        // 设置画笔颜色
        painter.setPen(*color);
        // 绘制步骤名称
        painter.drawText(textRect,Qt::AlignTop | Qt::AlignHCenter,processList[i]);
    }
}

//设置当前步骤
void ProcessProgressDisplayWidget::setCurrentStep(int value)
{
    currentStep = value;
    update();
}

void ProcessProgressDisplayWidget::setProcessList(const QStringList &value)
{
    processList = value;
    currentStep = 0;
    update();
}

消息提示红点BubblelTipButton.h


#pragma once
 
#include <QPushButton>
 
class BubblelTipButton : public QPushButton
{
	Q_OBJECT
 
public:
	BubblelTipButton(QWidget *parent);
	~BubblelTipButton();
 
	void setMsgNumber(int number);
 
protected:
	virtual void paintEvent(QPaintEvent * event);
 
private:
	int bubbleWidth;
	int m_msgNumber;
};

消息提示红点BubblelTipButton.cpp

#include <QPainter>
#include <QIcon>
#include "BubblelTipButton.h"
 
BubblelTipButton::BubblelTipButton(QWidget *parent)
	: QPushButton(parent)
	, bubbleWidth(20)
	, m_msgNumber(0)
{
}
 
BubblelTipButton::~BubblelTipButton()
{
}
 
void BubblelTipButton::setMsgNumber(int number)
{
	m_msgNumber = number;
	update();
}
 
void BubblelTipButton::paintEvent(QPaintEvent * event)
{
	QPainter painter(this);
	QRect rt = rect();
	painter.setRenderHint(QPainter::Antialiasing, true);
 
	QPoint pointMsg(0, 15);
	QPixmap pixmapMsg(QString::fromLocal8Bit("../Data/TestImage/message.png"));
	//painter.drawPixmap(pointMsg, pixmapMsg);
	QRect rt2 = QRect(rt.left(), rt.top(), rt.right() - 10, rt.bottom());
	painter.drawPixmap(rt2, pixmapMsg); // 在按钮矩形区域内绘制图片  
	if (m_msgNumber > 0)
	{
		if (m_msgNumber > 9)
		{
			bubbleWidth = 23;
		}
		if (m_msgNumber > 99)
		{
			bubbleWidth = 27;
		}
		
		QRect rt1 = QRect(rt.right() - bubbleWidth, rt.top(), bubbleWidth, bubbleWidth);
		painter.setPen(Qt::red);
		painter.setBrush(QBrush(Qt::red));
		painter.drawEllipse(rt1);
 
		painter.setPen(Qt::white);
		painter.drawText(rt1, Qt::AlignCenter, QString::number(m_msgNumber));
	}
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

2疑难点

int spacerLenth = (width - 2 * startX - (radius * 2 * size)) / (size - 1); 这行用于计算进度条上两个圆形之间的间距

2.1参数解释:

  • width: 窗口的总宽度。
  • startX: 第一个圆距离窗口左侧的偏移量。
  • radius: 圆的半径。
  • size: 总共的步骤数,也就是圆的数量

2.2 公式解释:

1.width - 2 * startX:这是去掉两边空白区域后,绘制所有圆和它们之间间距的总可用宽度。
2.radius * 2 * size:这是所有圆形的总宽度。因为每个圆的直径是 2 * radius,
所以总共有 size 个圆,它们的总宽度为 radius * 2 * size。
3.width - 2 * startX - (radius * 2 * size):这是除去圆形本身所占的宽度后,剩余的可用宽度(用于间距的总宽度)。
4.(size - 1):总共 size 个圆,那么圆与圆之间的间距总共会有 size - 1 个。

2.3 示例说明:

假设:

width = 600 (窗口宽度为600像素)
startX = 80 (圆形距离窗口左边和右边各80像素的空白)
radius = 20 (每个圆的半径为20像素)
size = 5 (总共有5个步骤/圆)

2.3.1计算可用绘制区域宽度

width−2∗startX=600−2∗80=440
去掉两边的空白区域后,可用宽度为440像素

2.3.2计算所有圆形所占的总宽度

radius∗2∗size=20∗2∗5=200
5个圆形的总宽度为200像素

2.3.3计算用于间距的总宽度

440−200=240
在所有圆形绘制完后,剩下240像素的空间用于圆之间的间距

2.3.4计算圆之间的间距

spacerLenth= 240/(5-1) = 240/4 = 60
所以,两个圆形之间的间距为60像素

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910963.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

域名邮箱推荐:安全与稳定的邮件域名邮箱!

域名邮箱推荐及绑定攻略&#xff1f;最好用的域名邮箱服务推荐&#xff1f; 域名邮箱&#xff0c;作为一种个性化且专业的电子邮件服务&#xff0c;越来越受到企业和个人的青睐。烽火将详细介绍域名邮箱登录的全过程&#xff0c;从注册到登录&#xff0c;帮助您轻松掌握这一重…

IDEA:设置类标签栏多行显示

使用场景&#xff1a; 当我们打开的类超出一行&#xff0c;多出来的类会隐藏或者关掉&#xff0c;不利于我们开发。 解决方案&#xff1a; 1.设置多行显示 2.效果

高级图像处理工具

图像处理-高级 1、功能概览 随着社交媒体的普及和个人创作需求的增长&#xff0c;图像处理成为了日常生活中不可或缺的一部分。无论是专业的设计师还是爱好者&#xff0c;都需要一款强大的工具来帮助他们完成各种任务。今天&#xff0c;我们将介绍一款基于Python开发的高级图…

江协科技STM32学习- P38 软件SPI读写W25Q64

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

P5665 [CSP-S2019] 划分

P5665 [CSP-S2019] 划分 难度&#xff1a;省选/NOI-。 考点&#xff1a;单调队列、贪心、前缀和。 题意&#xff1a; 没有题目大意&#xff0c;本题题目描述较长&#xff0c;认真阅读每一个信息。 ​ 这个题的样例有 n n n 组数据&#xff0c;数据从 1 ∼ n 1 \sim n 1∼n…

ThreadX在STM32上的移植:F1,F4通用启动文件tx_initialize_low_level.s

在嵌入式系统开发中&#xff0c;实时操作系统&#xff08;RTOS&#xff09;的选择对于系统性能和稳定性至关重要。ThreadX是一种广泛使用的RTOS&#xff0c;它以其小巧、快速和可靠而闻名。在本文中&#xff0c;我们将探讨如何将ThreadX移植到STM32微控制器上&#xff0c;特别是…

RTT 内核基础学习

RT-Thread 内核介绍 内核是操作系统的核心&#xff0c;负责管理系统的线程、线程间通信、系统时钟、中断以及内存等。 内核位于硬件层之上&#xff0c;内核部分包括内核库、实时内核实现。 内核库是为了保证内核能够独立运行的一套小型的类似C库的函数实现子集。 这部分根据编…

qt QPixmapCache详解

1、概述 QPixmapCache是Qt框架中提供的一个功能强大的图像缓存管理工具类。它允许开发者在全局范围内缓存QPixmap对象&#xff0c;从而有效减少图像的重复加载&#xff0c;提高图像加载和显示的效率。这对于需要频繁加载和显示图像的用户界面应用来说尤为重要&#xff0c;能够…

纯css制作声波扩散动画、js+css3波纹催眠动画特效、【css3动画】圆波扩散效果、雷达光波效果完整代码

一、纯css制作声波扩散动画 参考文章&#xff1a;纯css制作声波扩散动画 播放效果通过音频状态来控制 效果如下 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>波纹动画特效…

CocosCreator 构建透明背景应用(最新版!!!)

文章目录 透明原理补充设置截图以及代码step1: electron-js mian.jsstep2:ENABLE_TRANSPARENT_CANVASstep3:SOLID_COLOR Transparentstep:4 Build Web phonestep5:package electron-js & change body background-color 效果图补充 透明原理 使用Cocos creator 做桌面应用开…

在数据抓取的时候,短效IP比长效IP有哪些优势?

在数据抓取领域&#xff0c;代理IP的选择对于任务的成功率和效率至关重要。短效IP和长效IP各有其特点和适用场景&#xff0c;但在数据抓取过程中&#xff0c;短效IP因其独特的优势而受到青睐。本文将和大家一起探讨短效IP在数据抓取中相比长效IP的优势。 短效IP的定义与特点 …

FTP文件传输操作步骤

FTP文件传输操作步骤 步骤一&#xff1a;运行FTPServer.exe程序 步骤二、设置用户名和密码密码 步骤三、设置共享文件夹 步骤五、点击启动 步骤六、查看电脑ip(FTP server端) 步骤七、连接FTP 此电脑&#xff0c;地址栏输入&#xff1a;ftp://192.168.1.100 回车即可&…

【react使用AES对称加密的实现】

react使用AES对称加密的实现 前言使用CryptoJS库密钥存放加密方法解密方法结语 前言 项目中要求敏感信息怕被抓包泄密必须进行加密传输处理&#xff0c;普通的md5加密虽然能解决传输问题&#xff0c;但是项目中有权限的用户是需要查看数据进行查询的&#xff0c;所以就不能直接…

【网络原理】关于HTTP状态码以及请求的构造的哪些事

前言 &#x1f31f;&#x1f31f;本期讲解关于HTTP协议的重要的机制~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不…

苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了

今天凌晨&#xff0c;苹果正式发布了iOS 18.2首个公测版&#xff0c;将更多AI功能大批量推送给用户。其中最重要的就是Siri接入ChatGPT了&#xff0c;用户不必创建账户就可以免费使用ChatGPT&#xff0c;Siri将利用ChatGPT的专业知识回答用户问题&#xff0c;并在查询之前征求用…

前端 Canvas 绘画 总结

目录 一、使用案例 1、基础使用案例 2、基本案例改为直接JS实现 二、相关资料 1、API教程文档 2、炫酷案例 一、使用案例 1、基础使用案例 使用Canvas的基本步骤&#xff1a; 1、需要一个canvas标签 2、需要获取 画笔 对象 3、使用canvas提供的api进行绘图 <!--…

高级 <HarmonyOS主题课>借助AR引擎帮助应用实现虚拟与现实交互的能力的课后习题

持而盈之&#xff0c;不如其已&#xff1b; 揣而锐之&#xff0c;不可长保。 金玉满堂&#xff0c;莫之能守&#xff1b; 富贵而骄&#xff0c;自遗其咎。 功成身退&#xff0c;天之道也。 VR (Virtual Reality): 虚拟现实技术 AR (Augmented Reality): 增强现实) XR.(Extend…

tp接口 入口文件 500 错误原因

一、描述 二、可能的原因 1、runtime目录没权限 2、关闭了Tp记录日志的功能 3、关闭debug调试模式 4、关闭了debug模式还是报错 一、描述 Thinkphp项目本地正常&#xff0c;上传到线上后静态文件访问正常&#xff0c;访问tp接口报500错误。 经调试发现&#xff0c;在php入…

解决:ros进行gazebo仿真,rviz没有显示传感器数据

目录 前言解决总结 前言 看了很多urdf、xacro文件的编写&#xff0c;每次看了都觉得自己会了&#xff0c;然后自己写一点&#xff0c;就是废物了。 在我这里的案例是&#xff0c;我在一个大方块上面&#xff0c;添加了两个VLP-16的雷达&#xff0c;然后我想获取雷达扫描的数据…

C语言 | Leetcode C语言题解之第546题移除盒子

题目&#xff1a; 题解&#xff1a; int dp[100][100][100];int calculatePoints(int* boxes, int l, int r, int k) {if (l > r) {return 0;}if (dp[l][r][k] 0) {int r1 r, k1 k;while (r1 > l && boxes[r1] boxes[r1 - 1]) {r1--;k1;}dp[l][r][k] calcu…