在非主线程中创建窗口

本文探讨了在非主线程中创建窗口的问题,并通过实例代码展示了如何确保窗口能在分线程中正常工作,关键在于保持消息循环与窗口创建在同一线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//========================================================================
//TITLE:
// 在非主线程中创建窗口
//AUTHOR:
// norains
//DATE:
// Saturday 29-December-2007
//Environment:
// VS2005 + SDK-WINCE5.0-MIPSII
//========================================================================

很多朋友都会有过这样的经历,为什么在主线程中创建窗口且窗口工作很正常,但一移到非主线程(有的朋友喜欢叫它为工作线程),却无法正常工作.本文就这个问题和各位探讨,可能无法做到尽善尽美,但能抛砖引玉也算是欣慰了.

在主线程中创建一个能够正常工作的窗口,估计地球人都知道.

这是一段工作正常的代码:


#include
"windows.h"

HWNDg_hWnd
=NULL;
HINSTANCEg_hInst;


LRESULTWndProc(HWNDhWnd,UINTwMsg,WPARAMwParam,LPARAMlParam)
{
returnDefWindowProc(hWnd,wMsg,wParam,lParam);
}

voidCreateWnd(void)
{

WNDCLASSwc
={0};
wc.style
=0;
wc.lpfnWndProc
=WndProc;
wc.cbClsExtra
=0;
wc.cbWndExtra
=0;
wc.hInstance
=g_hInst;
wc.hIcon
=NULL;
wc.hCursor
=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground
=(HBRUSH)GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName
=NULL;
wc.lpszClassName
=TEXT("SimpleWindow");

RegisterClass(
&wc);

g_hWnd
=CreateWindowEx(0,
TEXT(
"SimpleWindow"),
TEXT(
"SimpleWindow"),
WS_VISIBLE,
0,
0,
200,
200,
NULL,
NULL,
g_hInst,
0);
}



intWINAPIWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTRlpCmdLine,
intnCmdShow)
{
//TODO:Placecodehere.

g_hInst
=hInstance;

CreateWnd();

//Themessageloop
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(
&msg);
DispatchMessage(
&msg);
}

return0;
}

如果我们创建一个线程,然后在这个线程中创建窗口,看看带给我们的是什么:

#include
"windows.h"

HWNDg_hWnd
=NULL;
HINSTANCEg_hInst;


LRESULTWndProc(HWNDhWnd,UINTwMsg,WPARAMwParam,LPARAMlParam)
{
returnDefWindowProc(hWnd,wMsg,wParam,lParam);
}

voidCreateWnd(void)
{

WNDCLASSwc
={0};
wc.style
=0;
wc.lpfnWndProc
=WndProc;
wc.cbClsExtra
=0;
wc.cbWndExtra
=0;
wc.hInstance
=g_hInst;
wc.hIcon
=NULL;
wc.hCursor
=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground
=(HBRUSH)GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName
=NULL;
wc.lpszClassName
=TEXT("SimpleWindow");

RegisterClass(
&wc);

g_hWnd
=CreateWindowEx(0,
TEXT(
"SimpleWindow"),
TEXT(
"SimpleWindow"),
WS_VISIBLE,
0,
0,
200,
200,
NULL,
NULL,
g_hInst,
0);
}


DWORDCreateThread(PVOIDpArg)
{
CreateWnd();
return0;
}


intWINAPIWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTRlpCmdLine,
intnCmdShow)
{
//TODO:Placecodehere.

g_hInst
=hInstance;

HANDLEhThrd
=CreateThread(NULL,0,CreateThread,NULL,0,NULL);
CloseHandle(hThrd);

//Themessageloop
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(
&msg);
DispatchMessage(
&msg);
}

return0;
}


我们似乎什么都没见到,只是窗口一闪,啥都没了.因为g_hWnd为全局变量,我们的理智告诉我们,在主线程没有退出之前,g_hWnd是不会销毁的.而用断点调试,将会发现在WndProc函数中只能接收WM_CREATE及以后一些消息,之后的再也收不到了,特别是WM_PAINT似乎就凭空消失了!那么,代码什么都没变更,只是移动到了分线程中,为何会出现这个问题呢?

