FreeCAD源码剖析之与工程绑定的UI

2021年01月23日 987点热度 1人点赞 0条评论

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 大概是对的?

 

大脸猫

半个C++程序员。

文章评论