wxWidgets源码分析(8) - MVC架构

目录

  • MVC架构

    • wxDocManager文档管理器
    • 框架菜单命令的执行过程
    • SDI消息传递总结
    • 更新视图

MVC架构

wxDocManager文档管理器

wxWidgets使用wxDocManager类来管理MVC中的文档和视图的对应关系,使用方法:

  1. 创建一个wxDocManager对象,然后向此对象中增加文档模板wxDocTemplate对象,文档模板对象中说明了文档类型和该文档对应的文档类、视图类;
  2. 将此wxDocManager对象传递给wxDocParentFrame类(SDI),这样框架类就和文档类关联起来了。
//// SDI
wxDocManager *docManager = new wxDocManager;
//// Create a template relating drawing documents to their views
new wxDocTemplate(docManager, "#docDescription", "*.#docExtention", "", "#docExtention",
                  "Doc", "View",
                  CLASSINFO(CClassPrefixDoc), CLASSINFO(CClassPrefixView));
wxFrame *frame = new wxDocParentFrame(docManager, ...);

看下wxDocTemplate构造函数,这里实现了Manager和模板的关联:

  1. 调用wxDocManagerAssociateTemplate将自己和manager关联起来,在wxDocManager中,它将所有的文档模板保存到m_templates容器中;
  2. 保存doc和view的ClassInfo。
wxDocTemplate::wxDocTemplate(wxDocManager *manager, ...)
{
    m_documentManager = manager;
    m_documentManager->AssociateTemplate(this);
    m_docClassInfo = docClassInfo;
    m_viewClassInfo = viewClassInfo;
}

void wxDocManager::AssociateTemplate(wxDocTemplate *temp)
{
    if (!m_templates.Member(temp))
        m_templates.Append(temp);
}

后续的所有文档操作都是通过wxDocManager进行的,我们接下来跟踪一下创建新文档的流程,用户代码如下:

docManager->CreateNewDocument();

跟踪到wxDocManager::CreateNewDocument,它复用CreateDocument的实现,CreateDocument使用flags参数,有如下数据:

  1. 获取用户注册的文档模板,如果没有则不需要处理了;
  2. 选择一个文档模板,这个是必须的,因为所有的重要操作都是在文档模板中完成的;
  3. 接着进行其他的有效性验证等。
  4. 最后调用用户注册的模板类来创建文档
wxDocument *CreateNewDocument()
    { return CreateDocument(wxString(), wxDOC_NEW); }

wxDocument *wxDocManager::CreateDocument(const wxString& pathOrig, long flags)
{
    wxDocTemplateVector templates(GetVisibleTemplates(m_templates));
    const size_t numTemplates = templates.size();
    if ( !numTemplates )
        return NULL;

    // 选择文档模板,如果用户传递进来的pathOrig有效,则根据这个pahOrig指定的
    // 扩展名进行选择
    // wxDOC_SILENT: 如果无此标记,则当用户要创建新文档,并且又有多种文档类型时,
    // 会弹出对话框让用户选择要创建的文档类型。
    wxString path = pathOrig;   // may be modified below
    wxDocTemplate *temp;
    if ( flags & wxDOC_SILENT )
    {
        temp = FindTemplateForPath(path);
    }
    else // not silent, ask the user
    {
        if ( (flags & wxDOC_NEW) || !path.empty() )
            temp = SelectDocumentType(&templates[0], numTemplates);
        else
            temp = SelectDocumentPath(&templates[0], numTemplates, path, flags);
    }
    if ( !temp )
        return NULL;

    // 检查文档数量是不是已经超出范围
    if ( (int)GetDocuments().GetCount() >= m_maxDocsOpen )
    {
        if ( !CloseDocument((wxDocument *)GetDocuments().GetFirst()->GetData()) )
        {
            // can‘t open the new document if closing the old one failed
            return NULL;
        }
    }

    // 调用文档模板类来生成文档对象
    wxDocument * const docNew = temp->CreateDocument(path, flags);
    if ( !docNew )
        return NULL;

    docNew->SetDocumentName(temp->GetDocumentName());

    // 如果是新创建文档则要调用doc的`OnNewDocument`和`OnOpenDocument`方法;
    if ( !(flags & wxDOC_NEW ? docNew->OnNewDocument()
                             : docNew->OnOpenDocument(path)) )
    {
        docNew->DeleteAllViews();
        return NULL;
    }

    // 历史文件。。。
    if ( !(flags & wxDOC_NEW) && temp->FileMatchesTemplate(path) )
        AddFileToHistory(path);
    docNew->Activate();
    return docNew;
}

