用于更新 MFC 应用程序的 C++11 线程 windows。需要 SendMessage()、PostMessage() 吗?

C++11 threads to update MFC application windows. SendMessage(), PostMessage() required?

在使用 C++/CX 和 ++/WinRT 处理简单的 UWP 应用程序后,我开始享受针对 Windows UI 应用程序开发环境的一些功能.

现在不得不回到更熟悉的 MFC 应用程序开发,我想将我的方法更改为类似于 UWP 应用程序开发的方法。思路是使用异步C++11线程生成内容和修改MFC中显示的内容UI.

我想做的主要改变是使用 C++11 线程来卸载一些耗时的任务,并让这些线程将结果传回主 MFC UI。

我希望卸载到 C++11 线程上的一些任务与我在 UWP 应用程序中使用 C++/CX 和 C++/WinRT 的异步任务类似:

我 运行 遇到的问题与 Can I have multiple GUI threads in MFC? 中描述的问题类似,但是,我正在寻找一种通用方法,而不是该问题中的特定进度条更新。

我一直在尝试使用 Visual Studio 模板对实验性 MFC 应用程序进行简单测试,该模板在左侧停靠了一个树控件,以便在工作线程中构建树。

如果我有一个 CViewTree,一个显示树视图的 MFC window,我想从 C++11 线程更新它,我目前正在使用 ::PostMessage()请求更新停靠窗格中的树控件。

如果我使用具有全局 std::thread 的 lambda,例如以下代码:

std::thread t1;

void CClassView::FillClassView()
{
    // ::SendMessage() seems to deadlock so ::PostMessage() is required.
    t1 = std::thread([this]() { Sleep(5000);  ::PostMessage(this->m_hWnd, WM_COMMAND, ID_NEW_FOLDER, 0); });

}

MFC 停靠窗格的消息处理程序,如下所示:

void CClassView::OnNewFolder()
{
    t1.join();   // this join seems to deadlock if ::SendMessage() is used.

    AddItemsToPane(m_wndClassView);
}

确实使用树控件内容更新了 MFC 停靠窗格,就像在创建 C++11 线程的同一位置调用函数 AddItemsToPane(m_wndClassView); 一样。当使用 C++11 线程只是为了提供线程方法实际工作的可见指示时,窗格更新延迟 5 秒。

我的问题是我希望 C++11 线程为树控件创建内容并将其提供给停靠窗格而不是让停靠窗格生成内容。

到目前为止,我能想到的唯一方法是开发我自己的 class 库,它将为 MFC 库和控件提供 C++11 线程类似物,使用 ::PostMessage() 发送适当的Windows 消息到指定的 MFC window 或控件。

我想知道是否可以让 C++11 线程拥有自己的、隐藏它们更新的 MFC 控件,然后向 UI 发送消息,要求 UI使用影子 MFC 控件的内容更新其显示的控件?或者还有其他一些人们正在使用的方法吗?

我正在寻找其他一些不太费力的方法来解决从 C++11 线程更新 MFC UI 的问题。

顺便说一句,#1 ::SendMessage() 似乎在 CClassView::OnNewFolder() 中的 join() 上出现死锁,我认为这意味着某种同步C+11 线程和 UI 线程之间阻止 C++11 线程到达其一侧 join()?

是的,线程等待 SendMessage() 到 return,而消息处理程序在 join() 处等待线程完成,因此存在死锁。根据 Windows Dev Center SendMessage function:

Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.

To send a message and return immediately, use the SendMessageCallback or SendNotifyMessage function. To post a message to a thread's message queue and return immediately, use the PostMessage or PostThreadMessage function.

顺便说一下#2 似乎在 C+ 的 lambda 中使用实际的 Window 句柄而不是 this 指针+11 线程会更安全。以防万一 this 指针由于某些原因(例如控件被删除)变得未定义?

顺便提一下#3 Microsoft concurrency 命名空间通过 #include <ppltasks.h> 提供,是 C++11 线程的替代方案。 concurrency 命名空间函数比 C++11 线程具有更高的抽象级别,更易于使用。

例如上面使用的std:thread可以重写为:

void CClassView::FillClassView()
{
    concurrency::create_task([this]() { Sleep(5000);  ::SendMessage(this->m_hWnd, WM_COMMAND, ID_NEW_FOLDER, 0); });
}

