4

在使用 C++/CX 和 ++/WinRT 的简单 UWP 应用程序花费了一些时间之后,我开始享受一些针对 Windows UI 应用程序开发的环境的功能。

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

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

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

  • 连接到另一台计算机并与之交换数据
  • 打开一个数据文件并解析它以更新 UI 视图
  • 将数据文件转换为另一种格式,例如 CSV 并导出到文件
  • 读取 CSV 等格式的文件并将内容转换为数据文件
  • 在 UI 中执行数据文件呈现的搜索和过滤

我遇到的问题类似于我可以在 MFC 中有多个 GUI 线程吗?,但是,我正在寻找一种通用方法,而不是该问题中的特定进度条更新。

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

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

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

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 停靠窗格,就像在AddItemsToPane(m_wndClassView);创建 C++11 线程的同一位置调用该函数一样。当 C++11 线程仅用于提供线程方法实际工作的可见指示时,窗格更新延迟 5 秒。

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

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

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

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

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

是的,当消息处理程序在等待线程完成时,线程等待SendMessage()返回时存在死锁join()。根据Windows 开发中心 SendMessage 功能

将指定的消息发送到一个或多个窗口。该SendMessage 函数调用指定窗口的窗口过程并且在窗口过程处理完消息之前不返回

要发送消息并立即返回,请使用SendMessageCallback orSendNotifyMessage函数。要将消息发布到线程的消息队列并立即返回,请使用PostMessageorPostThreadMessage 函数。

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

顺便说一下 #3通过 提供的 Microsoftconcurrency命名空间#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); });
}

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

笔记

注意#1: 关于消息和消息队列以及使用消息和消息队列

有关 MFC 特定内容,请参阅框架中的消息和命令

Note #2: Multithreading with C++ and MFC , 特别是Multithreading: Programming Tips其中说。

如果您有一个多线程应用程序,该应用程序使用 CWinThread 对象以外的方式创建线程,则您无法从该线程访问其他 MFC 对象。换句话说,如果要从辅助线程访问任何 MFC 对象,则必须使用多线程:创建用户界面线程或多线程:创建工作线程中描述的方法之一创建该线程。这些方法是唯一允许类库初始化处理多线程应用程序所需的内部变量的方法。

注意 #3: 可从经典桌面应用程序调用的 UWP API说:

除了一些值得注意的例外,一般规则是可以从经典桌面应用程序调用通用 Windows 平台 (UWP) API。作为此一般规则例外的两个主要 API 领域是 XAML UI API 和要求调用应用程序具有包标识的 API。UWP 应用具有定义明确的应用模型,并且它们具有包标识。经典桌面应用没有明确定义的应用模型,也没有包标识。已转换为 UWP 应用的经典桌面应用确实具有包标识。

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

注意#4: MFC 桌面应用程序是一个有很多链接的起点。另请参阅MFC COM,这是一个起点,其中包含许多关于 MFC 和 COM 的链接以及这篇文章COM 简介。另请参阅MFC 宏和全局

至于使用AfxGetMainWnd()获取主应用程序窗口,微软开发者中心在文章AfxGetMainWnd中有这样的说法:

如果从应用程序的主线程调用 AfxGetMainWnd,它会根据上述规则返回应用程序的主窗口。如果从应用程序中的辅助线程调用该函数,则该函数返回与进行调用的线程关联的主窗口。

4

1 回答 1

4