模板类创建文档对象

文档模板类wxDocTemplate创建文档的过程比较简单,通过用户注册的docClassInfo来创建一个文档对象,创建完成后再调用InitDocument执行初始化:

wxDocument *wxDocTemplate::CreateDocument(const wxString& path, long flags)
{
    wxDocument * const doc = DoCreateDocument();
    return doc && InitDocument(doc, path, flags) ? doc : NULL;
}

wxDocument *wxDocTemplate::DoCreateDocument()
{
    if (!m_docClassInfo)
        return NULL;
    return static_cast<wxDocument *>(m_docClassInfo->CreateObject());
}

文档初始化过程,将文档对象和文档模板、文档管理器都关联起来,然后调用doc->OnCreate运行用户的代码,这个是在创建过程中的唯一机会。

bool
wxDocTemplate::InitDocument(wxDocument* doc, const wxString& path, long flags)
{
    doc->SetFilename(path);
    doc->SetDocumentTemplate(this);
    GetDocumentManager()->AddDocument(doc);
    doc->SetCommandProcessor(doc->OnCreateCommandProcessor());

    if ( doc->OnCreate(path, flags) )
        return true;
    if ( GetDocumentManager()->GetDocuments().Member(doc) )
        doc->DeleteAllViews();
    return false;
}

文档创建过程中方法的调用顺序:

1. Constructor()
2. OnCreate()
3. OnNewDocument() or OnOpenDocument()

可以看到,上面的流程,创建文档完成后就没事了,返回成功,那视图是在哪创建的呢?

视图对象的创建

在定义文档类时,可能会实现OnCreate方法,如果用户想让doc类直接创建关联的视图,那么此时就必须调用父类的wxDocument::OnCreate方法。

bool CClassPrefixDoc::OnCreate(const wxString& path, long flags)
{
    if (!wxDocument::OnCreate(path, flags))
        return false;
    return true;
}

我们看下wxDocument::OnCreate方法的实现,wxDocTemplate在初始化doc对象的时候,已经将自己传递进去了,那么此时doc就可以再通过模板对象来创建View类,因为View的类型是在模板对象中指定的,自然它知道怎么创建。

  1. 调用wxDocTemplate::DoCreateView来实例化一个view对象;
  2. 调用view的OnCreate方法,这个方法也是给用户使用的。
bool wxDocument::OnCreate(const wxString& WXUNUSED(path), long flags)
{
    return GetDocumentTemplate()->CreateView(this, flags) != NULL;
}

wxView *wxDocTemplate::CreateView(wxDocument *doc, long flags)
{
    wxScopedPtr<wxView> view(DoCreateView());
    if ( !view )
        return NULL;
    view->SetDocument(doc);
    if ( !view->OnCreate(doc, flags) )
        return NULL;
    return view.release();
}

wxView *wxDocTemplate::DoCreateView()
{
    if (!m_viewClassInfo)
        return NULL;

    return static_cast<wxView *>(m_viewClassInfo->CreateObject());
}

创建顺序

从上面可以知道,创建顺序如下:

wxDocTemplate -> Document -> View

框架菜单命令的执行过程

wxDocParentFrame菜单入口

当用户调用菜单保存文档时,会产生菜单消息命令,由于菜单属于Frame的子项,所以此时会调用Frame的消息处理入口,调用流程如下:

// wxDocParentFrame
wxFrame::HandleCommand()
    -> wxFrame::HandleCommand()
        -> wxFrameBase::ProcessCommand()

// 参考前文分析,接着会调用 menu 和 menuBar 的处理函数,随后主动权
// 再次回到 wxDocParentFrame 中,此时的处理函数位于 wxEvtHandler 中。
wxEvtHandler::ProcessEventLocally
    -> ... -> wxDocParentFrame::TryBefore

由于TryBefore是虚方法,此时我们要看下 wxDocParentFrame 的继承关系才能搞清楚到底调用哪个函数:

