在另一线程中创建窗口(非主线程)

10

我有一个函数:

HWND createMainWindow(P2p_Socket_Machine * toSend){

    HWND hMainWnd = CreateWindow( 
        L"Class",/*(LPCWSTR) nameOfConference.c_str()*/L"Chat",  WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, 
    CW_USEDEFAULT, 0, 600,400, 
    (HWND)NULL, (HMENU)NULL, 
    /*(HINSTANCE)hlnstance*/NULL, NULL 
    ); 

    if (!hMainWnd) { 
        MessageBox(NULL, L"Cannot create main window", L"Error", MB_OK); 
        return 0; 
    }

    CreateWindowA("LISTBOX",NULL, WS_CHILD|WS_VISIBLE|WS_BORDER|WS_VSCROLL|LBS_NOTIFY|LBS_MULTIPLESEL,310,30,255,275,hMainWnd,(HMENU)List_Box,NULL,NULL);

    CreateWindowExA(NULL,"BUTTON", "Refresh", WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,385,310,100,24,hMainWnd,(HMENU)Button_Refresh, NULL ,NULL);

    CreateWindowExA(NULL,"BUTTON", "Send", WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,385,334,100,24,hMainWnd,(HMENU)Button_Send, NULL ,NULL);

    CreateWindowExA(NULL,"BUTTON", "New", WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,385,354,100,24,hMainWnd,(HMENU)Button_New, NULL ,NULL);

    CreateWindowA("EDIT",0,WS_BORDER|WS_VISIBLE|WS_CHILD|ES_LEFT|ES_MULTILINE|WS_VSCROLL|WS_DISABLED,
    10,30,265,275,hMainWnd,(HMENU)Text_Box_Get,NULL,NULL);

    CreateWindowA("EDIT",0,WS_BORDER|WS_VISIBLE|WS_CHILD|ES_LEFT|ES_MULTILINE|WS_VSCROLL,
    10,320,265,45,hMainWnd,(HMENU)Text_Box_Send,NULL,NULL);

    SetWindowLongPtr(hMainWnd,GWLP_USERDATA,(LONG_PTR)toSend);

    ShowWindow(hMainWnd, SW_SHOW); 
    //UpdateWindow(hMainWnd);

    return hMainWnd;

}

这是我的程序的主要部分:

