最開始設計這個編輯器時,其中一個要求就是能在運行過程中,通過UI來更新各對象,這樣我們就能明確每個Ogre對象更新其屬性影響的渲染效果.比如點光源,方向光源,聚光燈各屬性與效果,深度測試開啟與關閉,深度比較方式影響的效果等等.如下先看效果圖:
這個位置沒有用上一篇天龍的場景,主要是圖片大小限制,場景復雜后,生成的gif圖片太大.
這個功能當時我主界面完成后,就准備做的,但是當時一時想不到好的方案,如果針對每個Ogre屬性來生成UI,然后關聯每個對象,再想想后續如果要修改其中的字段屬性或是位置,這個工作量讓我不得不先想一個方案能夠避免這些問題.在完成第二篇天龍場景的加載時,順便在空閑時間想出現在的這種解決方法,不說這方法好,但是工作量少,簡潔,易修改,避免很多BUG.靈活性高,只有二個Ogre之間有關聯,就可以顯示在一起.如上面的Entity,他顯示出他父節點SceneNode,以及他的父類MovableObject的各屬性.
這個方案簡單來說,分成三個部分,一個部分主要是XML文件,XML文件包含每個Ogre對象要關聯的對象,字段,關聯字段等.第二個部分是自動生成控件界面,根據第一部分的信息來生成各個界面.第三部分是根據Ogre對象更新各個控件,以及更新控件上的值更新后反饋給對應Ogre對象.如下是三個部分代碼圖,VS中C++代碼生成的代碼關聯圖中,聚合關系不能顯示出來,所以一些聚合信息在上面沒有顯示出來.
XML文件與基本類
這個部分主要有二方面,一方面是XML文件保存的信息,包含每個Ogre對象關聯的對象,每個對象更新的字段,字段的項選擇,如下圖:
然后就是如第一張圖中的類OgreStyle,我們可以看到,這個類三個部分都有引用到,也是我們的這解決方法的核心類,這個類是一個樹型結構,這個結構在這主要有二個好處,一是容易添加在樹型控件里.二是可以得到父節點信息與子節點信息.其中我們設計在刪除節點時,所有子節點全刪除.如下是OgreStyle代碼.

