多线程 – 用于更新MFC应用程序窗口的C 11个线程.需要SendMessage(),PostMessage()吗?

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

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

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

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

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

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

我一直在使用Visual Studio模板尝试使用实验性MFC应用程序进行简单测试,该模板左侧有一个树形控件,用于在工作线程中构建树.

如果我有一个CViewTree,一个显示树视图的MFC窗口,我想从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停靠窗格,就像函数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()似乎在CClassView :: OnNewFolder()中的join()上死锁,我认为这意味着C 11线程和UI线程之间的某种同步阻止了C 11线程到达它的一边()?

是的,当线程等待SendMessage()返回而消息处理程序在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似乎使用实际的Window句柄而不是lambda中的this指针来表示C 11线程会更安全.以防万一由于某些原因导致该指针未定义,例如控件被删除了?

顺便说一句#3通过#include< ppltasks.h>提供的Microsoft并发命名空间是C 11线程的替代品.并发命名空间函数比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以及Using Messages and Message Queues.

有关MFC的具体内容,请参阅Messages and Commands in the Framework.

注意#2:Multithreading with C++ and MFC,特别是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与VS 2012和Windows 8的以下博客.虽然使用VS 2017的C / WinRT似乎比Windows运行时模板库(WRL)更适合Windows 10:

> Accessing WinRT From Desktop apps (Part 1)
> Accessing WinRT from Desktop Apps (Part 2)

注意#4:MFC Desktop Applications这是一个有很多链接的跳跃点.另请参阅MFC COM,其中包含许多关于MFC与COM以及本文Introduction to COM的链接.请参见MFC Macros and Globals.

至于使用AfxGetMainWnd()获取主应用程序窗口,Microsoft开发人员中心在文章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.

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

>并发任务功能比C 11 std:thread更容易使用,并且在使用协同程序时更灵活,但是std :: async比std :: thread更容易使用,并且也适用于co_await
>在WinRT中使用C / WinRT和Async类型函数时,使用co_await的coroutines看起来是一个很好的并发添加(请参阅C++ Coroutines: Understanding operator co_await的技术说明)
>您可以使用concurrency :: task<>创建自己的异步函数模板作为函数的返回类型或使用concurrency :: create_task(),你可以使用co_await这样的任务
>你也可以使用co_await和std :: async(),因为std :: async()返回一个std :: future<>具有Awaitable接口(尽管日期为2016年11月,见await/yield: C++ coroutines)
>您也可以将co_await与std :: future<>一起使用由std :: packaged_task<>的get_future()方法提供(另见What is the difference between packaged_task and async)
>你可以使用std :: experimental :: generator< type>创建生成器函数作为函数返回类型以及co_yield运算符,以在生成的系列中返回指定类型的值
>更新MFC UI要求任何代码在MFC UI线程中运行MFC对象,因此需要Windows消息与其他线程的MFC窗口和窗口类对象进行通信,或者必须将线程上下文/关联性切换到该对象的UI线程上下文
> winrt :: apartment_context可用于捕获当前线程上下文,之后使用co_await恢复,后者可用于捕获稍后要重用的主UI线程上下文(参见文章“使用C / WinRT进行并发和异步操作”中的Programming with thread affinity in mind)
> co_await winrt :: resume_background();可用于将当前线程的上下文推送到后台线程,这对于可能位于主UI线程上下文中的冗长任务非常有用,并且您希望确保它不是
>当向窗口发送消息时,确保窗口实际上已经创建并存在,在应用程序启动期间,应用程序必须先创建窗口才能使用它们;仅仅因为MFC类存在并不意味着已经创建了底层窗口
> :: SendMessage()是同步的,其中发送消息并返回响应
> :: PostMessage()是异步的,其中发送消息并且不返回响应
> using :: PostMessage()要小心,在接收者使用它们之前,消息中发送的指针不会超出范围,因为在:: PostMessage()返回和接收消息的消息句柄之间通常会有一个延迟.消息
>可能最直接的方法是在消息映射中使用ON_MESSAGE()宏,并使用afx_msg LRESULT OnMessageThing(WPARAM,LPARAM)的消息处理程序接口
>您可以在以WM_APP的已定义常量开头的空间中使用Windows消息标识符,并且可以在不同的类中使用相同的标识符
>你可以很容易地使用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
07004
May 06, 2018 at 3:27PM by Brent Rector, Kenny Kerr

CppCon 2017: Scott Jones & Kenny Kerr
07005
Published on Nov 2, 2017

