项目树是切换模型场景的索引,根据创建的项目树,用户可选择性显示某一部分内容,也可叠加显示一个大的场景,通过UI界面上的组织来控制数据层面的显示。所以项目树的创建与展示,直接影响着视图层面的显示组织,创建项目树也显的尤为重要。

BIMBase平台项目树分UI层面及数据层面的树,UI层面主要负责面板上树的展示,数据层面主要负责相应树及节点数据储存。UI层面树与数据层面树是相结合使用,控制面板树列表显示及视图中内容显示。UI的相关接口类在BPProjectUITreeManager类中,提供了树节点相应操作接口,数据层面接口类集中在BPTree、BPTreeNode、BPTreeManager中。文件中主要的接口如表8-2所示。

表8-2 UI显示项目树接口列表

方法 描述
HandleTreeItem append(string,string, handleTreeItem ) 增加一个节点
bool remove(handleTreeItem ) 删除一个节点
bool remove(wstring) 删除一个节点
void extend(HandleTreeItem) 展开树节点
void extend(wstring) 展开树节点
void collapse(wstring) 折叠起树节点
void collapse(HandleTreeItem) 折叠起树节点
HandleTreeItem findByID(const long long) 根据节点ID找到节点
HandleTreeItem findByName(const wchar_t*) 找到某一名字的节点
void clear() 清除项目树
HandleTreeItem getTreeRootItem() 获取树根节点Item
void registerMsgReactionFunction (ProjectUIMsgFunType) 回调函数,通过注册获取鼠标点击事件
bool reName(HandleTreeItem , wstring) 重命名某一节点
void select(wstring) 选中某一节点
void setImage(int, int, wstring) 给树节点设置图片

表8-3 数据层项目树接口列表

