FreeCAD中可以同时打开多个工程,每个工程都会有一两个与之对应的视窗,当同时打开多个工程时freeCAD是如何管理这些视窗的呢?
源码中定义了一个叫做BaseView的抽象类,想要与Document(工程对应的数据管理类)与之对应,最好是继承这个类。这个类中实现了一些与Document建立联系的方法,通过这些方法可以将BaseView绑定到Document中,在双方的对象中都可以获取到对方,十分的方便。
class GuiExport BaseView : public Base::BaseClass { TYPESYSTEM_HEADER(); public: /** View constructor * Attach the view to the given document. If the document is 0 * the view will attach to the active document. Be aware! there isn't * always an active document! */ BaseView(Gui::Document* pcDocument=0); /** View destructor * Detach the view from the document, if attached! */ virtual ~BaseView(); /** @name methods used by the Application and the GuiDocument */ //@{ /// sets the view to another document (called by Application) void setDocument(Gui::Document* pcDocument); /// is sent from the document in order to close the document void onClose(void); //@} /// returns the document the view is attached to Gui::Document* getGuiDocument() const {return _pcDocument;} /// returns the document the view is attached to App::Document* getAppDocument() const; /// indicates if the view is in passive mode bool isPassive(void) const {return bIsPassive;} /** @name methods to override */ //@{ /// get called when the document is updated virtual void onUpdate(void){} /// get called when the document is relabeled (change of its user name) virtual void onRelabel(Gui::Document *){} /// get called when the document is renamed (change of its internal name) virtual void onRename(Gui::Document *){} /// returns the name of the view (important for messages) virtual const char *getName(void) const { return "Base view"; } /// Message handler virtual bool onMsg(const char* pMsg, const char** ppReturn)=0; /// Message handler test virtual bool onHasMsg(const char* pMsg) const=0; /// overwrite when checking on close state virtual bool canClose(void){return true;} /// delete itself virtual void deleteSelf(); //@} protected: Gui::Document* _pcDocument; bool bIsDetached; bool bIsPassive; };
BaseView这个类中定义了onMsg和onHasMsg函数。这个两个函数主要用于与MainWindow上的Command互动,当Command触发的时候可以调用Application对象中的SendMsgToActiveView()函数,将命令消息发送给当前活动的视窗,让视窗去响应这些命令。
onMsg用于响应消息,onHasMsg用于判断这个视窗是否需要某条Command,不需要直接在接收到对应的消息时返回false,那么当这个视窗的状态为active的时候,MainWindow上对应的action会变成不可用的状态。
这个设计相当棒,大大的增加了二次开发的灵活性,相当一部分命令都是所有的类型的工程都需要用到的,如新建、打开、复制、粘贴、剪切这些。二次开发的时候不用考虑重新到界面上去实现这些按钮,只需要在视窗或者Document中去响应这些消息就行了。
emmm 如果你问为什么不直接调用Application.activeDocumet()响应这些通用的命令,我也为此感到很疑惑。我想可能是因为一个工程只会对应一个Document,但是有可能会对应多个视窗。比如我自己现在开发的项目就需要对应两个视窗,一个对应模型的三维显示,一个对应模型的文本信息。那么像复制粘贴这种命令,在不同的视窗active的时候应该呈现不同的效果,模型视窗对应的是复制模型对象,而文本视窗则对应的是文本的复制粘贴。所以这种情况使用窗口消息去响应操作是更好的,并且因为视窗的基类都是BaseView,所以在视窗响应了这些操作之后也可以很方便的调用Document响应,如果需要的情况下。
MDIView类
这个类是MainWindow.addWindow()函数的参数类型,这函数用于向MainWindw的中心区域添加窗口。MDIView继承了QMainWindow和BaseView这个两个类。
BaseView这个类前面已经详细说过了,那还有啥能说的?emmm 在这个类中有一个很有意思的设计,这个设计实现了当工程的视窗都被关闭的时候,那么这个工程也会被自动关系。
void Document::detachView(Gui::BaseView* pcView, bool bPassiv) { if (bPassiv) { if (find(d->passiveViews.begin(),d->passiveViews.end(),pcView) != d->passiveViews.end()) d->passiveViews.remove(pcView); } else { if (find(d->baseViews.begin(),d->baseViews.end(),pcView) != d->baseViews.end()) d->baseViews.remove(pcView); // last view? if (d->baseViews.size() == 0) { // decouple a passive view std::list<Gui::BaseView*>::iterator it = d->passiveViews.begin(); while (it != d->passiveViews.end()) { (*it)->setDocument(0); it = d->passiveViews.begin(); } // is already closing the document if (d->_isClosing == false) d->_pcAppWnd->onLastWindowClosed(this); } } } void MDIView::closeEvent(QCloseEvent *e) { if (canClose()) { e->accept(); if (!bIsPassive) { // must be detached so that the last view can get asked Document* doc = this->getGuiDocument(); if (doc && !doc->isLastView()) doc->detachView(this); } // Note: When using QMdiArea we must not use removeWindow() // because otherwise the QMdiSubWindow will lose its parent // and thus the notification in QMdiSubWindow::closeEvent of // other mdi windows to get maximized if this window // is maximized will fail. // This odd behaviour is caused by the invocation of // d->mdiArea->removeSubWindow(parent) which we must let there // because otherwise other parts don't work as they should. QMainWindow::closeEvent(e); } else e->ignore(); }
这个功能的实现主要是靠重新closeEvent()函数实现。这里为了方便阅读,我将document类中被调用的函数也贴了出来。
首先在closeEvent()中判断了这个窗口是否能被关闭,这主要是为了能够在某种特定的状况下不让用户关掉工程(比如工程修改了但是没被保存),然后调用Document.detachView()函数,将视窗从docment对象中分离出来。
然后再看看另一个函数中,说实话我不太清楚bPassiv这个变量代表的意思是什么,但是看了一下默认应该是false,那么可以直接忽略往下看,在从Document中分离视窗之后,还做了一件事。判断视窗容器的大小是否为零,如果为零那么调用Application的函数,将工程关闭。
最后closeEvent中还有一大段注释,凭我多年使用有道词典的经验,大意应该是我们在将这个类放到QMDIArea中的时候,千万不要调用RemovWindow来移除这个窗口,在某种情况下会失败,导致程序无法正常运行。emmm 大概是对的?
文章评论