使用Visual Studio 2017社区版,我使用Visual Studio样式创建了一个新的MFC单文档界面(SDI)项目.应用程序出现后,它看起来像下图.

screen shot of MFC app in Visual Studio theme

辅助函数用于消息

我做的第一个更改是提供一种方法将Windows消息发送到我想要更新的窗格之一(ClassView或OutputWindow).由于MainFrm.h中的CMainFrame类具有这些窗口的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()来消除指针超出范围问题.并发类适用于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窗口的原始裸基础结构.我将这些放入CMainFrame类中,因为它是一个中心点,AfxGetMainWnd()函数允许我在MFC应用程序中的任何位置访问该类的对象.包含这些原始函数的其他类是合适的.

然后,我将消息处理程序放入BEGIN_MESSAGE_MAP和END_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()中为“New”的消息处理程序插入以下代码,将文本字符串放入“Build”输出窗口:

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和并发

为了测试这一点以及使用C / WinRT和MFC进行测试,我向CMainFrame :: OnCreate()添加了以下并发任务,该任务在应用程序启动时调用.此源旋转一个任务,然后使用C / WinRT的Syndication功能获取RSS提要列表,并在OutputWindow窗格中显示标题为“Build”的标题,如上面的屏幕截图所示.

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

要使用并发和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的附加编译器选项,这些选项在下面的屏幕截图中用蓝色箭头标记.
screen shot of Visual Studio solution properties showing changes

使用co_await与MFC和C / WinRT

从@IInspectable的有用评论我看了Visual Studio 2017和MFC的协同程序.我一直对它们感到好奇,但似乎我无法想出任何可以编译而没有使用co_await的错误.

然而,从@IInspectable的评论中的链接开始,我找到了这个YouTube视频的链接,CppCon 2016: Kenny Kerr & James McNellis “Putting Coroutines to Work with the Windows Runtime”,它在10点28分左右有一个源代码示例,最后我能够提出一些可以编译和工作的内容.

我创建了以下函数,然后我用concurrency :: create_task()替换上面的源代码,并使用函数调用lambda代替以下函数.函数调用很简单,myTaskMain(this);在int CMainFrame :: OnCreate(LPCREATESTRUCT lpCreateStruct)方法中替换concurrency :: create_task([this](){lambda,然后在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();因为使用它们会触发异常并删除它们似乎没有任何区别
>移动睡眠(5000);在co_await之后离开它导致函数睡眠5秒,这意味着UI线程睡了5秒

我在调试器中发现的是,在调用函数myTaskMain()时,函数立即返回,并且在后台执行协程时,UI线程继续运行. UI快速显示,然后大约五秒钟后,其他操作,更新输出窗口的“构建”选项卡中的类视图树和RSS提要列表发生.

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

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

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

screen shot of mouse hover over green co_await showing operator overload

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

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的部分.

这是一个简单的演示函数,它创建了一个concurrency :: task<>使用lambda并返回与co_await一起使用的任务.这个特殊的lambda返回一个int,因此该函数被定义为一个返回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.

以上用于返回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;
}

然后使用courrency :: task<>将它与co_await一起使用如:

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

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

虽然std :: thread不能与co_await一起使用,导致编译错误,但您可以将std :: async与co_await一起使用.原因是co_await运算符所需的返回值以及std :: thread,std :: thread的返回值和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对象,因此创建此类对象的另一种方法是使用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();
}

return语句使用逗号运算符引入序列点,以便我们启动任务运行,然后在正在运行的任务上调用get_future()方法. get_future()方法的结果,std :: future< int>是函数实际返回的内容.

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

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

int jkjk = co_await mySleepTaskStdFutureInt();

生成器与co_yield和std :: experimental :: generator< type>

在调查co_await时,我遇到了co_yield,它用于返回值作为一组值的生成器的一部分.使用Co_yield的Visual Studio 2017需要包含头文件实验/生成器.以下是生成一系列整数的生成器的简单示例.

#include <experimental/generator>

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

此功能可以与范围一起使用,如:

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执行上述循环,包括9.

更复杂的消息:更新ClassView pan

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

在ClassView.h文件中的CClassView类中,我添加了以下数据结构.顺便说一句,在我完成之后,我意识到这可能是错误的地方,因为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 }
};

然后,我通过在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); });
https://stackoverflow.com/questions/50669108/c11-threads-to-update-mfc-application-windows-sendmessage-postmessage-re

转载注明原文:多线程 – 用于更新MFC应用程序窗口的C 11个线程.需要SendMessage(),PostMessage()吗?