这不需要使用 std::thread join() 来干净地终止线程。 SendMessage()PostMessage() 也可用于发送 Windows 消息,因为我们没有与 C++11 线程相同的死锁问题。

备注

注释 #1: About Messages and Message Queues as well as Using Messages and Message Queues.

有关 MFC 的特定内容,请参阅 Messages and Commands in the Framework

注释 #2: Multithreading with C++ and MFC and specifically Multithreading: Programming Tips 表示。

If you have a multithreaded application that creates a thread in a way other than using a CWinThread object, you cannot access other MFC objects from that thread. In other words, if you want to access any MFC object from a secondary thread, you must create that thread with one of the methods described in Multithreading: Creating User-Interface Threads or Multithreading: Creating Worker Threads. These methods are the only ones that allow the class library to initialize the internal variables necessary to handle multithreaded applications.

注释 #3: UWP APIs callable from a classic desktop app 表示:

With some notable exceptions, the general rule is that a Universal Windows Platform (UWP) API can be called from a classic desktop app. The two main areas of APIs that are an exception to this general rule are XAML UI APIs, and APIs that require the calling app to have a package identity. UWP apps have a well-defined app model, and they have a package identity. Classic desktop apps do not have a well-defined app model, and they do not have a package identity. A classic desktop app that has been converted to a UWP app does have a package identity.

另请参阅 2012 年 9 月以下关于 WinRT with VS 2012 和 Windows 8 的博客。尽管 C++/WinRT with VS 2017 似乎更适合 Windows 10 而不是 Windows使用的运行时模板库 (WRL):

注释 #4: MFC Desktop Applications which is a jumping off point with lots of links. See also MFC COM which is a jumping off point with lots of links about MFC with COM along with this article Introduction to COM. See as well MFC Macros and Globals.

关于使用AfxGetMainWnd()获取主应用window,微软开发者中心在文章AfxGetMainWnd:

中是这样说的

If AfxGetMainWnd is called from the application's primary thread, it returns the application's main window according to the above rules. If the function is called from a secondary thread in the application, the function returns the main window associated with the thread that made the call.