int WINAPI WinMain(HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, int 
nCmdShow) 
{
WNDCLASSEX wc; 
    wc.cbSize = sizeof(wc); 
    wc.style = CS_HREDRAW | CS_VREDRAW; 
    wc.lpfnWndProc = MyFunc; 
    wc.cbClsExtra = 0; 
    wc.cbWndExtra = 0; 
    wc.hInstance = hlnstance; 
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL; 
    wc.lpszClassName = L"Class"; 
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
HWND toSend = createMainWindow(P2pSocket);

//some code

hThread = CreateThread(NULL, 0, ClientThread, 
            Message2, 0, &dwThreadId);

        if (hThread == NULL)
        {
            cout<<"Create thread filed";
            exit(10);
        }


    while (GetMessage(&msg, NULL, 0, 0)) { 

        TranslateMessage(&msg); 
        DispatchMessage(&msg);

    }

    return msg.wParam;   

当我在程序的主要部分调用createMainWindow()函数时,它按照预期的工作,但当我在我的线程(ClientThread)中运行它时,它不起作用。我已经阅读过应该只在主线程中创建窗口的内容。这是真的吗?如果是,那么最简单的方法是从另一个线程调用此函数以在主线程中完成吗?
谢谢大家。现在我知道问题所在,但我卡在解决方案上了。 我的客户端线程代码如下:
while(1){

    vector<HWND> AllHandlers;

    pair<string,string> Answer = Pointer->receiveMsgByUdp();

    if(!Pointer->isMyLocalAddress(Answer.first)){

        int type = messageUdpContentType(Answer.second);

        switch(type){

        case 0 :

            Pointer->sendMsgToIpUdp(Answer.first,"<?xml version='1.0'?><accepted/>");
            AllHandlers = getAllHandlersOfElementsOnWindowsByIdentityCode(Pointer->getAllHandlers(),List_Box);
            for(vector<HWND>::iterator j = AllHandlers.begin();j!=AllHandlers.end();j++)
                if(SendMessageA(*j, LB_FINDSTRINGEXACT, 0, (LPARAM)Answer.first.c_str())==LB_ERR)
                    SendMessageA(*j, LB_ADDSTRING, 0, (LPARAM)Answer.first.c_str());

            break;

        case 1 :
            AllHandlers = getAllHandlersOfElementsOnWindowsByIdentityCode(Pointer->getAllHandlers(),List_Box);
            for(vector<HWND>::iterator j = AllHandlers.begin();j!=AllHandlers.end();j++)
                if(SendMessageA(*j, LB_FINDSTRINGEXACT, 0, (LPARAM)Answer.first.c_str())==LB_ERR)
                    SendMessageA(*j, LB_ADDSTRING, 0, (LPARAM)Answer.first.c_str());

            break;

        case 2 :
            AllHandlers = getAllHandlersOfElementsOnWindowsByIdentityCode(Pointer->getAllHandlers(),List_Box);
            for(vector<HWND>::iterator j = AllHandlers.begin();j!=AllHandlers.end();j++)
                if((i = SendMessageA(*j, LB_FINDSTRINGEXACT, 0, (LPARAM)Answer.first.c_str()))!=LB_ERR)
                    SendMessageA(*j,LB_DELETESTRING, 0, (LPARAM)Answer.first.c_str());

            break;

        case 3 :

            userReply = MessageBoxW(NULL, L"Принять приглашение на конференцию?",
                    L"", MB_YESNO | MB_ICONQUESTION); 
            if (userReply==IDYES){

                //todo: Проверка на создание встречи, в которой уже состоишь
                string nameOfConf = fetchNameOfInviteConf(Answer.second);
                Pointer->createConference(nameOfConf);
                HWND toSendTo = createMainWindow(Pointer);
                Pointer->setHandlerInfo(nameOfConf,toSendTo);               
                Pointer->addNewMemberToConference_ServerType(nameOfConf,Answer.first);
                string toSend = string("<?xml version='1.0'?><inviteAccepted>") + nameOfConf + string("</inviteAccepted>");
                Pointer->sendMsgToIpUdp(Answer.first,toSend);

            }
            break;

        case 4 :

            string nameOfConf = fetchNameOfInviteAcceptConf(Answer.second);

            toSend.clear();
            Participants.clear();
            Participants = Pointer->getCurrentParticipants(nameOfConf);
            toSend+="<?xml version='1.0'?>";
            toSend+="<conference>";
            toSend+="<nameOfConference>";
            toSend+=nameOfConf;
            toSend+="</nameOfConference>";
            for(vector<string>::iterator i = Participants.begin();i!=Participants.end();i++){
                toSend+="<participant>" + *i + "</participant>";
            }
            toSend+="</conference>";



            Pointer->addNewMemberToConference_ClientType(nameOfConf,Answer.first);

            Pointer->sendToIpTcp(Answer.first,toSend);

            break;

    }

函数receiveMsgByUdp()会使此线程停止直到接收到消息。很抱歉我缺乏知识,但我应该使用哪些函数或其他东西来解决这个问题?我应该重写我的方法receiveMsgByUdp()以便为异步,还是如何调用createMainWindow()函数在主线程上运行?关于最后一种变体:我该如何在纯WinAPI中实现这一点,我找不到任何简单的示例。能否提供一些代码片段。再次感谢


谢谢大家。我成功让这个程序工作了。我使用了David Heffernan的方法,并参考了这个链接上的信息:https://dev59.com/dVrUa4cB1Zd3GeqPiENt - knightOfSpring
3个回答

9

确实可以在除主UI线程之外的其他线程中创建窗口。但是,这些窗口将与创建它们的线程有亲和性,并且您需要在每个创建窗口的线程中运行消息泵。

因此,虽然您可以做到您所要求的事情,但 Win32 实际上设计成所有进程中的所有窗口都具有与同一线程的亲和性。从创建多个 UI 线程中真正能够得到的东西很少。你唯一成功的是使你的生活变得异常复杂而不必要。


是的,这很复杂。但在我的情况下,我找不到其他解决方案。在非主线程套接字接收消息时,当我接收到特殊消息时,我需要创建窗口。我不知道如何在主线程中创建它。我是winapi的新手,也许你可以告诉我如何完成这种技巧? - knightOfSpring
但如果我需要使用纯WinAPI来完成这个功能?会非常复杂吗? - knightOfSpring
抱歉,是什么类型的消息? - knightOfSpring
在 WM_APP 范围内定义一个由您指定的内容。 - David Heffernan
或者您可以切换到异步套接字操作。或者您可以将套接字操作放在后台线程上。 - Raymond Chen
显示剩余3条评论

5
你可以在“非主线程”上创建窗口,但请注意这些窗口附加到创建线程,并且您需要确保在那里实现消息循环并继续调度发布在队列中的消息。如果不这样做,您的窗口将会冻结。
参见:
- 使用消息和消息队列 - 消息处理--关于消息和消息队列 系统不会为每个线程自动创建消息队列。相反,系统仅为执行需要消息队列的操作的线程创建消息队列。如果线程创建了一个或多个窗口,则必须提供消息循环;此消息循环从线程的消息队列中检索消息并将其分派到适当的窗口过程。

所以,我只需要在非主线程中添加代码来分派消息。但是有一个问题:在这个线程中,我使用套接字,接收消息,并且一些函数会停止我的循环,直到套接字接收到消息。我认为解决这个问题的唯一方法是在我的非主线程中创建另一个线程来从窗口分派消息。你觉得呢? - knightOfSpring
@Roman 在我看来,建议使用多个UI线程并不是最好的解决方案。你的意见呢?你同意我的观点吗? - David Heffernan
你有多种API可用于与套接字一起使用,你所需要做的就是不要避免阻塞调用,在API内部花费长时间执行。 - Roman R.
1
@David 我同意UI在后台线程上的想法可能是OP认为的解决方案,但实际上它使应用程序结构更加不合理。如果后台线程UI是可能的,这并不意味着这是一个好主意。我的建议是将所有通信代码保留在后台。至少,它不会干扰UI,不会阻塞UI,也不会被诸如消息框之类的东西所阻塞。我会将所有UI保留在主线程上,并实现某些“线程间”通信来连接套接字代码和UI。 - Roman R.

5

如果您在另一个线程中创建窗口,则还需要在该线程上实现消息循环,因为排队的消息会被发布到拥有窗口的线程的消息队列中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接