wxDocParentFrame -> wxDocParentFrameBase (wxDocParentFrameAny<wxFrame>) ->
    wxFrame & wxDocParentFrameAnyBase

wxDocParentFrame继承关系中的wxDocParentFrameAny模板类实现了TryBefore方法,所以就是这个了,函数中调用了两个函数:

  1. BaseFrame::TryBefore(event)这个必然是wxFrame::TryBefore,可忽略
  2. 第二个TryProcessEvent方法,则是继承自wxDocParentFrameAnyBase,所以调用的是wxDocChildFrameAnyBase::TryProcessEvent
template <class BaseFrame>
class WXDLLIMPEXP_CORE wxDocParentFrameAny : public BaseFrame,
                                             public wxDocParentFrameAnyBase {
    virtual bool TryBefore(wxEvent& event)
    {
        return BaseFrame::TryBefore(event) || TryProcessEvent(event);
    }

我们继续看wxDocChildFrameAnyBase::TryProcessEvent,这个函数改写了原有frame类的处理过程,它查找当前Frame关联的wxDocManager,然后把消息传递给这个对象去处理。在给wxDocManager处理之前,我们可以看到它实际上是先调用childFrame->HasAlreadyProcessed函数处理的,如果这个函数没有处理则交给wxDocManager

由于我们这次使用的是预定义的,并且我们自身没有实现任何消息映射,所以此时一定会走到m_docManager->ProcessEventLocally中。

bool wxDocParentFrameAnyBase::TryProcessEvent(wxEvent& event)
{
    if ( !m_docManager )
        return false;
    if ( wxView* const view = m_docManager->GetAnyUsableView() )
    {
        wxDocChildFrameAnyBase* const childFrame = view->GetDocChildFrame();
        if ( childFrame && childFrame->HasAlreadyProcessed(event) )
            return false;
    }
    return m_docManager->ProcessEventLocally(event);
}

wxDocManager类的处理

此时我们可以先看下wxDocManager的消息映射表,如果已经有注册,那么此时就会走到

wxDocManager的消息注册表中。

参考源码可以看到wxDocManager已经实现了很多个消息的预处理,对于Save来说已经有了wxDocManager::OnFileSave

BEGIN_EVENT_TABLE(wxDocManager, wxEvtHandler)
    EVT_MENU(wxID_OPEN, wxDocManager::OnFileOpen)
    EVT_MENU(wxID_CLOSE, wxDocManager::OnFileClose)
    EVT_MENU(wxID_CLOSE_ALL, wxDocManager::OnFileCloseAll)
    EVT_MENU(wxID_REVERT, wxDocManager::OnFileRevert)
    EVT_MENU(wxID_NEW, wxDocManager::OnFileNew)
    EVT_MENU(wxID_SAVE, wxDocManager::OnFileSave)

继续跟踪wxDocManager::OnFileSave,发现转向到了doc的save函数,doc中处理save时首先检查是否是新建的文件并且有改变,如果是新建的则走SaveAs流程,否则继续处理保存。

void wxDocManager::OnFileSave(wxCommandEvent& WXUNUSED(event))
{
    wxDocument *doc = GetCurrentDocument();
    if (!doc)
        return;
    doc->Save();
}

bool wxDocument::Save()
{
    if ( AlreadySaved() )
        return true;

    if ( m_documentFile.empty() || !m_savedYet )
        return SaveAs();

    return OnSaveDocument(m_documentFile);
}

Save也好,走SaveAs也好,最终都会调用wxDocument::OnSaveDocument来实现文档的保存,这里最后调用的DoSaveDocument方法来实现保存,这个是虚方法,需要用户来实现。

bool wxDocument::OnSaveDocument(const wxString& file)
{
    if ( !file )
        return false;

    if ( !DoSaveDocument(file) )
        return false;

    if ( m_commandProcessor )
        m_commandProcessor->MarkAsSaved();

    Modify(false);
    SetFilename(file);
    SetDocumentSaved(true);
#if defined( __WXOSX_MAC__ ) && wxOSX_USE_CARBON
    wxFileName fn(file) ;
    fn.MacSetDefaultTypeAndCreator() ;
#endif
    return true;
}

对于文档的另存、打开等也有同样的处理。wxWidgets的文档视图框架已经提供了比较好的支持,我们可以省掉很多重复代码了。

当然我们也可以通过重载OnSaveDocument (const wxString &filename)来实现文档的保存,这样的话,用户需要自己去保存文档的状态等等,实在是没有必要。

wxView类的处理

前文有描述,当收到命令菜单时,最终会调用到m_docManager->ProcessEventLocally(event),我们再回过头看下ProcessEventLocally的调用关系,先调用TryBefore然后再调用TryHereOnly

bool wxEvtHandler::ProcessEventLocally(wxEvent& event)
{
    return TryBeforeAndHere(event) || DoTryChain(event);
}
bool wxEvtHandler::TryBeforeAndHere(wxEvent& event)
{
    return TryBefore(event) || TryHereOnly(event);
}

TryBefore是虚方法,我们看下wxDocManager的实现,查找当前的一个view,然后再调用view->ProcessEventLocally,这里需要关注GetAnyUsableView,具体的实现就是获取到当前最新的View,如果获取不到在获取到当前最近使用的doc,并获取到这个doc中的第一个view:

bool wxDocManager::TryBefore(wxEvent& event)
{
    wxView * const view = GetAnyUsableView();
    return view && view->ProcessEventLocally(event);
}
wxView *wxDocManager::GetAnyUsableView() const
{
    wxView *view = GetCurrentView();
    if ( !view && !m_docs.empty() )
    {
        wxList::compatibility_iterator node = m_docs.GetFirst();
        if ( !node->GetNext() )
        {
            wxDocument *doc = static_cast<wxDocument *>(node->GetData());
            view = doc->GetFirstView();
        }
    }
    return view;
}

接着继续看wxView的wxView::TryBefore方法,view中会先找Doc类来处理这个命令,如果doc不处理那么View才会处理。

bool wxView::TryBefore(wxEvent& event)
{
    wxDocument * const doc = GetDocument();
    return doc && doc->ProcessEventLocally(event);
}

右键菜单命令的执行过程

对于MVC程序来说,View类并没有真实的窗口,在创建wxView对象的时候,由view创建一个新的wxWindow窗口,此窗口继承自Frame,然后将此窗口绑定到wxFrame对象上。

而右键菜单则应用在wxWindow窗口上,弹出菜单代码为:

void XXXWindow::OnMouseEvent(wxMouseEvent& event)
{
    if (event.RightDown()) {
        wxMenu *popMenu = new wxMenu;
        popMenu->Append(ID_MenuTest, "Canvas Test Menu");
        PopupMenu(popMenu);
    }
}

此时消息处理流程略有变更,由于菜单绑定在当前窗口上,所以最先处理此消息的是当前的wxWindow对象,如果此对象不进行处理,则交给父类wxFrame处理,父类则会安装MVC的标准流程处理。

SDI消息传递总结

处理原则:

  1. wxDocParentFrame收到消息后处理:优先传递给wxDocManager处理,然后自己处理;
  2. wxDocManager收到消息后,优先传递给wxView处理,然后自己处理;
  3. wxView优先将消息传递给wxDocument处理,然后自己处理;

这样最后将导致处理优顺序为:

    wxDocument > wxView > wxDocParentFrame

如果是右键菜单命令,则优先触发此菜单的对象处理,剩下的流程同上。

更新视图

wxDocument提供了UpdateAllViews方法,可以在doc发生变更时,通知所有的view更新视图,实际上是调用view的OnUpdate方法实现更新:

void wxDocument::UpdateAllViews(wxView *sender, wxObject *hint)
{
    wxList::compatibility_iterator node = m_documentViews.GetFirst();
    while (node)
    {
        wxView *view = (wxView *)node->GetData();
        if (view != sender)
            view->OnUpdate(sender, hint);
        node = node->GetNext();
    }
}

wxView并没有实现OnUpdate方法,这个需要用户自行实现,对于绘图类的,最简单的办法就是直接调用更新图板:

void CProjectView::OnUpdate(wxView *sender, wxObject *hint)
{
    if (canvas)
        canvas->Refresh();
}

原文地址:https://www.cnblogs.com/psbec/p/9784664.html

时间: 2024-11-09 03:47:43

wxWidgets源码分析(8) - MVC架构的相关文章

转载Aaron博客 ---- jQuery 2.0.3 源码分析core - 整体架构

jQuery 2.0.3 源码分析core - 整体架构 整体架构 拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! github上最新是jquery-master,加入了AMD规范了,我就以官方最新2.0.3为准 整体架构 jQuery框架的核心就是从HTML文档中匹配元素并

js菜鸟进阶-jQuery源码分析(1)-基本架构

导读: 本人JS菜鸟一枚,为加强代码美观和编程思想.所以来研究下jQuery,有需要进阶JS的同学很适合阅读此文!我是边看代码(jquery2.2.1),边翻“javascript高级程序设计”写的,有很多基本知识点我都写了书本对应的章节.有分析得不好的还请各位多多指教,更正! 希望我的分析对大家有所帮助,谢谢! 一.代码构成 (function(global, factory){ if ( typeof module === "object" && typeof mo

jQuery源码分析-01总体架构

1. 总体架构 1.1自调用匿名函数 self-invoking anonymous function 打开jQuery源码,首先你会看到这样的代码结构: (function( window, undefined ) { // jquery code })(window); 1.这是一个自调用匿名函数.在第一个括号内,创建一个匿名函数:第二个括号,立即执行 2.为什么要创建这样一个“自调用匿名函数”呢? 通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命

jQuery 2.0.3 源码分析core - 整体架构

转载http://www.cnblogs.com/aaronjs/p/3278578.html 整体架构 jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作. 例如: $().find().css() $().hide().html('....').hide(). 从上面的写法上至少可以发现2个问题 1. jQuery对象的构建方式 2 .jQuery方法的调用方式 分析一:jQuery的无new构建 JavaScript是函数式语言,函数可以实现类,类就是面向对象编程中最基本的概

wxWidgets源码分析(9) - wxString

目录 wxString wxString的中文字符支持 wxString与通用字符串的转换 字符集转换 wxString wxString的中文字符支持 中文字符的编码格式如下: 汉字 GBK 区位码 UTF-8 UTF-16 中 D6 D0 54 48 E4 B8 AD 4E 2D 文 CE C4 46 36 E6 96 87 65 87 不同操作系统的默认内码 Windows系统(默认GBK):41 42 d6 d0 ce c4 Linux系统(默认UTF-8):41 42 e4 b8 ad

wxWidgets源码分析(1) - App启动过程

目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wxApp入口定义 wxApp通过IMPLEMENT_APP宏注册App类,这个宏同时定义了入口,实现在wx/app.h文件中. // wx/app.h 文件中定义 #define IMPLEMENT_APP(app) wxIMPLEMENT_APP(app); // 可以忽略 wxIMPLEMENT_

wxWidgets源码分析(7) - 窗口尺寸

目录 窗口尺寸 概述 窗口Size消息的处理 用户调整Size消息的处理 调整窗口大小 程序调整窗口大小 wxScrolledWindow设置窗口大小 获取TextCtrl控件最合适大小 窗口尺寸 概述 类型 说明 Size 当前窗口实际大小,通过wxWindow::SetSize()获取: Client Size 客户区大小: Best Size 最合适的大小,用户可以实现wxWindow::DoGetBestSize()方法,自定义返回最合适的大小: Best Client Size 最合适

wxWidgets源码分析(6) - 窗口关闭过程

目录 窗口关闭过程 调用流程 关闭文档 删除视图 删除文档对象 关闭Frame App清理 多文档窗口的关闭 窗口的正式删除 窗口关闭过程总结 如何手工删除view 窗口关闭过程 调用流程 用户点击窗口的关闭按钮后,Win32系统会向当前的Frame对象发送WM_CLOSE消息,此时会进入到Frame的wxFrame::MSWWindowProc函数进行处理: WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX

Docker源码分析之——Docker架构

Docker是PaaS圈内开源的基于LXC的应用容器引擎,基于Go语言开发,遵从Apache2.0协议. 最近一年来,Docker在云计算方面的热度持续升温,社区等活跃度也持续走高,使得大家对于Docker普遍持有积极态度. 笔者在研究生期间,主要从事PaaS方面的研究与实践工作,具体的研究平台为开源的Cloud Foundry.最近Docker的火热,更是让自己处于对Docker的学习过程中,不能自拔. 从功能的角度,Docker于Cloud Foundry的warden相仿.然而从产品的定位