经过一些实验后,我提出了一些建议。

  • concurrency 任务功能比 C++11 std:thread 更易于使用,并且在与协程一起使用时更灵活,但是 std::asyncstd::thread 更易于使用] 并与 co_await 以及
  • 一起工作
  • 在使用 C++/WinRT 和 WinRT 中的 Async 类型函数时,使用 co_await 的协程看起来是对 concurrency 的一个很好的补充(有关技术信息,请参阅 C++ Coroutines: Understanding operator co_await解释)
  • 您可以使用 concurrency::task<> 模板作为函数的 return 类型创建自己的异步函数,或者使用 concurrency::create_task() 并且您可以使用 co_await 这样的函数任务
  • 您还可以将 co_awaitstd::async() 一起使用,因为 std::async() return 是一个具有 Awaitable 接口的 std::future<>(尽管参见 await/yield: C++ coroutines它的日期是 2016 年 11 月)
  • 您还可以将 co_awaitstd::future<> 一起使用,如 std::packaged_task<>get_future() 方法所提供的那样(另请参阅 What is the difference between packaged_task and async
  • 您可以使用 std::experimental::generator<type> 作为函数生成函数 return 类型以及 co_yield 运算符 return 生成系列中指定类型的值
  • 更新 MFC UI 要求 MFC UI 中的任何代码 运行 线程 MFC object 已创建,因此 Windows 消息是需要与来自其他线程的 MFC windows 和 window class object 通信,或者您必须将线程 context/affinity 切换到 UI 线程object
  • 的上下文
  • winrt::apartment_context 可用于捕获当前线程上下文,稍后使用 co_await 恢复,这可用于捕获主 UI 线程上下文以供稍后重用(请参阅 Programming with thread affinity in mind 在文章中使用 C++/WinRT 进行并发和异步操作)
  • co_await winrt::resume_background(); 可用于将当前线程的上下文推送到后台线程,这对于可能位于主 UI 线程上下文中且您想确保它不是
  • 向window发送消息时,请确保window确实已创建并存在,在应用程序启动期间应用程序必须创建windows才能使用它们; 仅仅因为 MFC class 存在并不意味着基础 window 已经创建
  • ::SendMessage() 是同步的,发送消息和响应 returned
  • ::PostMessage() 是异步的,其中发送消息而不是 returned
  • 响应
  • 使用 ::PostMessage() 小心,消息中发送的指针在接收者使用它们之前不要超出范围,因为通常在 ::PostMessage() return 和接收消息的消息句柄实际上对消息做了一些事情
  • 可能最直接的方法是在消息映射中使用 ON_MESSAGE() 宏,消息处理程序接口为 afx_msg LRESULT OnMessageThing(WPARAM, LPARAM)
  • 您可以在 space 中使用 Windows 消息标识符,以 WM_APP 定义的常量开头,并且可以在不同的 classes
  • 您可以很容易地使用 MFC 的大部分 C++/WinRT,只需一点点小心,尽管我只尝试了一些东西并且有一些限制,例如根据文档不使用 XAML
  • 如果您确实在 MFC 应用程序中使用 C++/WinRT,您将应用程序限制为具有 Windows 运行时 的 Windows 版本几乎意味着 Windows 10(这排除了将 C++/WinRT 与 Windows 7、POS Ready 7 等一起使用)
  • 使用 C++/WinRT 需要添加编译器选项 /stdc++17 以启用 C++ 语言标准的 ISO C++17 标准,使用协程需要 /await 编译器选项

这里是查看资源。

Microsoft Build 2018 Effective C++/WinRT for UWP and Win32 May 06, 2018 at 3:27PM by Brent Rector, Kenny Kerr

CppCon 2017: Scott Jones & Kenny Kerr C++/WinRT and the Future of C++ on Windows Published on Nov 2, 2017

使用Visual Studio 2017社区版,我使用Visual Studio风格创建了一个新的MFC单文档界面(SDI)项目。应用上来后,如下图所示

消息的辅助函数

我所做的第一个更改是提供一种方法来将 Windows 消息发送到我想要更新的其中一个窗格(ClassView 或 OutputWindow) .由于 MainFrm.h 中的 CMainFrame class 具有用于这些 windows 的 MFC object,如:

protected:  // control bar embedded members
    CMFCMenuBar       m_wndMenuBar;
    CMFCToolBar       m_wndToolBar;
    CMFCStatusBar     m_wndStatusBar;
    CMFCToolBarImages m_UserImages;
    CFileView         m_wndFileView;
    CClassView        m_wndClassView;
    COutputWnd        m_wndOutput;
    CPropertiesWnd    m_wndProperties;

我修改了 class 以提供一种向这些 windows 发送消息的方法。我选择使用 SendMessage() 而不是 PostMessage() 来消除指针超出范围的问题。 concurrency class 与 SendMessage() 配合使用效果很好。

LRESULT  SendMessageToFileView(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndFileView.SendMessage(msgId, wParam, lParam); }
LRESULT  SendMessageToClassView(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndClassView.SendMessage(msgId, wParam, lParam); }
LRESULT  SendMessageToOutputWnd(UINT msgId, WPARAM wParam, LPARAM lParam) { return m_wndOutput.SendMessage(msgId, wParam, lParam); }

这些是用于发送消息以更新各种 MFC windows 的原始、裸露的基础结构。我将它们放入 CMainFrame class 中,因为这是一个中心点,AfxGetMainWnd() 函数允许我在 class 中的任何位置访问 object MFC 应用程序。额外的 classes 来包装这些原始函数是合适的。

然后我将消息处理程序放入 BEGIN_MESSAGE_MAPEND_MESSAGE_MAP 宏中的每个 classes 中。输出 window 更新是最简单和最简单的,看起来像:

BEGIN_MESSAGE_MAP(COutputWnd, CDockablePane)
    ON_WM_CREATE()
    ON_WM_SIZE()
    // ADD_ON: message handler for the WM_APP message containing an index as
    //         to which output window to write to along with a pointer to the
    //         text string to write.
    //         this->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)_T("some text"));
    ON_MESSAGE(WM_APP, OnAddItemsToPane)
END_MESSAGE_MAP()

消息处理程序如下所示:

// ADD_ON: message handler for the WM_APP message containing an array of the
//         struct ItemToInsert above. Uses method AddItemsToPane().
LRESULT  COutputWnd::OnAddItemsToPane(WPARAM wParam, LPARAM lParam)
{
    switch (wParam) {
    case OutputBuild:
        m_wndOutputBuild.AddString((TCHAR *)lParam);
        break;
    case OutputDebug:
        m_wndOutputDebug.AddString((TCHAR *)lParam);
        break;
    case OutputFind:
        m_wndOutputFind.AddString((TCHAR *)lParam);
        break;
    }

    return 0;
}

我将方法原型与此枚举一起添加到 class 以使功能更易于使用。

enum WindowList { OutputBuild = 1, OutputDebug = 2, OutputFind = 3 };

通过上述更改,我能够将以下代码插入 BOOL CMFCAppWinRTDoc::OnNewDocument() 中 "New" 的消息处理程序,以将文本字符串放入 "Build" 输出 Window:

CMainFrame *p = dynamic_cast <CMainFrame *> (AfxGetMainWnd());

if (p) {
    p->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)_T("this is a test from OnNewDocument()."));
}

