基于Qt实现的电路图编辑程序
参考Qt Diagram Scene Example
图场景示例是一个应用程序,您可以在其中创建流程图。可以添加流程图形状和文本,并通过箭头连接形状,如上图所示。形状、箭头和文本可以被赋予不同的颜色,并且可以更改文本的字体、样式和下划线。
电路图编辑程序具有以下功能:
1. 自定义元件
2. 实现电路图的导入,导出功能
3. 实现绘制动作的撤销,恢复功能
4. 实现元件的搜索功能
5. 实现元件的连接及动态连接
6. 实现元件的右键功能
实现自定义电路元件绘制
通过实现继承类来创建这样的自定义电路元件, 电源,开关,电阻,电容,电感等.并自动生成对应元件的图标. 具体代码如下:
DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu,
QGraphicsItem *parent)
: QGraphicsRectItem(parent)
{
myDiagramType = diagramType;
myContextMenu = contextMenu;
point_left = QPoint( -120, 0 );
point_right = QPoint( 120, 0 );
switch (myDiagramType) {
case Power:
myPath.moveTo(-120, 0);
myPath.lineTo(-10, 0);
myPath.moveTo(-10, 10);
myPath.lineTo(-10, -10);
myPath.moveTo(10, 20);
myPath.lineTo(10, -20);
myPath.moveTo(10, 0);
myPath.lineTo(120, 0);
break;
case Capacitor:
myPath.moveTo(-120, 0);
myPath.lineTo(-10, 0);
myPath.moveTo(-10, 20);
myPath.lineTo(-10, -20);
myPath.moveTo(10, 20);
myPath.lineTo(10, -20);
myPath.moveTo(10, 0);
myPath.lineTo(120, 0);
break;
case Resistor:
myPath.moveTo(-120, 0);
myPath.lineTo(-40, 0);
myPath.lineTo(-40, -20);
myPath.lineTo(40, -20);
myPath.lineTo(40, 20);
myPath.lineTo(-40, 20);
myPath.lineTo(-40, 0);
myPath.moveTo(40, 0);
myPath.lineTo(120, 0);
break;
case GND:
myPath.moveTo(0, -80);
myPath.lineTo(0, 40);
myPath.moveTo(-20,40);
myPath.lineTo(20,40);
myPath.moveTo(-15, 60);
myPath.lineTo(15, 60);
myPath.moveTo(-10, 78);
myPath.lineTo(10, 78);
point_left = QPoint( 0, -80 );
break;
case Inductor:
myPath.moveTo(-120, 0);
myPath.lineTo(-40, 0);
myPath.cubicTo( -40,0, -30,-20, -20,0 );
myPath.cubicTo( -20,0, -10,-20, 0,0 );
myPath.cubicTo( 0,0, 10,-20, 20,0 );
myPath.cubicTo( 20,0, 30,-20, 40,0 );
myPath.moveTo(40,0);
myPath.lineTo(120,0);
break;
}
setRect( QRect( -125, -125, 250, 250 ) );
通过继承QGraphicsRectItem图形项, 使用QPainterPath来绘图各种元件.
实现对应元件的图标,代码如下:
QPixmap DiagramItem::image() const
{
QPixmap pixmap(250, 250);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setPen(QPen(Qt::black, 8));
painter.translate(125, 125);
painter.drawPath( myPath );
return pixmap;
}
实现电路图的导入导出功能
使用QSettings来对图元进行保存, 需要保存各个图形项的位置,类型,角度,连接线等信息, 实现电路图的导出功能. 代码如下:
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./1.ini", tr("(*.ini)"));
QSettings settings( fileName, QSettings::IniFormat);
settings.setIniCodec("utf-8");
settings.clear();
int cnt = 0;
QMap< QGraphicsItem*, QString > map_item;
foreach ( auto item, scene->items() ){
QString str = "item" + QString::number(++cnt);
settings.beginGroup(str);
settings.setValue( "pos", item->pos() );
settings.setValue( "type", item->type() );
settings.setValue( "degree", item->rotation() );
map_item.insert( item, str );
if( item->type() == Arrow::Type ){
auto ln = (Arrow*)item;
settings.setValue( "Arrow", ln->line() );
settings.setValue( "startItem", map_item.value( ln->startItem() ) );
settings.setValue( "endItem", map_item.value( ln->endItem() ) );
settings.setValue( "startPoint", ln->startPoint() );
settings.setValue( "endPoint", ln->endPoint() );
}
else if( item->type() == DiagramItem::Type ){
auto ln = (DiagramItem*)item;
settings.setValue( "DiagramType", ln->diagramType() );
}
else if( item->type() == DiagramTextItem::Type ){
auto text = (DiagramTextItem*)item;
settings.setValue( "text", text->toPlainText().toLocal8Bit() );
}
settings.endGroup();
}
读取ini文件,获取每个图形项的位置,类型,角度,连接线等信息, 并创建对应的图形项, 即可实现电路图的导入功能. 代码如下:
QString fileName = QFileDialog::getOpenFileName(this, tr("Opem File"), "./", tr("(*.ini)"));
if(fileName.isEmpty()) return;
QSettings settings( fileName, QSettings::IniFormat);
scene->clear();
int cnt = 0;
QMap< QString, DiagramItem* > map_item;
while(1){
QString item = "item" + QString::number(++cnt);
if( !settings.contains(item+"/type") )
break;
int type = settings.value( item+"/type" ).toInt();
if( type == DiagramItem::Type ){
int DiagramType = settings.value( item+"/DiagramType" ).toInt();
auto item0 = new DiagramItem( DiagramItem::DiagramType( DiagramType ), ui->menuItem );
// item0->setPen( QPen( Qt::black, 3 ) );
item0->setPos( settings.value( item+"/pos" ).toPointF() );
item0->setRotation( settings.value( item+"/degree" ).toDouble() );
scene->addItem( item0 );
map_item.insert( item, item0 );
}
else if( type == Arrow::Type ){
auto startItem = map_item.value( settings.value( item+"/startItem").toString() );
auto endItem = map_item.value( settings.value( item+"/endItem").toString() );
if( startItem==nullptr || endItem==nullptr ) continue;
auto arrow = new Arrow( startItem, endItem );
arrow->setStartPoint( settings.value(item+"/startPoint").toPointF() );
arrow->setEndPoint( settings.value(item+"/endPoint").toPointF() );
startItem->addArrow(arrow);
endItem->addArrow(arrow);
arrow->setZValue(-1000.0);
scene->addItem(arrow);
arrow->updatePosition();
}
else if( type == DiagramTextItem::Type ){
auto item0 = new DiagramTextItem();
item0->setPos( settings.value( item+"/pos" ).toPointF() );
item0->setRotation( settings.value( item+"/degree" ).toDouble() );
item0->setPlainText( QString::fromLocal8Bit( settings.value( item+"/text" ).toByteArray() ) );
scene->addItem( item0 );
}
}
实现绘制动作的撤销,恢复功能
使用QUndoCommand来实现绘制动作的撤销恢复功能.
定义每一个动作, 并加入QUndoStack, 通过
QAction *createUndoAction(QObject *parent, const QString &prefix = QString()) const;
QAction *createRedoAction(QObject *parent, const QString &prefix = QString()) const;
实现撤销和恢复功能. 具体的工作的代码如下:
class MoveCommand : public QUndoCommand
{
public:
enum { Id = 1234 };
MoveCommand(DiagramItem *diagramItem, const QPointF &oldPos,
QUndoCommand *parent = 0);
void undo() override;
void redo() override;
bool mergeWith(const QUndoCommand *command) override;
int id() const override { return Id; }
private:
DiagramItem *myDiagramItem;
QPointF myOldPos;
QPointF newPos;
};
class DeleteCommand : public QUndoCommand
{
public:
explicit DeleteCommand(QGraphicsScene *graphicsScene, QUndoCommand *parent = 0);
void undo() override;
void redo() override;
private:
QList<QGraphicsItem *> list_item;
QMap<QGraphicsItem *, QList<Arrow *> > map_item_arrow;
QGraphicsScene *myGraphicsScene;
};
class AddCommand : public QUndoCommand
{
public:
AddCommand(DiagramItem *item, QGraphicsScene *graphicsScene,
QUndoCommand *parent = 0);
~AddCommand();
void undo() override;
void redo() override;
private:
DiagramItem *myDiagramItem;
QGraphicsScene *myGraphicsScene;
QPointF initialPosition;
};
实现元件的搜索功能
这个功能比较简单, 只是简单的图元名称的匹配, 包含搜索关键词的显示, 否则隐藏,并对元件图标进行重新显示. 代码如下:
auto keys = map_name_widget.keys();
foreach( auto key, keys ){
if( key.contains( arg1, Qt::CaseInsensitive ) ){
map_name_widget.value(key)->show();
}
else
map_name_widget.value(key)->hide();
if( arg1.isEmpty() )
map_name_widget.value(key)->show();
}
QGridLayout *layout = ui->gridLayout_2;
int r = 1;
int c = 0;
foreach( auto w, map_name_widget.values() ){
if( w->isHidden() )
layout->removeWidget( w );
else{
layout->addWidget( w, r, c );
if( ++c>=2 ){
c=0;
++r;
}
}
}
实现元件的连接功能
在每个元件中记录各自的连接点, 在元件移动的时候更新连接线, 保证元件始终连接在一起. 重载图形项方法,通知连接线更新.
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
QVariant DiagramItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange) {
foreach (Arrow *arrow, arrows) {
arrow->updatePosition();
}
}
return value;
}