namespace Ogre3DX { typedef std::vector<string> VectorString; class OgreStyle; typedef std::vector<OgreStyle*> OgreStyleVector; class OgreStyle { public: //ResourceType,SceneType int type = 0; std::string ogreName = ""; std::string ogreSecond = ""; std::string ogreText = ""; OgreStyle* parent = nullptr; OgreStyleVector childs; public: OgreStyle() { childs.clear(); } std::string getText() { if (ogreText.empty()) return ogreName; return ogreText; } void add(OgreStyle* child) { if (!check(child)) { throw exception("child is parent."); } if (child->parent != nullptr) { auto index = find(child->parent->childs.begin(), child->parent->childs.end(), child); child->parent->childs.erase(index); } child->parent = this; childs.push_back(child); } void remove(OgreStyle* child) { remove(child, true); } void removeAll() { //刪除所有子節點 while (!childs.empty()) { auto child = childs.back(); childs.pop_back(); //這句 避免遞歸時重復delete child->parent = nullptr; if (child != nullptr) { delete child; child = nullptr; } } } OgreStyle* findChild(int type) { if (childs.empty()) return nullptr; for (auto child : this->childs) { if (child->type == type) return child; } return nullptr; } OgreStyle* findChild(std::string name) { if (childs.empty()) return nullptr; for (auto child : this->childs) { if (child->ogreName == name) return child; } return nullptr; } bool check(OgreStyle* style) { OgreStyle* parent = this->parent; while (parent != nullptr) { if (parent == style) return false; parent = parent->parent; } return true; } ~OgreStyle() { if (parent != nullptr) parent->remove(this, false); //刪除所有子節點 while (!childs.empty()) { auto child = childs.back(); childs.pop_back(); //這句 避免遞歸時重復delete child->parent = nullptr; if (child != nullptr) { delete child; child = nullptr; } } } private: void remove(OgreStyle* child, bool bDelete) { if (child->parent != nullptr) { auto index = find(child->parent->childs.begin(), child->parent->childs.end(), child); child->parent->childs.erase(index); } child->parent = nullptr; if (bDelete) { delete child; child = nullptr; } } }; }
其中OgreStyle中的Type就是對應的Ogre中的對象類型,而ogreName是對應Ogre對象的標識,parent與childs樹結構.type對應的值在如下類中:

namespace ResourceEnum { //對應Ogre資源類型 enum ResourceType { Resource = 100, Material, Technique, Pass, Mesh, SubMesh, TextureUnit, Texture, Particle, Compositor, Program, Shader_Cg, Shader_hlsl, Shader_glsl, }; //對應Ogre場景 enum SceneType { Scene = 200, SceneNode, Viewport, Camera, RenderSystem, RenderTarget, MovableObject, Renderable }; enum MovableType { MovableOther = 300, BillboardChain, BillboardSet, Entity, Light, ManualObject, ParticleSystem, RibbonTrail, }; enum RenderType { RenderOther = 400, RenderSubEntity }; enum FileType { FL_Materials = 500, FL_Material, FL_Group, }; }
先暫時列這么多,其中上面第一份XML文件就是每個type關聯那些type,第二份文件就是每個type對應的字段,第三個type就是其中Ogre中的一些enum.下面這個類把type與三份三XML文件關聯起來.其中方法initXML不是有意要使用這種比較難理解的方式,主要是因為DataStreamHolder里面的數據是shared_ptr智能指針類型,最后得到的root只有在函數initXML內才有效,傳出這個值無效,而針對XML文件有效性檢查的代碼是一樣,這樣只有把方法傳過來.

namespace Ogre3DX { struct NodeStyle { std::string nodeName; std::string nodeText; NodeStyle(){}; NodeStyle(std::string name, std::string text) :nodeName(name), nodeText(text){} string getText() { if (nodeText.empty()) { string result; for (auto ch = nodeName.begin(); ch != nodeName.end(); ++ch) { if (ch != nodeName.begin() && isupper(*ch)) { result.push_back(' '); } result.push_back(*ch); } return result; } return nodeText; } }; struct OgreField { string fieldName; string fieldType; string fieldKey; string fieldText; bool fieldManual = false; OgreField(){}; OgreField(std::string name, std::string type, std::string key) :fieldName(name), fieldType(type), fieldKey(key){} string getText() { if (fieldText.empty()) { string result; for (auto ch = fieldName.begin(); ch != fieldName.end(); ++ch) { if (ch != fieldName.begin() && isupper(*ch)) { result.push_back(' '); } result.push_back(*ch); } return result; } return fieldText; } }; struct RelationVisible { string name; string relationName; string relationValue; bool relationVisible; string nodeName; RelationVisible(std::string name, std::string dName, std::string value, bool visible, string node) :name(name), relationName(dName), relationValue(value), relationVisible(visible), nodeName(node){} }; typedef std::vector<OgreField*> FieldVector; typedef std::vector<NodeStyle*> NodeStyleVector; typedef std::vector<RelationVisible*> RelationVector; } namespace Ogre3DX { class OgreStyleManager : public tools::Singleton<OgreStyleManager> { public: OgreStyleManager(); ~OgreStyleManager(); void initialise(); int getOgreStyleType(const string& name, int deault = 0) { for (auto kv : ogreStyles) { if (kv.second == name) return kv.first; } return deault; } std::string getOgreStyleName(int type, const string& name = "") { if (ogreStyles.find(type) != ogreStyles.end()) { return ogreStyles[type]; } return name; } NodeStyleVector getNodeStyles(int type) { NodeStyleVector result; if (ogreStyles.find(type) != ogreStyles.end()) { auto styleName = ogreStyles[type]; if (ogreNodes.find(styleName) != ogreNodes.end()) { auto nodeNames = ogreNodes[styleName]; int size = nodeNames.size(); for (int i = 0; i < size; i++) { auto name = nodeNames[i]; auto nodeStyle = nodeStyles[name]; auto text = ogreTexts[styleName][i]; if (!text.empty()) nodeStyle->nodeText = text; result.push_back(nodeStyle); } } } return result; } FieldVector getFieldVector(const string& nodeName) { if (nodeFields.find(nodeName) != nodeFields.end()) { return nodeFields[nodeName]; } return FieldVector(); } VectorString getKeyItems(const string& key) { if (keyItems.find(key) != keyItems.end()) { return keyItems[key]; } return VectorString(); } RelationVector getRelationFields(const string& nodeName) { RelationVector result; for (auto rf : relationFields) { if (rf->nodeName == nodeName) result.push_back(rf); } return result; } bool getRelation(const string& fieldName) { for (auto rf : relationFields) { if (rf->name == fieldName || rf->relationName == fieldName) return true; } return false; } private: typedef void(OgreStyleManager::*ptrFun)(xml::ElementPtr root); void initXML(std::string xmlName, ptrFun func); void initOgreStyles(); void initStyleNodes(xml::ElementPtr root); void initNodeFields(xml::ElementPtr root); void initKeyItems(xml::ElementPtr root); void initRelationFields(); xml::ElementPtr getRoot(DataStreamHolder data); private: // OgreResource int -> OgreStyleName std::map<int, string> ogreStyles; //NodeName -> NodeStyle std::map<string, NodeStyle*> nodeStyles; //OgreStyleName -> Vector NodeName (StyleNode.xml) std::map<string, VectorString> ogreNodes; std::map<string, VectorString> ogreTexts; //NodeName -> Vector NodeField (NodeField.xml) std::map<string, FieldVector> nodeFields; //key - items (KeyItem.xml) std::map<string, VectorString> keyItems; RelationVector relationFields; }; } template <> OgreStyleManager* tools::Singleton<OgreStyleManager>::msInstance = nullptr; namespace Ogre3DX { OgreStyleManager::OgreStyleManager() { } OgreStyleManager::~OgreStyleManager() { } void OgreStyleManager::initialise() { initOgreStyles(); initXML("StyleNode.xml", &OgreStyleManager::initStyleNodes); initXML("NodeField.xml", &OgreStyleManager::initNodeFields); initXML("KeyItem.xml", &OgreStyleManager::initKeyItems); initRelationFields(); } void OgreStyleManager::initOgreStyles() { ogreStyles[ResourceEnum::ResourceType::Resource] = "Resource"; ogreStyles[ResourceEnum::ResourceType::Material] = "Material"; ogreStyles[ResourceEnum::ResourceType::Technique] = "Technique"; ogreStyles[ResourceEnum::ResourceType::Mesh] = "Mesh"; ogreStyles[ResourceEnum::ResourceType::Texture] = "Texture"; ogreStyles[ResourceEnum::ResourceType::Particle] = "Particle"; ogreStyles[ResourceEnum::ResourceType::Compositor] = "Compositor"; ogreStyles[ResourceEnum::ResourceType::Program] = "Program"; ogreStyles[ResourceEnum::ResourceType::SubMesh] = "SubMesh"; ogreStyles[ResourceEnum::ResourceType::Pass] = "Pass"; ogreStyles[ResourceEnum::SceneType::Scene] = "SceneManager"; ogreStyles[ResourceEnum::SceneType::SceneNode] = "SceneNode"; ogreStyles[ResourceEnum::SceneType::Viewport] = "Viewport"; ogreStyles[ResourceEnum::SceneType::Camera] = "Camera"; ogreStyles[ResourceEnum::SceneType::RenderSystem] = "RenderSystem"; ogreStyles[ResourceEnum::SceneType::RenderTarget] = "RenderTarget"; ogreStyles[ResourceEnum::SceneType::MovableObject] = "MovableObject"; ogreStyles[ResourceEnum::SceneType::Renderable] = "Renderable"; ogreStyles[ResourceEnum::MovableType::MovableOther] = "MovableOther"; ogreStyles[ResourceEnum::MovableType::BillboardChain] = "BillboardChain"; ogreStyles[ResourceEnum::MovableType::BillboardSet] = "BillboardSet"; ogreStyles[ResourceEnum::MovableType::Entity] = "Entity"; ogreStyles[ResourceEnum::MovableType::Light] = "Light"; ogreStyles[ResourceEnum::MovableType::ManualObject] = "ManualObject"; ogreStyles[ResourceEnum::MovableType::ParticleSystem] = "ParticleSystem"; ogreStyles[ResourceEnum::MovableType::RibbonTrail] = "RibbonTrail"; ogreStyles[ResourceEnum::RenderType::RenderSubEntity] = "SubEntity"; } void OgreStyleManager::initXML(std::string xmlName, ptrFun initFunc) { DataStreamHolder data = MyGUI::DataManager::getInstance().getData(xmlName); if (data.getData() == nullptr) { throw exception(""); } xml::Document doc; if (!doc.open(data.getData())) { throw exception(""); } xml::ElementPtr root = doc.getRoot(); if ((nullptr == root) || (root->getName() != "OgreResource")) { throw exception(""); } (this->*initFunc)(root); } void OgreStyleManager::initStyleNodes(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("OgreStyle")) { auto name = node->findAttribute("name"); auto field = node->getElementEnumerator(); while (field.next()) { auto nodeName = field->findAttribute("name"); auto text = field->findAttribute("text"); ogreNodes[name].push_back(nodeName); ogreTexts[name].push_back(text); } } } void OgreStyleManager::initNodeFields(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("OgreNode")) { auto name = node->findAttribute("name"); auto text = node->findAttribute("text"); NodeStyle* nodeStyle = new NodeStyle(name, text); nodeStyles[name] = nodeStyle; auto field = node->getElementEnumerator(); while (field.next()) { auto fieldName = field->findAttribute("name"); auto fieldType = field->findAttribute("type"); auto fieldKey = field->findAttribute("key"); auto fieldText = field->findAttribute("text"); auto fieldManual = field->findAttribute("manual"); OgreField* field = new OgreField(fieldName, fieldType, fieldKey); field->fieldText = fieldText; if (fieldManual.size() > 0) { field->fieldManual = Ogre::StringConverter::parseBool(fieldManual); } nodeFields[name].push_back(field); } } } void OgreStyleManager::initKeyItems(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("Key")) { auto name = node->findAttribute("name"); auto field = node->getElementEnumerator(); while (field.next()) { auto nodeName = field->findAttribute("name"); keyItems[name].push_back(nodeName); } } } void OgreStyleManager::initRelationFields() { RelationVisible* entitySkeleton = new RelationVisible("DisplaySkeleton", "HaveSkeleton", "false", false, "Entity"); RelationVisible* targetPrimary0 = new RelationVisible("Update", "Primary", "true", false, "RenderTarget"); RelationVisible* targetPrimary1 = new RelationVisible("Active", "Primary", "true", false, "RenderTarget"); RelationVisible* targetPrimary2 = new RelationVisible("AutoUpdated", "Primary", "true", false, "RenderTarget"); RelationVisible* spot = new RelationVisible("Spotlight", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotInner = new RelationVisible("SpotlightInner", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotOuter = new RelationVisible("SpotlightOuter", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotFalloff = new RelationVisible("SpotlightFalloff", "Type", "SPOTLIGHT", true, "Light"); relationFields.push_back(entitySkeleton); relationFields.push_back(targetPrimary0); relationFields.push_back(targetPrimary1); relationFields.push_back(targetPrimary2); relationFields.push_back(spot); relationFields.push_back(spotInner); relationFields.push_back(spotOuter); relationFields.push_back(spotFalloff); } xml::ElementPtr OgreStyleManager::getRoot(DataStreamHolder data) { //Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(_name, mGroup, true); //MyGUI::OgreDataStream* data = new MyGUI::OgreDataStream(stream); // "OgreNodes.xml"); if (data.getData() == nullptr) { throw exception(""); } xml::Document doc; if (!doc.open(data.getData())) { throw exception(""); } xml::ElementPtr root = doc.getRoot(); if ((nullptr == root) || (root->getName() != "OgreResource")) { throw exception(""); } return root; } }
通過這個類,我們知道,一個OgreStyle對應多個NodeStyle,就如Entity分別顯示Entiy, SceneNode, MovableObject 這三個節點,每個節點NodeStyle又包含多個OgreField,就如SceneManager包含name,typename,ambientLight等字段.上面的RelationVisible主要是指二個字段有主從關系的那種,如上面是聚光燈才顯示一些聚光燈的信息,后面也會放入XML文件中.
然后就是OgreQuery,這個類比較復雜,在這我們只列出一個方法,就是得到我們現在樹型控件上的數據.

OgreStyle* OgreQuery::getTLRoot() { ogreRoot = shared_ptr<OgreStyle>(new OgreStyle()); auto ogreSceneRoot = new OgreStyle(); auto scene = Ogre::Root::getSingleton().getSceneManager(DSceneName); ogreSceneRoot->type = ResourceEnum::SceneType::Scene; ogreSceneRoot->ogreName = scene->getName(); ogreRoot->add(ogreSceneRoot); auto factorys = Ogre::Root::getSingleton().getMovableObjectFactoryIterator(); while (factorys.hasMoreElements()) { auto currentFactory = factorys.getNext(); OgreStyle* factory = new OgreStyle(); factory->ogreName = currentFactory->getType(); factory->type = ResourceEnum::SceneType::MovableObject; ogreSceneRoot->add(factory); auto movables = scene->getMovableObjectIterator(factory->ogreName); while (movables.hasMoreElements()) { auto currentMovable = movables.getNext(); OgreStyle* movable = new OgreStyle(); movable->ogreName = currentMovable->getName(); int type = OgreStyleManager::getInstance().getOgreStyleType(currentMovable->getMovableType(), ResourceEnum::MovableType::MovableOther); movable->type = type; factory->add(movable); if (type == ResourceEnum::MovableType::Entity) { Ogre::Entity* entity = dynamic_cast<Ogre::Entity*>(currentMovable); if (MyGUI::utility::startWith(movable->ogreName, "Unnamed")) { movable->ogreText = entity->getMesh()->getName(); } int subCount = entity->getNumSubEntities(); for (int i = 0; i < subCount; i++) { auto subEntity = entity->getSubEntity(i); OgreStyle* subStyle = new OgreStyle(); movable->add(subStyle); getRenderable(subEntity, subStyle, ResourceEnum::RenderType::RenderSubEntity, i); } } } } auto ogreRenderRoot = new OgreStyle(); ogreRoot->add(ogreRenderRoot); auto render = Ogre::Root::getSingleton().getRenderSystem(); getRenderSystem(render, ogreRenderRoot); return ogreRoot.get(); }
這個類現在設計了二種顯示方式,一種是上面的這種,不顯示SceneNode,還有一種是顯示SceneNode,這里就不放出來了,和上面這個方法差不多.
自動生成界面
第二部分全是UI顯示部分,主要是控件顯示分類,控件創建,顯示,更新.
我們把我們要的控件分成如下幾種顯示方式,大部分是從MyGUI中的LayoutEditor中修改后直接使用.
1.PropertyFieldLabel:主要是顯示一個文本,主要用於表示分組.
2.PropertyFieldEditBox:顯示一個文本與一個值,一般用來設定字符串,如Technique象的名稱.
3.PropertyFieldColour:顏色選擇.對應對象的顏色.
4.PropertyFieldCheckBox:對應Bool類型,是或否.
5.PropertyFieldComboBox:對應多項選擇,如Ogre常見的Enum,對光源類型,對應NodeField.xml文件中的type="ComboBox",這種type一般后面會有一個key的屬性,用於去KeyItem.xml查找對應的多項顯示,如光源類型對應的點光源,方向光源,聚光燈這些.
6.PropertyFieldAutoComplete:自定義MyGUI控件AutoComplete的包裝,用於項非常多的時候,如天龍中,一個場景中Mesh與Material,一般成百上千的,下拉框根本顯示不過來,AutoComplete能根據輸入給出最適合的選擇.后面會專門寫一篇針對MyGUI各控件的擴展.
7.PropertyFieldNumeric:對應各type有n int,n float,用於檢查輸入是否合法.
其中PropertyFieldManager用於管理這些類,我們先看這個類的實現.

namespace tools { class PropertyFieldManager : public MyGUI::Singleton<PropertyFieldManager> { public: PropertyFieldManager(); virtual ~PropertyFieldManager(); void initialise(); void shutdown(); IPropertyField* createPropertyField(MyGUI::Widget* _window, const std::string& _type); private: typedef MyGUI::delegates::CDelegate2<IPropertyField*&, MyGUI::Widget*> Delegate; typedef std::map<std::string, Delegate> MapFactoryItem; MapFactoryItem mFactories; }; } template <> tools::PropertyFieldManager* MyGUI::Singleton<tools::PropertyFieldManager>::msInstance = nullptr; template <> const char* MyGUI::Singleton<tools::PropertyFieldManager>::mClassTypeName = "PropertyFieldManager"; namespace tools { template <typename Type> class GenericFactory { public: typedef MyGUI::delegates::CDelegate2<IPropertyField*&, MyGUI::Widget*> Delegate; static typename Delegate::IDelegate* getFactory() { return MyGUI::newDelegate(createFromFactory); } private: static void createFromFactory(IPropertyField*& _instance, MyGUI::Widget* _parent) { _instance = new Type(_parent); } }; PropertyFieldManager::PropertyFieldManager() { } PropertyFieldManager::~PropertyFieldManager() { } void PropertyFieldManager::initialise() { mFactories["EditBox"] = GenericFactory<PropertyFieldEditBox>::getFactory(); mFactories["Label"] = GenericFactory<PropertyFieldLabel>::getFactory(); mFactories["Colour"] = GenericFactory<PropertyFieldColour>::getFactory(); mFactories["Bool"] = GenericFactory<PropertyFieldCheckBox>::getFactory(); mFactories["ComboBox"] = GenericFactory<PropertyFieldComboBox>::getFactory(); mFactories["Auto"] = GenericFactory<PropertyFieldAutoComplete>::getFactory(); mFactories["StaticEditBox"] = GenericFactory<PropertyFieldStaticEditBox>::getFactory(); mFactories["1 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["2 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["4 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["1 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["2 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["3 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["4 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); } void PropertyFieldManager::shutdown() { } IPropertyField* PropertyFieldManager::createPropertyField(MyGUI::Widget* _window, const std::string& _type) { IPropertyField* result = nullptr; MapFactoryItem::iterator item = mFactories.find(_type); MYGUI_ASSERT(item != mFactories.end(), "Factory PropertyField '" << _type << "' not found."); (*item).second(result, _window); result->initialise(_type); return result; } }
PropertyFieldManager主要是根據不同的NodeField.xml中NodeField中的Type來生成對應IPropertyField.IPropertyField是上面各字段控件的基類,我們來看下他的實現,了解所有字段控件實現能那些功能.對比Mygui中的LayoutEditor中的IPropertyField有些改動主要一是為了讓IPropertyField承載更多公共實現,免的子類型的各部分代碼差不多.二是更好的適合我們自己的項目,針對Ogre的各屬性更改.

using namespace Ogre3DX; namespace tools { typedef MyGUI::delegates::CDelegate3<const std::string&, const std::string&, bool> PropertyFieldActionDelegate; class IPropertyField { public: virtual ~IPropertyField(); virtual void initialise(const std::string& _type); virtual void setValue(const std::string& _value) = 0; //Relation Field virtual string getValue(); virtual void onFillValues(); virtual void setField(OgreField* _field); virtual OgreField* getField(); //Update Field virtual bool getFocus(); virtual void setName(const std::string& _value); virtual void setVisible(bool _value); virtual bool getVisible(); virtual void onAction(const std::string& _value, bool _final); virtual bool onCheckValue(); virtual MyGUI::IntSize getContentSize(); virtual void setCoord(const MyGUI::IntCoord& _coord); virtual bool checkFocus(MyGUI::Widget* widget); PropertyFieldActionDelegate eventAction; protected: MyGUI::TextBox* mText = nullptr; std::string mType; std::string mName; OgreField* field = nullptr; MyGUI::Widget* mainWidget = nullptr; }; } namespace tools { IPropertyField::~IPropertyField() { } void IPropertyField::initialise(const std::string& _type) { mType = _type; } string IPropertyField::getValue() { return ""; } void IPropertyField::onFillValues() { } void IPropertyField::setField(OgreField* _field) { field = _field; onFillValues(); } OgreField* IPropertyField::getField() { return field; } bool IPropertyField::getFocus() { //InputManager::getInstance().getKeyFocusWidget(); return false; } bool IPropertyField::checkFocus(MyGUI::Widget* widget) { auto mouseF = MyGUI::InputManager::getInstance().getMouseFocusWidget(); if (mouseF == widget) return true; auto keyF = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (keyF == widget) return true; return false; } void IPropertyField::onAction(const std::string& _value, bool _final) { eventAction(field->fieldName, _value, _final); } bool IPropertyField::onCheckValue() { return true; } MyGUI::IntSize IPropertyField::getContentSize() { return MyGUI::IntSize(0, mainWidget->getHeight()); } void IPropertyField::setCoord(const MyGUI::IntCoord& _coord) { mainWidget->setCoord(_coord); } void IPropertyField::setName(const std::string& _value) { mName = _value; mText->setCaption(_value); } void IPropertyField::setVisible(bool _value) { mainWidget->setVisible(_value); } bool IPropertyField::getVisible() { return mainWidget->getVisible(); } }
可以看到IPropertyField與第一部分中的OgreField是一一對應的,拿一些方法說下.
1.initialise():子類主要根據對應的Layout文件生成各個控件.在這主要把BaseLayout中的mMainWidget給IPropertyField中的mainWidget,這樣子類就不需要每個來實現getVisible,setVisible,getContentSize,setCoord就不需要子類型去實現了.
2.setValue,getValue:設置和得到IPropertyField控件中的值.
3.setField,getField:設置和得到對應的OgreField.
4.getFocus:檢查當前IPropertyField是否在得到鼠標或鍵盤的焦點.如果是,我們不去更新上面的值.
5.onAction:當我們更新IPropertyField中的值后,我們需要通知事件上的各個方法.
如下是PropertyFieldComboBox的具體實現,這個類可以讓我們更好理解相關方法需要如何實現.

namespace tools { class PropertyFieldComboBox : public wraps::BaseLayout, public IPropertyField { public: PropertyFieldComboBox(MyGUI::Widget* _parent); virtual ~PropertyFieldComboBox(); virtual void setValue(const std::string& _value); virtual string getValue(); protected: virtual void onFillValues(); private: void notifyApplyProperties(MyGUI::Widget* _sender); void notifyForceApplyProperties2(MyGUI::ComboBox* _widget, size_t _index); protected: MyGUI::ComboBox* mField; }; } namespace tools { PropertyFieldComboBox::PropertyFieldComboBox(MyGUI::Widget* _parent) : BaseLayout("PropertyFieldComboBox.layout", _parent), mField(nullptr) { assignWidget(mText, "Text"); assignWidget(mField, "Field"); mainWidget = mMainWidget; mField->eventComboAccept += newDelegate(this, &PropertyFieldComboBox::notifyForceApplyProperties2); } PropertyFieldComboBox::~PropertyFieldComboBox() { } void PropertyFieldComboBox::onFillValues() { if (field == nullptr || field->fieldKey.empty()) return; mField->removeAllItems(); auto items = OgreStyleManager::getInstance().getKeyItems(field->fieldKey); for (auto item : items) { mField->addItem(item); } } void PropertyFieldComboBox::notifyApplyProperties(MyGUI::Widget* _sender) { std::string value = MyGUI::utility::toString(mField->getIndexSelected());// mField->getOnlyText(); onAction(value, true); } void PropertyFieldComboBox::notifyForceApplyProperties2(MyGUI::ComboBox* _sender, size_t _index) { notifyApplyProperties(_sender); } void PropertyFieldComboBox::setValue(const std::string& _value) { int index = MyGUI::utility::parseInt(_value); if (index >= 0 && index < mField->getItemCount()) mField->setItemSelect(index); //mField->setOnlyText(_value); } string PropertyFieldComboBox::getValue() { return mField->getOnlyText(); } }
當我們知道如何根據OgreField生成IPropertyField后,如前面一個OgreStyle對應多個NodeStyle,每個節點NodeStyle又包含多個OgreField.對應PropertiesPanelView包含多個PanelProperties.每個PanelProperties又包含多個IPropertyField.
當PropertiesPanelView得到OgreStyle后,根據OgreStyle的NodeStyle生成PanelProperties,然后PanelProperties根據對應的NodeStyle中的OgreField 生成IPropertyField.這樣OgreStyle需要的各個控件顯示出來了,如下是相關PropertiesPanelView與PanelProperties代碼.

namespace tools { class PropertiesPanelView : public wraps::BaseLayout { public: PropertiesPanelView(MyGUI::Widget* _parent = nullptr); virtual ~PropertiesPanelView(); void notifyChangeSelectedWidget(OgreStyle* data); void hideAllPanel(); private: void notifyWindowChangeCoord(MyGUI::Window* _sender); std::vector<PanelProperties*> getPropertyWindow(OgreStyle* _style); size_t getIndexPanel(PanelProperties* _panel); void onRefleshFiled(float time); private: MyGUI::IntSize mOldSize; PanelView* mPanelView; typedef std::map<string, PanelProperties*> MapPropertyWindow; MapPropertyWindow mMapPropertyWindow; OgreStyle* currentStyle; }; } namespace tools { PropertiesPanelView::PropertiesPanelView(MyGUI::Widget* _parent) : BaseLayout("PropertiesPanelView.layout", _parent), mPanelView(nullptr), currentStyle(nullptr) { assignBase(mPanelView, "scroll_View"); MyGUI::Window* window = mMainWidget->castType<MyGUI::Window>(false); if (window != nullptr) { window->eventWindowChangeCoord += MyGUI::newDelegate(this, &PropertiesPanelView::notifyWindowChangeCoord); mOldSize = window->getSize(); } notifyChangeSelectedWidget(nullptr); Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &PropertiesPanelView::onRefleshFiled); } PropertiesPanelView::~PropertiesPanelView() { mPanelView->removeAllItems(); for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) delete (*item).second; mMapPropertyWindow.clear(); } void PropertiesPanelView::notifyWindowChangeCoord(MyGUI::Window* _sender) { const MyGUI::IntSize& size = _sender->getSize(); if (size != mOldSize) { mOldSize = size; mPanelView->setNeedUpdate(); } } void PropertiesPanelView::notifyChangeSelectedWidget(OgreStyle* _currentStyle) { currentStyle = _currentStyle; for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { (*item).second->setVisible(false); (*item).second->setStyle(nullptr, nullptr); (*item).second->update(); } if (currentStyle == nullptr) return; auto panels = getPropertyWindow(currentStyle); for (auto panel : panels) { panel->setVisible(true); panel->update(); } } void PropertiesPanelView::hideAllPanel() { for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { (*item).second->setVisible(false); (*item).second->setStyle(nullptr, nullptr); (*item).second->update(); } } std::vector<PanelProperties*> PropertiesPanelView::getPropertyWindow(OgreStyle* _style) { std::vector<PanelProperties*> result; auto nodeStyles = OgreStyleManager::getInstance().getNodeStyles(_style->type); for (auto nodeStyle : nodeStyles) { if (nodeStyle == nullptr || nodeStyle->nodeName.empty()) continue; std::string key = MyGUI::utility::toString(_style->type) + nodeStyle->nodeName; MapPropertyWindow::iterator item = mMapPropertyWindow.find(key); if (item == mMapPropertyWindow.end()) { PanelProperties* panel = new PanelProperties(); mPanelView->addItem(panel);// insertItem(getIndexByDepth(_depth), result); mMapPropertyWindow[key] = panel; } mMapPropertyWindow[key]->setStyle(_style, nodeStyle); result.push_back(mMapPropertyWindow[key]); } return result; } size_t PropertiesPanelView::getIndexPanel(PanelProperties* _panel) { for (size_t index = 0; index < mPanelView->getItemCount(); ++index) { if (mPanelView->getItem(index) == _panel) return index; } return MyGUI::ITEM_NONE; } void PropertiesPanelView::onRefleshFiled(float time) { for (auto item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { if ((*item).second->getVisible()) { (*item).second->onReflesh(); } } } }

using namespace Ogre3DX; namespace tools { typedef std::vector<std::pair<std::string, IPropertyField*>> MapPropertyField; typedef MapPropertyField::iterator FieldIter; class PanelProperties : public wraps::BasePanelViewItem { public: PanelProperties(); virtual void initialise(); virtual void shutdown(); void setStyle(OgreStyle* ogreStyle, NodeStyle* nodeStyle); void update(); void onReflesh(); private: void notifyAction(const std::string& _name, const std::string& _value, bool _final); size_t addParametrs(); void destroyPropertyFields(); void hidePropertyFields(); void updateSize(); void updateRelationFields(); IPropertyField* getPropertyField(MyGUI::Widget* _client, OgreField* field); FieldIter getField(const std::string& name); private: //typedef std::map<std::string, IPropertyField*> MapPropertyField; MapPropertyField mFields; OgreStyle* currentOgreStyle = nullptr; NodeStyle* currentNodeStyle = nullptr; }; } namespace tools { PanelProperties::PanelProperties() : BasePanelViewItem("PanelProperties.layout") { } void PanelProperties::initialise() { } void PanelProperties::shutdown() { destroyPropertyFields(); } size_t PanelProperties::addParametrs() { size_t result = 0; if (currentNodeStyle != nullptr) { auto fields = OgreStyleManager::getInstance().getFieldVector(currentNodeStyle->nodeName); for (auto iter = fields.begin(); iter != fields.end(); ++iter) { IPropertyField* field = getPropertyField(mWidgetClient, (*iter)); field->setField(*iter); field->setValue(""); result++; } } return result; } void PanelProperties::setStyle(OgreStyle* ogreStyle, NodeStyle* nodeStyle) { currentOgreStyle = ogreStyle; currentNodeStyle = nodeStyle; } void PanelProperties::update() { hidePropertyFields(); if (currentNodeStyle != nullptr) mPanelCell->setCaption(currentNodeStyle->getText()); size_t count = addParametrs(); setVisible(count > 0); onReflesh(); updateSize(); updateRelationFields(); } void PanelProperties::onReflesh() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) { auto fieldLayout = (*item).second; if (fieldLayout->getVisible() && !fieldLayout->getFocus()) { auto field = fieldLayout->getField(); if (field->fieldManual) continue; if (currentOgreStyle != nullptr && currentNodeStyle != nullptr && field != nullptr) { int type = OgreStyleManager::getInstance().getOgreStyleType(currentNodeStyle->nodeName); if (type != 0) { auto text = OgreManager::getInstance().getValue(currentOgreStyle, type, field->fieldName); fieldLayout->setValue(text); } } } } } void PanelProperties::updateSize() { int height = 0; for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) { if ((*item).second->getVisible()) { MyGUI::IntSize size = (*item).second->getContentSize(); (*item).second->setCoord(MyGUI::IntCoord(0, height, mMainWidget->getWidth(), size.height)); height += size.height; } } mPanelCell->setClientHeight(height); } void PanelProperties::updateRelationFields() { if (currentNodeStyle == nullptr) return; RelationVector rfs = OgreStyleManager::getInstance().getRelationFields(currentNodeStyle->nodeName); if (rfs.size() == 0) return; bool change = false; for (auto rf : rfs) { auto source = getField(rf->name); auto dest = getField(rf->relationName); if (source != mFields.end() && dest != mFields.end()) { auto nowValue = (*dest).second->getValue(); bool nowVisable = (*source).second->getVisible(); if ((nowValue == rf->relationValue) != (nowVisable == rf->relationVisible)) { change = true; (*source).second->setVisible(!nowVisable); } } } if (change) { updateSize(); } } void PanelProperties::destroyPropertyFields() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) delete (*item).second; mFields.clear(); } void PanelProperties::notifyAction(const std::string& _name, const std::string& _value, bool _final) { //if (currentOgreStyle != nullptr) //{ // currentOgreStyle->setValue(_name, _value); //} if (currentOgreStyle != nullptr && currentNodeStyle != nullptr) { int type = OgreStyleManager::getInstance().getOgreStyleType(currentNodeStyle->nodeName); if (type != 0) { OgreManager::getInstance().setValue(currentOgreStyle, type, _name, _value); } } updateRelationFields(); } void PanelProperties::hidePropertyFields() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) (*item).second->setVisible(false); } FieldIter PanelProperties::getField(const std::string& name) { for (auto i = mFields.begin(); i != mFields.end(); ++i) { if ((*i).first == name) return i; } return mFields.end(); } IPropertyField* PanelProperties::getPropertyField(MyGUI::Widget* _client, OgreField* field) { MapPropertyField::iterator item = getField(field->fieldName); if (item != mFields.end()) { (*item).second->setVisible(true); return (*item).second; } IPropertyField* result = PropertyFieldManager::getInstance().createPropertyField(_client, field->fieldType); result->setName(field->getText()); result->eventAction = MyGUI::newDelegate(this, &PanelProperties::notifyAction); //mFields[_name] = result; mFields.push_back(std::make_pair(field->fieldName, result)); return result; } }
這樣第二部分就把XML與控件生成關聯起來了,下面是第三部分,主要管理控件更新.
Oger對象驅動更新界面
最開始我這邊的處理是OgreStyle里關聯一個Ogre對象,如OgreStyle中的type是Camera,就關聯一個Ogre中的Camera對象,這樣相應更新就直接根據這個對象來處理,不過后面沒有這樣處理,主要是如下考慮,一是OgreStyle被太多類引用,最好不要過於復雜,首先更新必然會修改到更多類,其次在C++中更新這種很多類引用的文件編譯都要等死個人.二是我們的需求,如前面所說Entity類型的OgreStyle需要更新Entity的屬性,也可能是MovableObject或SceneNode,這樣綁定OgreStyle與Ogre對象后,並不好處理這種情況,因為並不是一對一,根據修改的Field的名字來查找對應的NodeStyle並不是一個好的選擇.基於上面考慮,放棄這種想法.
我們仔細想一下,應該是每一種type一種更新方式,並不和OgreStyle有關,而是和OgreStyle中的type有關,如OgreStyle為Entity,但是type可能是Entiyt, MovableObject, SceneNode這三種,我們可以根據type查找如何更新Ogre對象各字段的方法,然后根據OgreStyle的Ogrename找到對象.如下是代碼:

namespace Ogre3DX { class OgreManager : public tools::Singleton<OgreManager> { public: OgreManager() { mapObject[ResourceEnum::ResourceType::Resource] = new OgreResource(); mapObject[ResourceEnum::ResourceType::Material] = new OgreMaterial(); mapObject[ResourceEnum::ResourceType::Technique] = new OgreTechnique(); mapObject[ResourceEnum::ResourceType::Pass] = new OgrePass(); mapObject[ResourceEnum::SceneType::Viewport] = new OgreViewport(); mapObject[ResourceEnum::SceneType::SceneNode] = new OgreSceneNode(); mapObject[ResourceEnum::SceneType::MovableObject] = new OgreMovableObject(); mapObject[ResourceEnum::SceneType::RenderTarget] = new OgreRenderTarget(); mapObject[ResourceEnum::SceneType::Camera] = new OgreCamera(); mapObject[ResourceEnum::SceneType::Renderable] = new OgreRenderable(); mapObject[ResourceEnum::SceneType::Scene] = new OgreSceneManager(); mapObject[ResourceEnum::MovableType::Entity] = new OgreEntity(); mapObject[ResourceEnum::MovableType::Light] = new OgreLight(); scene = Ogre::Root::getSingleton().getSceneManager(DSceneName); } std::string OgreManager::getValue(OgreStyle* style, int type, const string& field) { OgreBasic* basic = getObject(style, type); if (basic != nullptr && basic->getObject() != nullptr) { return basic->getValue(field); } return ""; } bool OgreManager::setValue(OgreStyle* style, int type, const string& field, const string& value) { OgreBasic* basic = getObject(style, type); if (basic != nullptr && basic->getObject() != nullptr) { return basic->setValue(field, value); } return false; } OgreBasic* getObject(OgreStyle* style, int type) { auto object = getType(type); if (object == nullptr) return nullptr; //重新設置ogre值. if (style != preStyle || type != preType) { fillStyleObject(style, type); preStyle = style; preType = type; } return object; } OgreBasic* getType(int type) { if (mapObject.find(type) != mapObject.end()) { return mapObject[type]; } return nullptr; } void fillStyleObject(OgreStyle* style, int type) { auto object = getType(type); if (object == nullptr) return; switch (style->type) { case ResourceEnum::SceneType::Scene: if (true) { if (type == ResourceEnum::SceneType::Scene) { object->setObject(scene); } else if (type == ResourceEnum::SceneType::Viewport) { object->setObject(scene->getCurrentViewport()); } } break; case ResourceEnum::MovableType::Entity: if (true) { auto entity = scene->getEntity(style->ogreName); if (type == ResourceEnum::MovableType::Entity) { object->setObject(entity); } fillMovableable(object, entity, type); } break; case ResourceEnum::ResourceType::Material: if (true) { auto mat = MaterialManager::getSingleton().getByName(style->ogreName); fillMaterial(object, mat, type); } break; case ResourceEnum::RenderType::RenderSubEntity: if (style->parent != nullptr && style->parent->type == ResourceEnum::MovableType::Entity) { auto entity = scene->getEntity(style->parent->ogreName); int index = Ogre::StringConverter::parseInt(style->ogreName); auto subEntity = entity->getSubEntity(index); fillRenderable(object, subEntity, type); } break; case ResourceEnum::SceneType::RenderTarget: if (true) { auto render = Ogre::Root::getSingleton().getRenderSystem(); auto target = render->getRenderTarget(style->ogreName); if (type == ResourceEnum::SceneType::RenderTarget) { object->setObject(target); } } break; case ResourceEnum::SceneType::Viewport: if (true) { RenderSystem* render = Ogre::Root::getSingleton().getRenderSystem(); if (style->parent != nullptr && style->parent->type == ResourceEnum::SceneType::RenderTarget) { auto target = render->getRenderTarget(style->parent->ogreName); int index = Ogre::StringConverter::parseInt(style->ogreName); auto viewport = target->getViewport(index); auto camera = viewport->getCamera(); if (type == ResourceEnum::SceneType::Viewport) { object->setObject(viewport); } else if (camera != nullptr) { fillCamera(object, camera, type); } } } break; case ResourceEnum::SceneType::Camera: if (true) { auto camera = scene->getCamera(style->ogreName); if (camera != nullptr) { fillCamera(object, camera, type); } } break; case ResourceEnum::MovableType::Light: if (true) { auto light = scene->getLight(style->ogreName); if (type == ResourceEnum::MovableType::Light) { object->setObject(light); } fillMovableable(object, light, type); } break; default: break; } } void fillCamera(OgreBasic* object, Camera* camera, int type) { if (type == ResourceEnum::SceneType::Camera) { object->setObject(camera); } else if (type == ResourceEnum::SceneType::MovableObject) { object->setObject(camera); } else if (type == ResourceEnum::SceneType::Renderable) { object->setObject(camera); } } void fillRenderable(OgreBasic* object, Renderable* render, int type) { if (type == ResourceEnum::SceneType::Renderable) { object->setObject(render); } else if (type == ResourceEnum::ResourceType::Material) { object->setObject(render->getMaterial().get()); } else if (type == ResourceEnum::ResourceType::Technique) { auto tech = render->getTechnique(); object->setObject(tech); } else if (type == ResourceEnum::ResourceType::Pass) { auto pass = render->getTechnique()->getPass(0); object->setObject(pass); } } void fillMaterial(OgreBasic* object, MaterialPtr material, int type) { if (type == ResourceEnum::ResourceType::Material) { object->setObject(material.get()); } else if (type == ResourceEnum::ResourceType::Resource) { object->setObject(material.get()); } else if (type == ResourceEnum::ResourceType::Technique) { auto tech = material->getBestTechnique(); if (tech == nullptr) { tech = material->getTechnique(0); } object->setObject(tech); } else if (type == ResourceEnum::ResourceType::Pass) { auto tech = material->getBestTechnique(); if (tech == nullptr) { tech = material->getTechnique(0); } object->setObject(tech->getPass(0)); } } void fillMovableable(OgreBasic* object, MovableObject* movable, int type) { if (type == ResourceEnum::SceneType::MovableObject) { object->setObject(movable); } else if (type == ResourceEnum::SceneType::SceneNode) { object->setObject(movable->getParentSceneNode()); } } ~OgreManager() { } private: std::map<int, OgreBasic*> mapObject; //OgreBasic* pre OgreStyle* preStyle = nullptr; int preType = 0; SceneManager* scene = nullptr; }; }
其中OgreBasic是所有ogre對象更新類的基類,OgreTemplate是一個泛型類,提供一些方便.如下代碼:

using namespace Ogre; typedef Ogre::StringConverter format; namespace Ogre3DX { class OgreBasic { public: virtual std::string getValue(std::string field) = 0; virtual bool setValue(std::string field, std::string value) = 0; virtual void setObject(void* object){}; virtual void* getObject(){ return nullptr; } }; template<typename T> class OgreTemplate : public OgreBasic { public: OgreTemplate() { } virtual ~OgreTemplate(){}; virtual std::string getValue(std::string field) { return ""; } virtual bool setValue(std::string field, std::string value) { return false; } virtual void setObject(T* object) { ogreObject = object; } virtual T* getOgreObject() { return ogreObject; } protected: virtual void setObject(void* object) { return setObject((T*)object); } virtual void* getObject() { if (ogreObject == nullptr) return nullptr; return (void*)ogreObject; } protected: T* ogreObject = nullptr; }; }
其中OgreBasic是根據字段名得到對應Ogre屬性的值,setValue是對某個字段賦值,setObject是傳入相關Ogre對象指針,getObject是返回某對象指針,而OgreTemplate主要給我們提供一些方便,如上面都是void*指針,在C#中相當於object一樣,需要強制轉化后才能用,這個OgreTemplate自動幫我們做這些事件,基於最少可見原則,在OgreTemplate我們把父類中的setObject與getObject隱藏,我們並不希望用OgreTemplate泛型類后還在使用對應void*指針,這樣可以避免很多不安全的問題.而OgreBasic本身是給OgreManager使用,在這個類中,會保證生成正確的OgreTemplate,傳入正確的Ogre對象指針.
在這我們給出OgreSceneManager的一個OgreTemplate泛型具體化實現.

namespace Ogre3DX { class OgreSceneManager : public OgreTemplate<Ogre::SceneManager> { private: Plane* plane = nullptr; std::string planeMaterial = ""; FogMode fogMode = FogMode::FOG_NONE; ColourValue fogColor = ColourValue::White; Real fogDensity = 0.001; Real fogStart = 0.0; Real fogEnd = 1.0; public: OgreSceneManager() { plane = new Plane(0, -1, 0, 5000); } ~OgreSceneManager() { if (plane != nullptr) { delete plane; plane = nullptr; } } virtual std::string getValue(std::string field) { std::string result = ""; if (field == "Name") { result = ogreObject->getName(); } else if (field == "TypeName") { result = ogreObject->getTypeName(); } else if (field == "AmbientLight") { result = format::toString(ogreObject->getAmbientLight()); } else if (field == "PlaneEnable") { result = format::toString(ogreObject->isSkyPlaneEnabled()); } else if (field == "Plane") { Vector4 vp(plane->normal.x, plane->normal.y, plane->normal.z, plane->d); result = format::toString(vp); } else if (field == "BoxEnable") { result = format::toString(ogreObject->isSkyBoxEnabled()); } else if (field == "DomeEnable") { result = format::toString(ogreObject->isSkyDomeEnabled()); } else if (field == "FogMode") { result = format::toString(ogreObject->getFogMode()); } else if (field == "FogColour") { result = format::toString(ogreObject->getFogColour()); } else if (field == "FogStart") { result = format::toString(ogreObject->getFogStart()); } else if (field == "FogEnd") { result = format::toString(ogreObject->getFogEnd()); } else if (field == "FogDensity") { result = format::toString(ogreObject->getFogDensity()); } else if (field == "ShadowTechnique") { int index = 0; auto st = ogreObject->getShadowTechnique(); if (st == 0x12) { index = 1; } else if (st == 0x11) { index = 2; } else if (st == 0x22) { index = 3; } else if (st == 0x21) { index = 4; } else if (st == 0x25) { index = 5; } else if (st == 0x26) { index = 6; } result = format::toString(index); } else if (field == "ShadowColour") { result = format::toString(ogreObject->getShadowColour()); } else if (field == "ShadowFarDistance") { result = format::toString(ogreObject->getShadowFarDistance()); } return result; } virtual bool setValue(std::string field, std::string value) { if (field == "AmbientLight") { auto ambient = format::parseColourValue(value); ogreObject->setAmbientLight(ambient); } else if (field == "PlaneEnable") { auto check = format::parseBool(value); ogreObject->setSkyPlaneEnabled(check); } else if (field == "Plane" || field == "PlaneMaterial") { if (field == "Plane") { auto v4 = format::parseVector4(value); plane->normal.x = v4.x; plane->normal.y = v4.y; plane->normal.z = v4.z; plane->d = v4.w; } else if (field == "PlaneMaterial") { planeMaterial = value; } if (!planeMaterial.empty()) { ogreObject->setSkyPlane(true, *plane, planeMaterial); } } else if (field == "BoxEnable") { auto check = format::parseBool(value); ogreObject->setSkyBoxEnabled(check); } else if (field == "DomeEnable") { auto check = format::parseBool(value); ogreObject->setSkyDomeEnabled(check); } else if (field == "BoxMaterial") { if (!value.empty()) { ogreObject->setSkyBox(true, value); } } else if (field == "DomeMaterial") { if (!value.empty()) { ogreObject->setSkyDome(true, value); } } if (field == "FogMode" || field == "FogColour" || field == "FogStart" || field == "FogEnd" || field == "FogDensity") { if (field == "FogMode") { fogMode = (FogMode)format::parseInt(value); } else if (field == "FogColour") { fogColor = format::parseColourValue(value); } else if (field == "FogStart") { fogStart = format::parseReal(value); } else if (field == "FogEnd") { fogEnd = format::parseReal(value); } else if (field == "FogDensity") { fogDensity = format::parseReal(value); } ogreObject->setFog(fogMode, fogColor, fogDensity, fogStart, fogEnd); } else if (field == "ShadowTechnique") { int index = format::parseInt(value); ShadowTechnique st = (ShadowTechnique)index; if (index == 1) { st = ShadowTechnique::SHADOWTYPE_STENCIL_MODULATIVE; } else if (index == 2) { st = ShadowTechnique::SHADOWTYPE_STENCIL_ADDITIVE; } else if (index == 3) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_MODULATIVE; } else if (index == 4) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_ADDITIVE; } else if (index == 5) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED; } else if (index == 6) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED; } ogreObject->setShadowTechnique(st); } else if (field == "ShadowColour") { auto color = format::parseColourValue(value); ogreObject->setShadowColour(color); } else if (field == "ShadowFarDistance") { auto dist = format::parseReal(value); ogreObject->setShadowFarDistance(dist); } return true; } }; }
需要注意的是ShadowTechnique里我們KeyItem.xml文件里列出的項並沒與Ogre本身的對應,所以需要在這處理一下,如果是正常一一對應的,直接tostring與parseInt后就行. 然后別的Ogre對象分別實現的OgreTemplate泛型具體化類都差不多是這種處理.
回頭我們再看PanelProperties中updateRelationFields與notifyAction的處理,前者是每楨根據當前NodeStyle的對應type找到對應的OgreTemplate泛型具體化子類.然后根據當前OgreStyle的OgreName找到對應Ogre對應,然后根據字段,或是更新,更新得到對應OgreTemplate泛型具體化子類中的值.
至於為什么要每楨更新,而不是根據UI操作更新,簡單來說,是為了避免BUG產生,如上面我們點擊SceneNode的visible為flase后,對應節點下的MovableObject的visible也變成false,如果你不設置對應UI更新,對應的MovableObject上的visible還是顯示為true,如果你去更新UI,可以說這樣工作量又變大了,有一些你也想不到的關聯,這樣就等着更新大量的BUG去吧.其實這二種更新方式,對應我們官方的話來說:一個是事件驅動,一個是數據驅動.所以我們也不要以為WPF中的數據驅動是什么高大上的概念,就是因為如這里樣WPF關聯了控件與對應數據,數據更新后,每楨渲染時,根據數據顯示對應UI狀態.
如上通過這三個部分,我們實現了控件根據XML自動生成,控件根據Ogre對象自動更新.
當這個方案完成后,我只花了二個晚上的時間就完成了如上圖中的Ogre對象的各個UI界面更新,完成不需要更新任何有關UI控件的代碼,只要三個步驟就行,一是更新XML文件,二是具體化對應泛型OgreTemplate的實現,三是在OgreManager關聯Ogre對象與泛型OgreTemplate就行,在可見的將來,對應的粒子,地形,合成器的編輯都可以用這種方式.
PS 2016.1.12 因為現在這個設計不滿足Ogre2.1,暫時沒有繼續完成下去的欲望,代碼放git上免的讓別人認為是搞笑,就直接放http://pan.baidu.com/s/1kTWdqnl 這里,vs2013,相應DLL已經組織好,給mygui與ogre新手參考.