将 C++/WinRT 与 MFC 和 concurrency

结合使用

为了测试这一点以及使用 C++/WinRT 和 MFC 进行测试,我将以下 concurrency 任务添加到 CMainFrame::OnCreate() 中,该任务在应用程序启动时调用。此源衍生出一个任务,该任务然后使用 C++/WinRT 的 Syndication 功能来获取 RSS 提要列表并在标记为 "Build" 的输出 Window 窗格中显示标题,如屏幕所示上图

concurrency::create_task([this]() {
    winrt::init_apartment();

    Sleep(5000);

    winrt::Windows::Foundation::Uri uri(L"http://kennykerr.ca/feed");
    winrt::Windows::Web::Syndication::SyndicationClient client;
    winrt::Windows::Web::Syndication::SyndicationFeed feed = client.RetrieveFeedAsync(uri).get();
    for (winrt::Windows::Web::Syndication::SyndicationItem item : feed.Items())
    {
        winrt::hstring title = item.Title().Text();
        this->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)title.c_str());  // print a string to an output window in the output pane.
    }
    winrt::uninit_apartment();
});

要使用 concurrency 和 C++/WinRT 功能,我必须在 MainFrm.c 源文件顶部附近添加几个包含文件。

// ADD_ON: include files for using the concurrency namespace.
#include <experimental\resumable>
#include <pplawait.h>

#pragma comment(lib, "windowsapp")
#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.Web.Syndication.h"

此外,我必须修改解决方案的属性以指定 C++17 和 /await 的附加编译器选项,这些选项在下面的屏幕截图中用蓝色箭头标记。

在 MFC 和 C++/WinRT 中使用 co_await

根据@IInspectable 的有用评论,我查看了 Visual Studio 2017 和 MFC 的协程。我一直对它们很好奇,但似乎我无法想出任何可以通过使用 co_await.

编译而不会出错的东西

然而,从@IInspectable 的评论中的 link 开始,我发现了这个 YouTube 视频的 link,CppCon 2016: Kenny Kerr & James McNellis “Putting Coroutines to Work with the Windows Runtime",它有一个源代码示例 [=371] =] 最后我能够想出一些可以编译和工作的东西。

