六、实体管理(QTreeWidget、QFileDialog)
这一节的内容相对来说比较多,但是依然是很简单,只不过因为我们要求的操作比较多,制作起来比较麻烦一点而已。最后成品如下图:
在开始之前先把实体管理的XML表确定下来。暂时定了以下结构:
<GroupList> <Group name=”Tree”/> <Group name=”House”/> </GroupList> <EntityList> <Entity name=”Test” filename=”test.mesh” Group=”Tree” /> </EntityList>
用名字字符串还是ID,这是个很纠结的问题,不过只是使用编辑器的话那倒无所谓,编辑器在乎的是其稳定性与实用性,效率只要不是太差,一般都能接受(有些商业引擎编译时间那是相当地长啊)。
根据XML表就可以确定树型结构的层次,这种简单的实体管理把层级设定超过两层,简直就是找抽行为。
完成XML表后,现在来分析更细的需求:
1、工具栏:实体管理还有一些常用操作,我把它们分为添加组,删除组,添加实体,删除实体,清空组,清空所有。
2、编辑修改:组这一层不支持从文件中提取,只能由工具栏添加,所以我们要支持双击修改保存文字,实体则是由文件对话框添加的,以后配合好属性栏,就能很方便的修改,所以不需要双击编辑修改。
3、右键菜单:组这一层对应的操作应该是添加实体,删除组,清空组,而实体这一层则只有删除实体这个选项。
4、图标:工具栏肯定需要图标,组和实体前面也需要一个图标,不然看一堆文字很不舒服。
5、拖放:实体可以拖到另一个组。
需求确定后,首先按照上一节的方法新建一个Dock Widget,命名为m_pEntityDock。并把它的可挂接的位置,设为左,右,下三个区域,默认区域设为左侧。
然后我们添加一个新类,类名叫EntityViewWidget,继承于QWidget,构造函数接受父类指针,并把它赋给Qwidget。
类申明如下:
class EntityViewWidget : public QWidget { Q_OBJECT; public: explicit EntityViewWidget(QWidget *parent = 0); virtual ~EntityViewWidget(); public Q_SLOTS: void SSelectionChanged(); void SRemoveGroup(); void SAddGroup(); void SClearGroup(); void SClearAll(); void SRemoveEntity(); void SAddEntity(); protected: static const int MAX_GROUP_COUNT = 64; EntityTreeWidget *m_pTreeWidget; QToolBar *m_pToolBar; QAction *m_pAddGroup; QAction *m_pRemoveGroup; QAction *m_pClearGroup; QAction *m_pClearAll; QAction *m_pAddEntity; QAction *m_pRemoveEntity; QTreeWidgetItem *m_pRootItems[MAX_GROUP_COUNT]; QTreeWidgetItem *m_pCurrGroupItem; QTreeWidgetItem *m_pCurrEntityItem; bool RemoveGroup(int index); int m_nGroupCount; };
先按照第四节的方法在里面创建一个QToolBar,并添加6个基于图标的QAction,并按照上章QT基础里面介绍设定自定义Slot的方法,分别设定好公有Slot函数(SAddEntity、SRemoveEntity、SAddGroup、SRemoveGroup、SClearGroup、SClearAll),因为QT的函数使用的是小写字母开头的驼峰式,所以我把自定义的SLOT用S前缀命名,以免混淆,而普通函数则是大写字母开头,注:这种命名方式并不一定是最好的命名方式。
Slots中有一个SSelectionChanged(),这是针对QTreeWidget的信号itemSelectionChanged的槽,因为添加删除操作和当前选中的item相关联,在这个Slot里面就添加了选中处理代码。
MAX_GROUP_COUNT指的是允许的最大组数。因为QTreeWidget只是指向指针,所以要申请空间给QTreeWidgetItem,这里使用了预分配机制,配合一个int型的m_nGroupCount来避免遍历的工作,这个具体在后面再说。
留了两个指针分别表示当前选中的组和实体,之所以把它们分开是因为有可能会同时用到两个。
EntityTreeWidget是自定义的QTreeWidget,类申明如下:
class EntityTreeWidget : public QTreeWidget { Q_OBJECT; public: EntityTreeWidget(QWidget *parent = 0); virtual ~EntityTreeWidget() {}; public Q_SLOTS: void SItemEditDone(); protected: QTreeWidgetItem *m_pCurrentItem; void mousePressEvent(QMouseEvent *evt); void dragEnterEvent(QDragEnterEvent *evt); void dragMoveEvent(QDragMoveEvent *evt); void dropEvent(QDropEvent *evt); void contextMenuEvent(QContextMenuEvent *evt); void keyPressEvent(QKeyEvent *evt); void mouseDoubleClickEvent( QMouseEvent *evt ); };
把这个类放在EntityViewWidget类的上面,因为需要在这个类中调用一些EntityViewWidget的方法。
因为需求,所以重载了一些QTreeWidget的事件,这也是使用自定义TreeWidget的主要原因,它们用来处理按键、拖动、上下文菜单,另外还自定义了一个Slot,用来解决编辑后的反馈。
再来看EntityTreeWidget的实现,代码如下:EntityTreeWidget::EntityTreeWidget(QWidget *parent) : QTreeWidget(parent),m_pCurrentItem(0) { setColumnCount(1); //设置栏目数 setHeaderHidden(true); //隐藏栏目名 setSelectionMode(QAbstractItemView:: SingleSelection);//我们只允许选中单行 setSelectionBehavior(QAbstractItemView::SelectItems);//设定几种蓝框选中方式 setDragDropOverwriteMode(false);//关闭拖动放下覆盖指定项模式 setDragDropMode(QAbstractItemView::DragDrop);//设定拖放方式,我们这里支持拖和放 connect(itemDelegate(),SIGNAL(closeEditor(QWidget*, QAbstractItemDelegate::EndEditHint)), this, SLOT(SItemEditDone()));//在编辑完成时去触发指定槽 } //-------------------------编辑完成时的槽--------------------------------------------------------------- //-------用来修改item的文字,以及更改属性栏值 void EntityTreeWidget::SItemEditDone() { if(m_pCurrentItem) { int index = indexOfTopLevelItem(m_pCurrentItem);//取得当前的ID m_pCurrentItem->text(0).toStdString();//修改名字 m_pCurrentItem = 0; } } //-------------------刚开始拖放--------------------------------------------------------------------- void EntityTreeWidget::dragEnterEvent(QDragEnterEvent *evt) { if(evt->source() == this ) { evt->acceptProposedAction();//开启拖放行为,你如果不接受的话, //dragMoveEvent和dropEvent就不起作用 } } //--------------拖动移动要告诉用户是否可以被放下-------------------- void EntityTreeWidget::dragMoveEvent(QDragMoveEvent *evt) { bool allow = false; QTreeWidgetItem *item = itemAt(evt->pos());//取得当时所在的Item if(item) //如果这时鼠标位置有item { if(item->parent() == 0) //判断目标位置是否为组,实体不允许放在实体下面 { QList<QTreeWidgetItem*> list = selectedItems(); //取得当前选中列表 if (list.count() == 1) //因为只支持单个选中 { if(list[0]->parent() != 0)//组也不能放在组下面 { allow = true; } } allow = true; } } if(allow) evt->accept(); //接受放置 else evt->ignore(); //不接受放置 } //------------------------放下处理---------------------------------------------------------------- void EntityTreeWidget::dropEvent(QDropEvent *evt) { bool allow = false; QTreeWidgetItem *item = itemAt(evt->pos()); if(!item || !(item->parent() == 0) || !(evt->source() == this))//如果为空,或者是实体, //或者是从别的地方拖过来的,就忽略 { evt->ignore(); return; } int index = indexOfTopLevelItem(item); QList<QTreeWidgetItem*> list = selectedItems(); if (list.count() == 1) { if(list[0]->parent() != 0) { list[0]->parent() -> removeChild(list[0]); //从原组中移除 item->addChild(list[0]); //添加进新组 } } } //-------------------------处理上下文菜单--------------------------------------------------------- void EntityTreeWidget::contextMenuEvent(QContextMenuEvent *evt) { QTreeWidgetItem *item = itemAt(evt->x(), evt->y());//取得item和,evt->pos()一样效果 if(item) { if (item->parent() == 0) { QMenu* contextMenu = new QMenu(this); //创建上下文菜单 int index = indexOfTopLevelItem(item); contextMenu->addAction(tr("Add Entity"), parent(), SLOT(SAddEntity())); contextMenu->addSeparator(); contextMenu->addAction(tr("Remove Group"), parent(), SLOT(SRemoveGroup())); contextMenu->addSeparator(); contextMenu->addAction(tr("Clear Group"), parent(), SLOT(SClearGroup())); contextMenu->addSeparator(); contextMenu->exec(QCursor::pos()); //执行上下文菜单 delete contextMenu; //一定要记得Delete,不然会内存泄漏 return; } else { QMenu* contextMenu = new QMenu(this); int index = indexOfTopLevelItem(item); contextMenu->addAction(tr("Remove"), parent(), SLOT(SRemoveEntity())); contextMenu->addSeparator(); contextMenu->exec(QCursor::pos()); delete contextMenu; return; } } evt->accept(); } //------------------------------------按键处理----------------------------------------- void EntityTreeWidget::keyPressEvent(QKeyEvent *evt) { QTreeWidget::keyPressEvent(evt); } //-------------------鼠标按下处理,修改属性栏值(现在无)----------------------- void EntityTreeWidget::mousePressEvent(QMouseEvent *evt) { QTreeWidget::mousePressEvent(evt); } //---------------------------------双击事件------------------------------------------------------- void EntityTreeWidget::mouseDoubleClickEvent(QMouseEvent *evt) { m_pCurrentItem = 0; QTreeWidgetItem *item = itemAt(evt->pos()); if(item && item->parent() == 0) { m_pCurrentItem = item; editItem(item, 0);//打开编辑功能 return; } }