经过一些实验,有一些我觉得很舒服的建议。

  • concurrency任务功能比 C++11 更易于使用,std:thread并且在与协程一起使用时更灵活,但是比 C++11 更易于使用并且也可以与协同程序一起std::async使用std::threadco_await
  • 在使用 C++/WinRT 和 WinRT 中的类型函数时,使用的协程co_await看起来是一个很好的补充(请参阅C++ 协程:了解运算符 co_await以获得技术解释)concurrencyAsync
  • concurrency::task<>您可以使用模板作为函数的返回类型或使用来制作自己的异步函数,concurrency::create_task()并且可以co_await与此类任务一起使用
  • 您还可以使用co_awaitwith std::async()sincestd::async()返回std::future<>具有 Awaitable 接口的 a(请参阅await/yield: C++ coroutines尽管它的日期为 2016 年 11 月)
  • 您还可以使用co_awaitastd::future<>get_future()方法提供的 a std::packaged_task<>(另请参阅Packaged_task 和 async 之间的区别
  • 您可以使用std::experimental::generator<type>作为函数返回类型的生成器函数与co_yield运算符一起在生成的系列中返回指定类型的值
  • 更新 MFC UI 要求任何代码在 MFC UI 线程中运行 MFC 对象已创建,因此需要 Windows 消息与 MFC 窗口和来自其他线程的窗口类对象进行通信,或者您必须将线程上下文/关联性切换到 UI该对象的线程上下文
  • winrt::apartment_context可用于捕获当前线程上下文并稍后恢复使用co_await,可用于捕获主 UI 线程上下文以供稍后重用(请参阅使用 C++/WinRT 的并发和异步操作一文中考虑线程关联的编程)
  • co_await winrt::resume_background();可用于将当前线程的上下文推送到后台线程,这对于可能在主 UI 线程上下文中的冗长任务很有用,并且您希望确保它不是
  • 向窗口发送消息时,请确保窗口实际已创建并存在,在应用程序启动期间,应用程序必须创建窗口才能使用它们;仅仅因为 MFC 类存在并不意味着底层窗口已经创建
  • ::SendMessage()是同步的,其中发送消息并返回响应
  • ::PostMessage()是异步的,其中发送消息并且不返回响应
  • 使用::PostMessage()时要小心,在接收者使用它们之前,在消息中发送的指针不会超出范围,因为通常在::PostMessage()返回和接收消息的消息句柄之间存在延迟
  • 可能最直接的方法是ON_MESSAGE()在消息映射中使用带有消息处理程序接口的宏afx_msg LRESULT OnMessageThing(WPARAM, LPARAM)
  • 您可以在以定义的常量开头的空间中使用 Windows 消息标识符,WM_APP并且可以在不同的类中使用相同的标识符
  • 您可以在 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 和 Windows 上 C++ 的未来 2017 年 11 月 2 日发布

使用 Visual Studio 2017 社区版,我使用 Visual Studio 样式创建了一个新的 MFC 单文档界面 (SDI) 项目。应用程序启动后,如下图所示。

Visual Studio 主题中 MFC 应用程序的屏幕截图

消息的辅助函数

我所做的第一个更改是提供一种将 Windows 消息发送到我要更新的窗格(ClassView 或 OutputWindow)的方法。由于CMainFrameMainFrm.h 中的类具有这些窗口的 MFC 对象,如下所示:

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;

我修改了该类以提供一种向这些窗口发送消息的方法。我选择使用SendMessage()而不是PostMessage()消除指针超出范围的问题。该类concurrencySendMessage().

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 窗口的原始、裸露的基础设施。我将它们放入CMainFrame类中,因为这是一个中心点,该AfxGetMainWnd()函数允许我在 MFC 应用程序中的任何位置访问该类的对象。包装这些原始函数的附加类是合适的。

然后我将消息处理程序放入BEGIN_MESSAGE_MAPEND_MESSAGE_MAP宏中的每个类中。输出窗口更新是最简单和最简单的,如下所示:

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;
}

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

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

通过上述更改,我能够在BOOL CMFCAppWinRTDoc::OnNewDocument()以下代码中插入“新建”的消息处理程序,以将文本字符串放入“构建”输出窗口:

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

为了测试这一点以及使用带有 MFC 的 C++/WinRT 进行测试,我添加了以下在应用程序启动时调用的concurrency任务。CMainFrame::OnCreate()此源衍生出一个任务,然后使用SyndicationC++/WinRT 的功能来获取 RSS 提要列表,并在标有“构建”的 OutputWindow 窗格中显示标题,如上面的屏幕截图所示。

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();
});

要使用concurrencyC++/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在下面的屏幕截图中用蓝色箭头标记。 显示更改的 Visual Studio 解决方案属性的屏幕截图