我创建了以下函数,然后我用它用 concurrency::create_task() 替换上面的源代码,并用对以下函数的函数调用替换 lambda。函数调用很简单,myTaskMain(this); 替换 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 方法中的 concurrency::create_task([this]() { lambda,然后在 OnCreate() 函数 body 上方添加以下源代码。

winrt::Windows::Foundation::IAsyncAction myTaskMain(CMainFrame *p)
{
    winrt::Windows::Foundation::Uri uri(L"http://kennykerr.ca/feed");
    winrt::Windows::Web::Syndication::SyndicationClient client;
    winrt::Windows::Web::Syndication::SyndicationFeed feed = co_await client.RetrieveFeedAsync(uri);

    Sleep(5000);
    for (winrt::Windows::Web::Syndication::SyndicationItem item : feed.Items())
    {
        winrt::hstring title = item.Title().Text();
        p->SendMessageToOutputWnd(WM_APP, COutputWnd::OutputBuild, (LPARAM)title.c_str());  // print a string to an output window in the output pane.
    }
}

我从被替换的 concurrency::create_task() 的源代码中做了两处更改:

  • 删除了 winrt::init_apartment();winrt::uninit_apartment(); 因为使用它们会触发异常并且删除它们似乎没有什么区别
  • Sleep(5000); 移到 co_await 之后,因为将它留在原处导致函数休眠 5 秒,这意味着 UI 线程休眠了 5 秒

我在调试器中发现的是,在调用函数 myTaskMain() 时,函数立即 return 并且 UI 线程继续 运行ning 协程在后台执行。 UI 立即显示,大约五秒钟后,附加操作更新了 Class 视图树和输出 Window 的 "Build" 选项卡中的 RSS 提要列表。

注意 #1: 我在其他测试中 运行 的另一件事是 UI 会冻结几秒钟(菜单是non-functional)。这似乎是由于 Sleep(5000); 表明 co_await 之后的代码正在主 UI 线程上 运行。在我开始探索使用 winrt::apartment_context ui_thread; 捕获主 UI 线程上下文以便然后使用 co_await ui_thread; 到 return 我的协程线程到主线程之后,应用程序行为的这种变化开始了UI线程上下文。

可能发生的情况是 client.RetrieveFeedAsync(uri) 立即得到满足,没有延迟,可能来自缓存,因此与其将任务推送到另一个线程,然后 returning 到调用者, co_await 立即返回结果,函数 myTaskMain() 能够立即继续使用当前线程,即主 UI 线程?

我注意到在 Visual Studio 2017 年,与 client.RetrieveFeedAsync(uri) 一起使用的 co_await 是绿色的,而与 co_await ui_thread; 一起使用的 co_await 是蓝色的。将鼠标悬停在绿色 co_await 上,我得到一个工具提示,表明这是 co_await.

的不同版本

注意 #2: 有一个 C++/WinRT 函数可以移动到后台线程上下文,winrt::resume_background() 可以与 co_await 一起使用。如果我通过以下两行代码将调用 client.RetrieveFeedAsync(uri) 之后的 Sleep(5000); 代码行替换为将线程上下文推送到后台线程来修改上述函数 myTaskMain() ,我没有看到冻结(UI 响应菜单选择)并且 RSS 提要文本行在大约 15 秒后显示在输出 Window 的 "Build" 选项卡中。

co_await winrt::resume_background();  // switch context to background thread

Sleep(15000);

使用与 co_await

一起使用的 concurrency::task<> 滚动异步任务

我很好奇的一件事是能够创建我自己的异步任务,我可以将其与 co_await 一起使用,类似于 C++/WinRT 的异步类型函数。我在海里度过了一段时间直到我终于找到这篇文章,Concurrency and asynchronous operations with C++/WinRT, with a section called Asychronously return a non-Windows-Runtime type

这是一个简单的演示函数,它使用 lambda 和 return 创建一个 concurrency::task<> 然后与 co_await 一起使用的任务。这个特定的 lambda 是 returning 一个 int 所以这个函数被定义为一个 return 一个 int, concurrency::task<int>

的任务
concurrency::task<int> mySleepTaskAsync()
{
    return concurrency::create_task([]() {
        Sleep(15000);
        return 5;
    });
}

以上函数然后在语句中与 co_await 运算符一起使用,例如:

int jj = co_await mySleepTaskAsync();

这将导致变量 jj 在等待 15 秒后的值为 5。

上面用在return是winrt::Windows::Foundation::IAsyncAction的函数中,比如上面的myTaskMain()函数

如果您愿意,您也可以直接将 lambda 与 co_await 一起使用,如:

int jj = co_await concurrency::create_task([]() {
    Sleep(15000);
    return 5;
});

或者你可以有一个正常的功能,例如:

int mySleepTaskAsyncInt()
{
        Sleep(15000);
        return 5;
}

然后使用 concurrency::task<> 将其与 co_await 一起使用,如:

int jj = co_await concurrency::create_task(mySleepTaskAsyncInt);

使用与 co_await

一起使用的 std::async 滚动异步任务

虽然 std::thread 不能与 co_await 一起使用,导致编译错误,但您可以将 std::asyncco_await 一起使用。原因是 co_await 运算符需要的 return 值的种类以及 std::threadstd::thread 和 [=414] 的 return 值的差异=] std::async 的值,一个 std::future<>.

co_await 运算符要求其操作的变量是 std::future<>,具有从线程检索结果的 get() 方法,并且是 Awaitable。

#include <future>

int mySleepTaskAsyncInt()
{
    Sleep(7000);
    return 5;
}

winrt::Windows::Foundation::IAsyncAction myTaskMain(CMainFrame *p)
{
    auto t1 = co_await std::async (std::launch::async, mySleepTaskAsyncInt);

    // do something with the variable t1
}

使用 std::packaged_task<>std::future<> 使用 co_await

滚动异步任务

由于 co_await 需要 Awaitable object,另一种创建此类 object 的方法是使用 std::packaged_task<> 创建任务,然后启动任务并使用get_future() 任务的方法来获得 std::future<> 然后可以与 co_await.

一起使用

例如,我们可以使用以下简单函数来创建任务包、开始执行任务,然后 return 一个 std::future<>。然后我们可以使用此函数作为 co_await 运算符的目标来实现协程。

#include <future>


std::future<int> mySleepTaskStdFutureInt()
{
    // create the task to prepare it for running.
    std::packaged_task<int()> task([]() {
        Sleep(7000);
        return 455;   // return an int value
    });

    // start the task running and return the future
    return task(), task.get_future();
}

然后在我们的源代码中,我们将使用类似于以下的函数:

int jkjk = co_await mySleepTaskStdFutureInt();

return 语句使用逗号操作符引入序列点,以便我们启动任务 运行ning 然后调用 [=449= 上的 get_future() 方法]宁任务。 get_future() 方法的结果,std::future<int> 是函数实际 return 编辑的结果。

使用 std::packaged_task() 创建的任务必须使用变量调用之类的函数启动 。如果您不启动任务,那么由函数编辑的 std::future<> return 将永远不会有变量,而正在等待 Awaitable 完成并提供值的 co_await 将永远不会火。结果是 co_await 之后的源不会被执行,因为 co_await 永远不会被触发。

带有 co_yieldstd::experimental::generator<type>

的生成器

在调查 co_await 时,我发现 co_yield 用于 return 值作为一组值生成器的一部分。使用 Visual Studio 2017 使用 co_yield 需要包含 header 文件 experimental/generator。这是生成一系列整数的生成器的简单示例。

#include <experimental/generator>

std::experimental::generator<int> makeSomeInts(int kCount)
{
    for (int i = 0; i < kCount; i++) {
        co_yield i;
    }
}

并且此函数可以与范围 for 一起使用,如:

for (int kkk : makeSomeInts(10)) {
    // code that uses the variable kkk which contains
    // an int from the generated range 0 up to be not including 10.
}

以上循环将对 0 到 9 的每个整数值执行。

更复杂的消息:更新Class视图面板

我还对 ClassView 树控件进行了实验,以提供一种执行最基本操作的简单方法:创建一个初始树控件并添加到其中。

CClassViewclass文件ClassView.h中,我添加了以下数据结构。顺便说一下,在我完成后,我意识到这可能是放置它的错误位置,因为 CFileView class 使用相同的树结构,因此相同的方法适用于这两个窗格。无论如何,我添加了以下内容:

// ADD_ON: enumeration listing the various types of tree control icons which
//         correspond to the position of a control in the tree.
// choose either classview_hc.bmp or classview.bmp for the bitmap strip that
// contains the 7 icons we are using for the images in our tree control.
// icons are standard size in height and width (15x15 pixels) in the order of:
//   - main root icon
//   - tree node icon which can be opened to show nodes beneath it
//   - folder icon which is used to indicate a folder
//   - method icon indicating a method of a class
//   - locked method icon
//   - member variable icon
//   - locked member variable icon

enum IconList { MainRoot = 0, TreeNode = 1, FolderNode = 2, MethodNode = 3, MethodLockedNode = 4, MemberNode = 5, MemberLockedNode = 6 };

// ADD_ON: struct used to contain the necessary data for a node in the tree control.
struct ItemToInsert {
    std::wstring  label;            // text to be displayed with the node.
    int           nImage;           // zero based offset of the node's icon in the image, one of enum IconList above.
    int           nSelectedImage;   // zero based offset of the node's icon in the image, one of enum IconList above.
};

我创建了一个消息处理程序,并将其添加到 ClassView.cpp

中的消息映射
ON_MESSAGE(WM_APP, OnAddItemsToPane)

并添加了实际的消息处理程序本身以及执行实际处理的辅助函数。

// ADD_ON: function for filling in the ClassView pane using an array of the
//         struct ItemToInsert above. array is terminated by an entry with
//         all zeros as in { _T(""), 0, 0 }
void CClassView::AddItemsToPane(CViewTree &xwndClassView, void *xrayp)
{

    if (xrayp == 0) return;

    // the images are icons that are laid out in a line of icons within a single bitmap image.
    // see class method OnChangeVisualStyle() for when the bitmap image is loaded and then
    // divided up into sections, 0 through 6, of the single bitmap image loaded.
    // see classview.bmp and classview_hc.bmp in the ResourceFiles list.


    HTREEITEM hRoot = xwndClassView.GetRootItem();
    HTREEITEM hClass = 0;
    ItemToInsert *xray = (ItemToInsert *)xrayp;

    for (int i = 0; xray[i].label.size() != 0; i++) {
        switch (xray[i].nImage) {
        case MainRoot:
            hRoot = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage);
            xwndClassView.SetItemState(hRoot, TVIS_BOLD, TVIS_BOLD);
            xwndClassView.Expand(hRoot, TVE_EXPAND);
            break;
        case TreeNode:
            hClass = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hRoot);
            break;
        case FolderNode:
            hClass = xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hRoot);
            break;
        case MethodNode:
        case MethodLockedNode:
        case MemberNode:
        case MemberLockedNode:
            xwndClassView.InsertItem(xray[i].label.c_str(), xray[i].nImage, xray[i].nSelectedImage, hClass);
            break;
        default:
            break;
        }
    }
}