方法 描述
BPTreeManager BPTreeOperateStatus removeTreeByTreeId(const long long) 根据指定树Id在当前工程文件中删除对应的树
BPTreeP getTreeByTreeId(const long long) 根据ID获取树
BPTreeOperateStatus loadBimBaseTreeFromFile(); 加载当前工程文件中的BPTree数据并生成缓存
BPTreeP createTree(BPTreeNodeR) 以指定节点作为根节点创建树
BPTreeNodeP transformNode (long long,long long,long long,long long) 将某树中的某个节点移动到目标节点,使目标节点成为被移动节点的父节点
BPTree BPTreeNodeP getRootNode() 获取根节点
BPTreeNodeP insertNode(BPTreeNodeCR, const long long) 在BPTree中以指定节点为父节点插入节点
BPTreeOperateStatus deleteNodeById(const long long) 在BPTree中删除指定Id对应节点
BPTreeNodeP getNodeById(long long) 根据ID获取节点
long long getTreeId() 获取当前树ID
std::wstring getName() 获取当前树的名称
BPTreeNode long long getNodeId() 获取当前节点ID
long long getTreeId() 获取当前树ID
long long getParentNodeId() 获取父节点ID
BPTreeNodeP getParentNode() 获取父节点
BPTreeOperateStatus setName(std::wstring 设置节点名称
std::wstring getName() 获取节点名称
BPTreeOperateStatus attachResource(std::wstring) 节点附加资源,将用户自定义字符串作为资源附加到节点,并支持持久化存储
BPTreeOperateStatus detachResource(std::wstring) 节点去除资源
BPTreeOperateStatus clearResourceVec() 节点清空资源
std::vector getResourceVec() 获取节点资源集合
BPTreeOperateStatus setReserverdData(const byte* , size_t) 设置节点预留数据
byte* getReservedData() 获取节点预留数据
BPTreeNodePtr deepClone(BPTreeNodeCR) 深拷贝一个节点,拷贝内容只限于节点类型、资源等数据,不包括节点Id、深度、父子节点信息;新节点与源节点数据独立
BPTreeNodePtr shallowCloneWithId(BPTreeNodeCR) 浅拷贝一个节点,并使用相同Id,拷贝内容只限于节点类型、资源等数据,不包括深度、父子节点信息;新节点与源节点Id相同,数据共享
BPTreeNodePtr createNode() 创建一个空节点
BPTreeOperateStatus updateInProject(::BIMBase::Core::BPProjectR) 将对从工程文件中取出的节点的修改更新到工程文件中
size_t getDepth() 获取当前节点深度

代码片段8-2展示了调用所列接口创建项目树的过程。

//项目树事件行为的定义
#define UI_PROJECT_TREE_MSG_LEFTCLICK  L"LeftClick"
#define UI_PROJECT_TREE_MSG_RIGHTCLICK    L"RightClick"
#define UI_PROJECT_TREE_MSG_DBLCLICK   L"DblClick"
#define IDM_CREATE_NODE  12001
//当前项目树的ID
static long long s_CurrentTreeIdDemo;
static int g_TreeNodeCount = 1;
void ProjectTreeDemo::traverseTreeNodeAndAdd(const BPTreeNodeP& tTree, int depth, BIMBase::FrameWork::HandleTreeItem& ssptri)
{
    if (depth < 1)
        return;
    for (auto node : BPTreeNodeCollection(*tTree, depth))
    {
        if (node->getParentNodeId() != tTree->getNodeId())
            continue;

        g_TreeNodeCount++;
        BIMBase::FrameWork::HandleTreeItem ptrSub = BIMBase::FrameWork::BPProjectUITreeManager::append(node->getName().c_str(), node->getName().c_str(), ssptri, node->getNodeId());
        traverseTreeNodeAndAdd(node, node->getDepth(), ptrSub);
    }
}
void ProjectTreeDemo::projectTreeRefresh()
{
    BIMBase::FrameWork::BPProjectUITreeManager::clear();

    //注册后,后续可以获取左击、右击、双击响应消息
    BIMBase::FrameWork::BPProjectUITreeManager::registerMsgReactionFunction(ProjectTreeDemo::onProjectManagerMsg);

    //项目树名称
    std::wstring sTreeName = _T("DemoTree");

    bool bHaveProjectTree = false;

BIMBase::Core::IBPProjectManagerP pProjectManager = BIMBase::Core::BPApplication::getInstance().getProjectManager();
if (pProjectManager == nullptr)
    return;
BPProjectP project = pProjectManager->getMainProject();
if (project == nullptr)
    return;

    //若是若有同名树则选择第一个树
    for (auto tree : BPTreeCollection(*project))//遍历树
    {
        std::vector<std::wstring> wsVecResource = tree->getRootNode()->getResourceVec();
        for (auto it : wsVecResource)
        {
            CString csTemp = it.c_str();
            if (csTemp.Find(sTreeName.c_str()) >= 0)
            {
                BPTreeP mainTree = tree;
                if (mainTree == nullptr)
                    continue;
                bHaveProjectTree = true;
                s_CurrentTreeIdDemo = mainTree->getTreeId();
                break;
            }
        }
    }
    if (!bHaveProjectTree)//创建默认项目树
    {
        vector<std::wstring> resource;
        resource.push_back(sTreeName);
        BPTreeNodePtr pNode = BPTreeNode::createNode();
        if (pNode.isNull())
            return;
        pNode->setName(_T("工程项目"));
        pNode->setNoteDataType(BIMBase::Data::BPTreeNodeType::enRootNode);
        for (auto res : resource)
        {
            pNode->attachResource(res);
        }

        BPTreeP pTree = BPTreeManager::getInstance(project).createTree(*pNode);
        if (pTree == nullptr)
            return;
        s_CurrentTreeIdDemo = pTree->getTreeId();
    }

    //填充树
    BPTreeP curTree = BPTreeManager::getInstance(project).getTreeByTreeId(s_CurrentTreeIdDemo);
    if (curTree == nullptr)
        return;

    BIMBase::FrameWork::HandleTreeItem ptrMain = BIMBase::FrameWork::BPProjectUITreeManager::append(curTree->getName().c_str(), curTree->getName().c_str(),BIMBase::FrameWork::BPProjectUITreeManager::getTreeRootItem(),curTree->getRootNode()->getNodeId());
    traverseTreeNodeAndAdd(curTree->getRootNode(),curTree->getRootNode()->getDepth(), ptrMain);

    BIMBase::FrameWork::BPProjectUITreeManager::extend(ptrMain);
    BIMBase::FrameWork::BPProjectUITreeManager::setImageList(nullptr);
}

代码8-2:接口创建项目树

在该代码创建项目树片段中,projectTreeRefresh函数是刷新项目树,在刷新的时候会判断是否存在项目树。如果已经存在,则需要获取已有的项目树,并且展示出来,如果没有项目树,则创建一个项目树。在创建项目树的过程中,先是在数据层面创建一个根节点,根据根节点创建一个树,然后再到UI层面去显示创建的树。在显示的过程中,采用了递归的方式,即traverseTreeNodeAndAdd函数,循环把所有的节点都显示到面板项目树中。在递归循环的过程中,循环是先往深度进行,再往下进行,例如工程项目下面有场景1及场景2,场景1下有场景3,场景2下有场景4,在进行树节点迭代的时候,是先把场景1下所有的子节点遍历完,再到场景2下遍历。

在项目树初始化另一个比较重要的点,就是需要注册回调函数,此注册在projectTreeRefresh函数实现,用来响应左键、右键、双击等事件,这样一来就可以在界面上响应鼠标事件完成相应的逻辑了。上述事例中调用接口registerMsgReactionFunction即是注册自己定义的事件响应函数,传入的参数是回调函数的名称。

上面的代码片段,我们增加了一个项目树,并且注册了鼠标响应事件。代码片段8-3展示了鼠标事件具体响应后的逻辑,平台提供的鼠标响应事件关键字为“RightClick”、“DbClick”、“LeftClick”,分别代表右键点击、双击、左键点击等事件。根据不同的事件我们可以完成不同的逻辑,范例中示例了右键及双击响应的用法。在右键响应的时候,弹出右键菜单,里面填充上创建节点。在双击节点的时候,完成切换不同的model的功能。

void ProjectTreeDemo::onProjectManagerMsg(wchar_t const* msgType, wchar_t const* text, wchar_t const* name, BIMBase::FrameWork::HandleTreeItem hdl)
{
    BPProjectP project = BPProject::getActiveProject();

    if (project == nullptr)
        return;

    int nNodeId = BIMBase::FrameWork::BPProjectUITreeManager::getItemData(hdl);
    BPTreeP curTree = BPTreeManager::getInstance(project).getTreeByTreeId(s_CurrentTreeIdDemo);
    if (curTree == nullptr)
        return;
    BPTreeNodeP pCurrentSelectNode = curTree->getNodeById(nNodeId);

    if (pCurrentSelectNode == nullptr)
        return;

    if (0 == wcscmp(UI_PROJECT_TREE_MSG_RIGHTCLICK, msgType))
    {
        createRightKeyMenu(text, pCurrentSelectNode, curTree);
    }
    else if (0 == wcscmp(UI_PROJECT_TREE_MSG_DBLCLICK, msgType))
    {
        onDbClickFunction(pCurrentSelectNode);
    }

    return;
}
void ProjectTreeDemo::createRightKeyMenu(wchar_t const* text, BPTreeNodeP& pSelectNode, BPTreeP& pTree)
{
    if (pSelectNode == nullptr)
        return;

    if (pTree == nullptr)
        return;

    BIMBase::Core::IBPProjectManagerP pProjectManager = BIMBase::Core::BPApplication::getInstance().getProjectManager();
    if (pProjectManager == nullptr)
        return;
    BPProjectPtr project = pProjectManager->getMainProject();
    if (project.isNull())
        return;

    CPoint cp;
    GetCursorPos(&cp);

    CMenu pMenu;
    VERIFY(pMenu.CreatePopupMenu());

    pMenu.AppendMenuW(MF_STRING, IDM_CREATE_NODE, _T("创建节点"));

    CWnd* cwnd = AfxGetMainWnd();
    int retID = pMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cp.x, cp.y, cwnd);

    if (retID == IDM_CREATE_NODE)
    {
        BPTreeNodePtr pNode = BPTreeNode::createNode();
        if (!pNode)
            return;

        std::wstring strName = std::to_wstring(g_TreeNodeCount);
        strName = _T("场景") + strName;
        pNode->setName(strName);
        pNode->setNoteDataType(BIMBase::Data::
                               BPTreeNodeType::enScene);

        BPTreeNodeP newNodeP = pTree->insertNode(*pNode, pSelectNode->getNodeId());
        if (newNodeP == nullptr)
            return;


        BIMBase::FrameWork::HandleTreeItem itemFather = BIMBase::FrameWork::BPProjectUITreeManager::findByName(text);
        BIMBase::FrameWork::HandleTreeItem ptrSub = BIMBase::FrameWork::BPProjectUITreeManager::
            append(newNodeP->getName().c_str(), newNodeP->getName().c_str(), itemFather,newNodeP->getNodeId());

        BIMBase::FrameWork::BPProjectUITreeManager::extend(itemFather);

        //给相应节点创建Model
        P3DStatus status;
        p3d::Utf8String modelName = "";
        modelName.sprintf("newModel%d", g_TreeNodeCount);

        g_TreeNodeCount++;
        p3d::platform::P3DModelType modelType = p3d::platform::P3DModelType::enPhysical;
        BPModelPtr newModel = project->createNewModel(status, modelName, modelType, true);
        if (newModel.isNull())
            return;

        Int32 nModelId = newModel->getModelId().m_id;

        std::wstring strId = std::to_wstring(nModelId);

        vector<std::wstring> resource;
        resource.push_back(strId);
        for (auto res : resource)
        {
            pNode->attachResource(res);
        }

        newNodeP->updateInProject(*project);

        BPViewManagerR mg = BPViewManager::getInstance();
        UInt32 nIndex = mg.getActiveIndex();

        //显示创建的model
        BPViewManager::getInstance().displayModelOnViewPort(nModelId, nIndex);
    }

}
void ProjectTreeDemo::onDbClickFunction(BPTreeNodeP& pSelectNode)
{
    if (pSelectNode == nullptr)
        return;

    std::vector<std::wstring> strVct = pSelectNode->getResourceVec();
    if (strVct.size() == 0)
        return;

    Int32 Id = _wtoi(strVct[0].c_str());

    BPViewManagerR mg = BPViewManager::getInstance();
    UInt32 nIndex = mg.getActiveIndex();

    BPViewManager::getInstance().displayModelOnViewPort(Id, nIndex);
}

代码8-3:项目树鼠标响应

上述代码中,onProjectManagerMsg是我们注册的回调函数,当项目树点击左键、右键、双击等,会响应到此函数。在此函数中,我们根据传过来的鼠标消息,分别创建了右键菜单及双击切换项目树节点功能。创建右键菜单(createRightKeyMenu)中的创建节点功能,数据层面会先创建一个节点,然后增加到当前项目树中,同时会创建一个model,model可以理解为放置构件的容器,该modelID存于节点附加资源,用于model与项目树节点绑定,这样切换到不同的节点时,将会展示对应model上不同的模型。最后再根据新创建的数据节点在UI面板上进行展示,新创建的节点会变为当前选中节点。双击响应函数(onDbClickFunction)中,传入的是当前双击的节点,根据双击的节点,可以获取到节点中储存的modelID,把此model显示到视图中,即完成了节点双击切换的功能。

最后在程序中得到项目树的效果如下图所示



图8-7 项目树效果