前言
之前我们已经实现了桌面图标的创建、移动、文本绘制、双击响应。位置匹配等功能,今天我们将对桌面图标功能进行最后的升级,后面将开始组件的开发了,除非有bug需要修改或者有必要的功能需要添加,否则不会对桌面图标功能进行大的调整。今天我们将对桌面图标完善的功能包括窗口流式布局、图标位置重复检测和位置插入以及右键菜单功能。
开始之前呢,还是先来看一下实现效果:
窗口流式布局:
图标位置重复检测和位置插入:
右键菜单:
如何实现窗口流式布局?
首先我们要想明白,流式布局时,布局之所以会发生变化是因为窗口大小发生了变化。所以我们需要重写主窗口的resizeEvent函数,在窗口大小变化时获取窗口的宽度,利用宽度除以每个图标布局的宽度,即可得到每行能放置的图标个数,然后重新设置图标的位置即可。具体实现方法如下:
//重新布局图标Item m_nEachLineIconCount = this->width() / ICON_GAP; for (int i=0; isetPos(m_pIconWidgetVector[i].nRow * ICON_GAP + SCENE_SHIFTING, m_pIconWidgetVector[i].nColumn * ICON_GAP + SCENE_SHIFTING); }
从上面的代码中我们发现m_pIconWidgetVector存放的数据类型发生了变化,不在是单纯的存放一个IconWidget指针,而是存放一个自定义结构:
//图标Item信息结构typedef struct{ IconWidget *pIconWidget; int nRow; int nColumn;} ICON_ITEM_INFO;
该结构主要是为了方便下面的图标位置重复检测和位置插入功能的实现,而且后续也可能会扩充很多其他的信息,例如组件版本、创建日期及其相关属性等。
图标位置重复检测和位置插入
想要实现图标位置重复检测和位置插入,最简单的方法就是创建一个二维数组,数组中的每个值都表示该位置是否被占用,例如a[m][n] = 1表示第m行第n列已经被占用。这种方法对于绝大部分需求来说都足够了,但是如果我真的需要放置很多图标,不能使用一个固定的数组来实现呢?因此我就想到了我们之前用来存放图标Item指针的m_pIconWidgetVector,然后就设计了上面介绍的ICON_ITEM_INFO结构,用来存放图标item的坐标信息。
当我们拖动一个图标时,我们肯定是在鼠标松开的时候才能确定图标的最终位置,才能开始检测位置是否重复,因此需要重写mouseReleaseEvent函数,之前我们实现图标移动功能时已经重写了该函数,因此只需要在此基础上进行修改。
要想实现图标位置的插入,首先需要知道图标的原有位置和要插入的新位置,为了图标在场景中的实际位置能够和图标信息在m_pIconWidgetVector中的位置使用关联,因此我一直维护着m_pIconWidgetVector中图标信息的顺序,使其使用与图标在场景中的位置序号对应。
当我们将图标插入一个新位置时,需要该位置没有图标,那么就相当于之前我们实现的移动操作。但是如果有图标位置重复时,我们便需要对后续可能受影响的所有位置进行移动,直到找到第一个空位置,这个空位置就可以看作一个缓冲,终止我们的移动操作。
具体的代码实现如下:
//鼠标释放响应void IconWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){ //根据鼠标在scene中的位置,计算出Item应该在的行和列 int xPos = event->scenePos().x(); int yPos = event->scenePos().y(); int nRow = xPos / ICON_GAP; int nColumn = yPos / ICON_GAP; int nNewIndex, nOldIndex; for (nOldIndex=0; nOldIndexm_pIconWidgetVector.count(); nOldIndex++) { if (g_MainWin->m_pIconWidgetVector[nOldIndex].pIconWidget == this) break; } //删除旧的位置 ICON_ITEM_INFO info; info.pIconWidget = this; info.nRow = nRow; info.nColumn = nColumn; g_MainWin->m_pIconWidgetVector.erase(g_MainWin->m_pIconWidgetVector.begin() + nOldIndex); //插入新的位置 for (nNewIndex=0; nNewIndexm_pIconWidgetVector.count(); nNewIndex++) { if (g_MainWin->m_pIconWidgetVector[nNewIndex].nRow >= nRow && g_MainWin->m_pIconWidgetVector[nNewIndex].nColumn >= nColumn) break; } g_MainWin->m_pIconWidgetVector.insert(nNewIndex, info); this->setPos(nRow * ICON_GAP + SCENE_SHIFTING, nColumn * ICON_GAP + SCENE_SHIFTING); for (int i=nNewIndex+1; im_pIconWidgetVector.count(); i++) { //从插入位置后一的一个位置开始,循环比较每个位置是否被占用,只要有一个不被占用,移动就可以结束了 if (g_MainWin->m_pIconWidgetVector[i].nRow == nRow && g_MainWin->m_pIconWidgetVector[i].nColumn == nColumn) { nColumn = nRow == (g_MainWin->m_nEachLineIconCount-1) ? nColumn+1 : nColumn; nRow = nRow == (g_MainWin->m_nEachLineIconCount-1) ? 0 : nRow+1; g_MainWin->m_pIconWidgetVector[i].nRow = nRow; g_MainWin->m_pIconWidgetVector[i].nColumn = nColumn; g_MainWin->m_pIconWidgetVector[i].pIconWidget->setPos(nRow * ICON_GAP + SCENE_SHIFTING, nColumn * ICON_GAP + SCENE_SHIFTING); } else break; } //update(); //因为上面setPos了,所以不需要手动update了 QGraphicsItem::mouseReleaseEvent(event);}
右键菜单实现
右键菜单实现其实是最简单的了,只需要重写contextMenuEvent函数即可,可参考如下代码:
//右键菜单响应void IconWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event){ QMenu menu; QAction *open_Action = menu.addAction("打开(O)"); menu.exec(event->screenPos()); connect(open_Action, SIGNAL(triggered()), this, SLOT(open()));}
最后
其实本来还要实现一个文字阴影效果的,避免文字颜色和背景颜色相近时,就容易看不清楚的问题,但是现在已经1点多了,准备睡觉了,明天还要搬砖,night~night~晚安。