// ADD_ON: message handler for the WM_APP message containing an array of the
//         struct ItemToInsert above. Uses method AddItemsToPane().
LRESULT  CClassView::OnAddItemsToPane(WPARAM wParam, LPARAM lParam)
{
    switch (wParam) {
    case 1:
        AddItemsToPane(m_wndClassView, (void *)lParam);
        break;
    }

    return 0;
}

然后我为初始树创建了一些示例数据,然后添加了一个节点。

// ADD_ON: this is the content to be put into the ClassView tree pane.
//         this is a tree structure.
CClassView::ItemToInsert xray2[] = {
    { _T("CFakeMainProject"), CClassView::MainRoot, CClassView::MainRoot },
        { _T("CFakeAboutDlg"), CClassView::TreeNode, CClassView::TreeNode },
            { _T("CFakeAboutDlg()"), CClassView::MethodNode, CClassView::MethodNode },
        { _T("CFakeApp"), CClassView::TreeNode, CClassView::TreeNode },
            { _T("CFakeApp()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("InitInstance()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("OnAppAbout()"), CClassView::MethodNode, CClassView::MethodNode },
        { _T("CFakeAppDoc"), CClassView::TreeNode, CClassView::TreeNode },
            { _T("CFakeAppDoc()"), CClassView::MethodLockedNode, CClassView::MethodLockedNode },
            { _T("~CFakeAppDoc()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("OnNewDocument()"), CClassView::MethodNode, CClassView::MethodNode },
        { _T("CFakeAppView"), CClassView::TreeNode, CClassView::TreeNode },
            { _T("CFakeAppView()"), CClassView::MethodLockedNode, CClassView::MethodLockedNode },
            { _T("~CFakeAppView()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("GetDocument()"), CClassView::MethodNode, CClassView::MethodNode },
        { _T("CFakeAppFrame"), CClassView::TreeNode, CClassView::TreeNode },
            { _T("CFakeAppFrame()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("~CFakeAppFrame()"), CClassView::MethodNode, CClassView::MethodNode },
            { _T("m_wndMenuBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
            { _T("m_wndToolBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
            { _T("m_wndStatusBar"), CClassView::MemberLockedNode, CClassView::MemberLockedNode },
        { _T("Globals"), CClassView::FolderNode, CClassView::FolderNode },
            { _T("theFakeApp"), CClassView::MemberNode, CClassView::MemberNode },
    { _T(""), 0, 0 }
};

CClassView::ItemToInsert xray3[] = {
    { _T("CAdditionalDelay"), CClassView::TreeNode, CClassView::TreeNode },
        { _T("CAdditionalDelayMethod()"), CClassView::MethodNode, CClassView::MethodNode },
    { _T(""), 0, 0 }
};

然后我通过在 CMainFrame::OnCreate() 方法中分离两个 concurrency 任务来练习这个消息处理程序,该方法做了一个时间延迟,然后更新了 ClassView window 树内容。

concurrency::create_task([this]() { Sleep(5000);  this->SendMessageToClassView(WM_APP, 1, (LPARAM)xray2); }); 
concurrency::create_task([this]() { Sleep(10000);  this->SendMessageToClassView(WM_APP, 1, (LPARAM)xray3); });