一切似乎很简单,在MSDN中我们找到了答案(原文见:http://support.microsoft.com/kb/90975/en-us):

In a multithreaded application, any thread can call the CreateWindow() API to create a window. There are no restrictions on which thread(s) can create windows.

It is important to note that the message loop and window procedure for the window must be in the thread that created the window. If a different thread creates the window, the window won't get messages from DispatchMessage(), but will get messages from other sources. Therefore, the window will appear but won't show activation or repaint, cannot be moved, won't receive mouse messages, and so on.

该段话大意是:窗口在任何线程中都可以创建,但消息循环必须要和创建窗口在同一线程,否则窗口将无法从DispatchMessage()获取任何消息!

原来如此,最重要是这么一句:It is important to note that the message loop and window procedure for the window must be in the thread that created the window.

好吧,那么我们在支线程中放置消息循环代码,看看是什么结果吧:


#include
"windows.h"

HWNDg_hWnd
=NULL;
HINSTANCEg_hInst;


LRESULTWndProc(HWNDhWnd,UINTwMsg,WPARAMwParam,LPARAMlParam)
{
returnDefWindowProc(hWnd,wMsg,wParam,lParam);
}

voidCreateWnd(void)
{

WNDCLASSwc
={0};
wc.style
=0;
wc.lpfnWndProc
=WndProc;
wc.cbClsExtra
=0;
wc.cbWndExtra
=0;
wc.hInstance
=g_hInst;
wc.hIcon
=NULL;
wc.hCursor
=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground
=(HBRUSH)GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName
=NULL;
wc.lpszClassName
=TEXT("SimpleWindow");

RegisterClass(
&wc);

g_hWnd
=CreateWindowEx(0,
TEXT(
"SimpleWindow"),
TEXT(
"SimpleWindow"),
WS_VISIBLE,
0,
0,
200,
200,
NULL,
NULL,
g_hInst,
0);
}


DWORDCreateThread(PVOIDpArg)
{
CreateWnd();

//Themessageloop
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(
&msg);
DispatchMessage(
&msg);
}


return0;
}


intWINAPIWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTRlpCmdLine,
intnCmdShow)
{
//TODO:Placecodehere.

g_hInst
=hInstance;

HANDLEhThrd
=CreateThread(NULL,0,CreateThread,NULL,0,NULL);
CloseHandle(hThrd);


MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(
&msg);
DispatchMessage(
&msg);
}

return0;
}

一切正常,如同在主线程创建一样!

当然了,还有点需要注意的,在这个例子中,由于消息循环在主线程和分线程都分别存在,如果在WndProc()调用PostQuitMessage(),那么退出的也仅仅是分线程,而主线程还是会不停地在等待消息,从而导致程序无法正常退出.不过倒不用过分担心,和这个示例代码不同,在实际代码编写中,在主线程往往都会创建主窗口,而在这个主窗口消息处理函数调用PostQuitMessage()则完全可以让主线程正常退出.

事实告诉我们,非主线程创建窗口也能工作正常,只要我们注意一点:消息循环必须要和创建窗口在同一线程!
在子线程中创建文件选择窗口而避免阻塞主线程通常需要使用一些并发编程技术。由于直接从非主线程操作UI元素通常会导致线程安全问题和不可预测的行为,因此需要特别注意。以下是在不同平台上实现该目标的一般方法: 1. 对于Java的Swing应用,你可以使用`SwingUtilities.invokeLater`或者`SwingUtilities.invokeAndWait`来确保UI的更新在事件调度线程(EDT)中执行。在子线程中处理文件选择逻辑,然后通过`invokeLater`提交一个任务到EDT来更新UI。 ```java new Thread(() -> { // 文件选择逻辑 File file = showFileDialog(); // 假设这是调用文件选择窗口的方法 // 使用SwingUtilities.invokeLater来确保UI的更新在EDT中执行 SwingUtilities.invokeLater(() -> { // 更新主线程中的UI元素 }); }).start(); ``` 2. 对于JavaFX应用,可以使用`Platform.runLater`方法来在JavaFX的主线程中更新UI。 ```java new Thread(() -> { // 文件选择逻辑 File file = showFileDialog(); // 假设这是调用文件选择窗口的方法 // 使用Platform.runLater来确保UI的更新在JavaFX的主线程中执行 Platform.runLater(() -> { // 更新主线程中的UI元素 }); }).start(); ``` 3. 在.NET环境(例如WPF),可以使用`Dispatcher.Invoke`方法来将代码调度回UI线程执行。 ```csharp new Thread(() => { // 文件选择逻辑 var file = ShowFileDialog(); // 假设这是调用文件选择窗口的方法 // 使用Dispatcher.Invoke来确保UI的更新在UI线程中执行 Dispatcher.Invoke(() => { // 更新主线程中的UI元素 }); }).Start(); ``` 需要注意的是,虽然这些方法可以将任务调度到主线程,但并不意味着它们会创建一个独立的文件选择窗口。通常,文件选择对话框是由系统提供的,它们在创建的时候就需要指定父窗口(在GUI框架中通常是一个窗口句柄或组件),并且它们的行为由操作系统和GUI框架共同决定。如果你的应用程序需要在非主线程中处理文件选择逻辑,可以考虑异步地启动一个文件选择对话框,并在对话框关闭时从主线程处理选择结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值