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

问题描述 投票:0回答:4

我有一个功能:

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 中执行此操作,我找不到任何简单的示例。有人可以给出代码片段吗。再次感谢)

c++ multithreading winapi createwindow
4个回答
10
投票

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

因此,虽然您可以按照您的要求进行操作,但 Win32 确实设计用于与同一个线程具有关联性的进程中的所有窗口。创建多个 UI 线程确实没有任何好处。你所能成功的就是让你的生活变得异常且不必要的复杂。


6
投票

您可以在“非主”线程上创建窗口,但请注意这些窗口附加到创建线程,并且您需要确保在那里实现消息循环并继续调度发布在队列上的消息。如果你不这样做,你的窗户就会冻结。

参见:

系统不会自动为每个消息创建一个消息队列 线。相反,系统仅为线程创建消息队列 执行需要消息队列的操作。 如果螺纹 创建一个或多个窗口,必须提供消息循环;这 消息循环从线程的消息队列中检索消息并 将它们分派给适当的窗口过程。


6
投票

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


0
投票

您还可以将自定义 WM 消息发送回主窗口,以便它为您创建一个新窗口(可能带有一些定义参数,如 WPARAM 和 LPARAM),并获取 HWND 作为该消息的 LRESULT。这在我的简单案例中对我有用。

你可以

#define WM_CREATE_MY_WINDOW (WM_APP+1)

在你的主 WndProc 中

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
   if(msg==WM_CREATE_MY_WINDOW){
      // Create the window that you need, maybe defined by the content of WPARAM and LPARAM
      // and return its handle as result.
      return CreateWindowEx(0, L"EDIT", L"Edit control", WS_CHILD/*styles you need*/, 
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        hWnd/*or other parent passed in params*/, nullptr, hInstance, nullptr);
   }
}

然后将主窗口的 HWND 以及 Message2 参数传递给线程,该参数看起来像结构中的函数指针。

struct HWNDParam{
   HWND hWnd = nullptr;
   void* param = nullptr;
   HWNDParam(HWND hWnd, void* param){
      this->hWnd = hWnd;
      this->param = param;
   }
}

在你的 win main 中,你将指针传递给该结构以获取 Message2 和 HWND。

int WINAPI WinMain(HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) {

   ...
   HWND toSend = createMainWindow(P2pSocket);

   HWNDParam* pHWndParam = new HWNDParam(toSend/*your main wnd*/, Message2);
   hThread = CreateThread(NULL, 0, ClientThread, 
        pHWndParam, 0, &dwThreadId);

   ...
   
}

在你的线程内

DWORD WINAPI ClientThread(LPVOID lpParam){
   HWNDParam* pHWndParam = (HWNDParam*)lpParam;
   
   // Send message to your main thread to create the window for you
   HWND hWndNew = (HWND)SendMessage(pHWndParam->hWnd, WM_CREATE_MY_WINDOW, 
      0/*some params maybe*/, 0/*some params maybe*/);
   
   // And delete the data when not necessary anymore
   delete pHWndParam;
}
© www.soinside.com 2019 - 2024. All rights reserved.