co_await与 MFC 和 C++/WinRT 一起使用

从@IInspectable 的有用评论中,我了解了 Visual Studio 2017 和 MFC 的协程。我一直对它们很好奇,但似乎我无法想出任何可以编译而不会因使用co_await.

然而,从@IInspectable 评论中的链接开始,我发现了一个指向此 YouTube 视频的链接,CppCon 2016:Kenny Kerr 和 James McNellis “Put Coroutines to Work with the Windows Runtime”,在 10:28 左右有一个源代码示例最后我能够想出一些可以编译和工作的东西。

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

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()调用函数时,函数立即返回,UI 线程继续运行,而协程在后台执行。UI 立即显示,然后大约五秒钟后,发生了更新类视图树和输出窗口“构建”选项卡中的 RSS 提要列表的附加操作。

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

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

我注意到在 Visual Studio 2017 中co_await使用的 withclient.RetrieveFeedAsync(uri)是绿色的,而co_awaitused withco_await ui_thread;是蓝色的。将鼠标悬停在绿色上,co_await我得到一个工具提示,表明这是不同版本的co_await.

鼠标悬停在绿色 co_await 上的屏幕截图,显示操作员过载

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

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

Sleep(15000);

concurrency::task<>使用适用的滚动异步任务co_await

我很好奇的一件事是能够创建我自己的异步任务,我可以使用co_await类似于 C++/WinRT 的异步类型函数。我花了一些时间搜索,直到最终找到这篇文章Concurrency and asynchronous operations with C++/WinRT,其中有一个名为Asychronously return a non-Windows-Runtime type的部分。

这是一个简单的演示函数,它使用 lambda 创建 aconcurrency::task<>并返回然后使用的任务co_await。这个特定的 lambda 正在返回一个int,因此该函数被定义为一个返回一个的任务intconcurrency::task<int>

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

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

int jj = co_await mySleepTaskAsync();

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

以上用于返回 a 的函数,winrt::Windows::Foundation::IAsyncAction例如myTaskMain()上面的函数。

如果你喜欢,你也可以直接使用带有co_awaitas 的 lambda:

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

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

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

然后将其与co_awaitusing一起使用concurrency::task<>,如下所示:

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

std::async使用适用的滚动异步任务co_await

虽然std::thread不能与 一起使用co_await,导致编译错误,但您可以使用std::asyncwith co_await。原因是co_awaitoperator 需要的返回值的种类以及 a 的返回值和std::threadastd::thread的返回值的std::async不同std::future<>

co_await运算符要求它所操作的变量是 a ,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 对象,创建此类对象的另一种方法是创建一个任务,std::packaged_task<>然后启动该任务,并使用该任务的get_future()方法来获得一个std::future<>然后可以使用的co_await

例如,我们可以有以下简单的函数,它将创建一个任务包,开始执行任务,然后返回一个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语句使用逗号运算符引入一个序列点,以便我们启动任务运行,然后调用get_future()正在运行的任务上的方法。方法的结果get_future(),astd::future<int>是函数实际返回的结果。

创建的任务std::packaged_task() 必须以使用变量调用之类的函数开始。如果您不启动任务,则std::future<>函数返回的将永远不会有变量,并且co_await等待 Awaitable 完成并提供值的将永远不会触发。结果是你之后的源co_await将不会被执行,因为co_await永远不会被触发。

发电机co_yieldstd::experimental::generator<type>

在调查co_await时,我遇到了co_yield哪个用于返回一个值作为一组值的生成器的一部分。使用 Visual Studio 2017co_yield需要包含头文件experimental/generator。这是一个生成一系列整数的生成器的简单示例。

#include <experimental/generator>

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

这个函数可以与 ranged 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 的每个整数值执行上述循环。

更复杂的消息:更新 ClassView pan

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

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

// 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 }
};

然后,我通过在方法中执行两个concurrency任务来执行此消息处理程序,这些任务执行CMainFrame::OnCreate()了时间延迟,然后更新了 ClassView 窗口树的内容。

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); });
于 2018-06-08T03:43:27.530 回答