主窗口中如何获取子窗口某控件句柄?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了主窗口中如何获取子窗口某控件句柄?相关的知识,希望对你有一定的参考价值。

在子窗口中设定了一个ip地址,主窗口启动时用到。类似参数设定。

楼主我给你看个例程吧
第九课 子窗口控件

--------------------------------------------------------------------------------
本课中我们将探讨控件,这些控件是我们程序主要的输入输出设备。
理论:
WINDOWS 提供了几个预定义的窗口类以方便我们的使用。大多数时间内,我们把它们用在对话框中,所以我们一般就它们叫做子窗口控件。子窗口控件会自己处理消息,并在自己状态发生改变时通知父窗口。这样就大大地减轻了我们的编程工作,所以我们应尽可能地利用它们。本课中我们把这些控件放在窗口中以简化程序,但是大多数时间内子窗口控件都是放在对话框中的。我们示例中演示的子窗口控件包括:按钮、下拉菜单、检查框、单选按钮、编辑框等。使用子窗口控件时,先调用CreateWindow 或 CreateWindowEx。在这里由于WINDOWS 已经注册了这些子控件,所以无须我们再注册。当然我们不能改变它们的类名称。譬如:如果您想产生一个按钮,在调用上述两个函数时就必须指定类名为"button"。其他必须指定的参数还有父窗口的句柄和将要产生的子控件的ID号。子控件的ID号是用来标识子控件的,故也必须是唯一 的。子控件产生后,当其状态改变时将会向父窗口发送消息。一般我们应在父窗口的WM_CREATE消息中产生字控件。子控件向父窗口发送的消息是WM_COMMAND,并在传递的参数wPara的底位中包括控件的ID号,消息号在wParam的高位,lParam中则包括了子控件的窗口的句柄。各类控件有不同的消息代码集,详情请参见WIN32 API参考手册。父窗口也可以通过调用函数SendMessage向子控件发送消息,其中第一个参数是子控件的窗口句柄,第二个参数是要发送的消息号,附加的参数可以在wParam和lParam中传递,其实只要知道了某个窗口的句柄就可以用该函数向其发送相关消息。所以产生了子窗口后必须处理WM_COMMAND消息以便可以接收到子控件的消息。

例子:
我们将产生一个窗口,在该窗口中有一个编辑框和一个按钮。当您按下按钮时 ,会弹出一个对话框其中显示了您在编辑框中输入的内容。另外,该应用程序还有一个菜单,其中有四个菜单项:
Say Hello -- 把一个字符串输入编辑控件;
Clear Edit Box -- 清除编辑控件中的字符串;
Get Text -- 弹出对话框显示编辑控件中的字符串;
Exit -- 退出应用程序。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box

.const
ButtonID equ 1 ; The control ID of the button control
EditID equ 2 ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

分析:
我们现在开始分析,
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
or ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
我们在WM_CREATE中产生子控件,其中在函数CreateWindowEx中给子控件窗口一个WS_EX_CLIENTEDGE风格,它使得子控件窗口看上去边界下凹,具有立体感。每一个子控件的类名都是预定义的,譬如:按钮的预定义类名是"button",编辑框是"edit"。接下来的参数是窗口风格,除了通常的窗口风格外,每一个控件都有自己的扩展风格,譬如:按钮类的扩展风格前面加有BS_,编辑框类则是:ES_,WIN32 API 参考中有所有的扩展风格的描述。注意:您在CreateWindowsEx函数中本来要传递菜单句柄的地方传入子窗口空间的ID号不会有什么副作用,因为子窗口控件本身不能有菜单。产生控件后,我们保存它们的句柄,然后调用SetFocus把焦点设到编辑控件上以便用户立即可以输入。接下来的是如何处理控件发送的通知消息WM_COMMAND:
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

我们以前讲过选择菜单想也会发送WM_COMMAND 消息,那我们应如何区分呢?看了下表您就会一目了然:

Low word of wParam High word of wParam lParam
Menu Menu ID 0 0
Control Control ID Notification code Child Window Handle

其中我们可以看到不能用wParam来区分,因为菜单和控件的ID号可能相同,而且子窗口空间的消息号也有可能为0。

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

您可以调用SetWindowText函数把一字符串繁缛到编辑控件中去,为了清0,传入NULL值。SetWindowText是一个通用函数,即可以用它来设定一个窗口的标题,也可以用它来改变一个按钮上的文字。如果是要得到按钮上的文字,则调用GetWindowText。

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

上面的片段是处理用户按钮事件的。他首先检查wParam的高字节看是否是按钮的ID 号,若是则检查低字节看发送的消息号是否BN_CLICKED,该消息是在按钮按下时发送的,如果一切都对,则转入处理该消息,我们可以从处理消息IDM_GETTEXT处复制全部的代码,但是更专业的办法是在发送一条IDM_GETTEXT消息让主窗口过程处理,这只要把传送的消息设置为WM_COMMAND,再把wParam的低字节中设置为IDM_GETTEXT即可。这样一来您的代码就简洁了许多,所以尽可能利用该技巧。最后,当然不是或有或无,必须在消息循环中调用函数TranslateMessage,因为您的应用程序需要在编辑框中输入可读的文字。如果省略了该函数,就不能在编辑框中输入任何东西。
参考技术A 假设有Form1/Form2,Form1上有一Button和Textbox1;Form2上有一个TextBox,

public partial class Form1 : Form

Form2 f=null ;
public Form1()

InitializeComponent();

private void Form1_Load(object sender, EventArgs e)

f = new Form2();
f.Show();

private void button1_Click(object sender, EventArgs e)

if (f == null) return;
foreach (Control c in f.Controls)

if (c is TextBox)

this.textBox1.Text = (c as TextBox).Text;
break;



本回答被提问者和网友采纳
参考技术B 用API函数:
HWND hWnddlg = AfxGetMainWnd()->m_hWnd; //主窗口句柄
HWND hwnd=::GetDlgItem(hWnddlg,IDC_BUTTON_SETUPBLOCK);//子窗口(或控件)句柄
参考技术C 你是想从子窗口中传参数到主窗口么?
或者是说主窗口加载的时候自动获取子窗口中的一个参数的值?追问

后者

如何从进程 ID 获取主窗口句柄?

【中文标题】如何从进程 ID 获取主窗口句柄?【英文标题】:How to get main window handle from process id? 【发布时间】:2010-12-25 17:20:09 【问题描述】:

如何从进程id获取ma​​in窗口句柄?

我想把这个窗口放在前面。

它在“进程资源管理器”中运行良好。

【问题讨论】:

如果您打开了两个 Firefox 窗口,哪一个是“主”窗口?他们是平等的。 Process Explorer 似乎会选择最近关注的那个。 Windows 不保留“主窗口” 的概念。有***窗口、窗口和拥有窗口。任何进程都可以有零个或多个***窗口。除非你提供一个简洁的说明是什么决定了“主窗口”,否则这个问题是无法回答的。 【参考方案1】:

我不相信 Windows(相对于 .NET)提供了一种直接的方式来实现这一点。

我知道的唯一方法是用EnumWindows() 枚举所有***窗口,然后找到每个属于GetWindowThreadProcessID() 的进程。这听起来间接且效率低下,但并没有您想象的那么糟糕——在典型情况下,您可能需要穿过十几个***窗口...

【讨论】:

我怎么知道主窗口? +1。您准确地描述了 msdn 文章链接所建议的内容。但在大约 1000 字以内。 @Alexey:来自 MSDN:“EnumWindows 函数不枚举子窗口。” @hometoast:经验造就简洁。我在那篇文章之前四年发表了这种方法。 作为参考,这是 .NET 检索主窗口句柄的方式:System.Diagnostics.MainWindowFinder.FindMainWindow 和 EnumWindowsCallback。所以可以说,.NET 也没有提供“直接方式”【参考方案2】:

这里有误会的可能。 .Net 中的 WinForms 框架自动将创建的第一个窗口(例如,Application.Run(new SomeForm()))指定为MainWindow。然而,win32 API 不承认每个进程都有一个“主窗口”的概念。消息循环完全能够处理系统和进程资源允许您创建的尽可能多的“主”窗口。因此,您的进程没有“主窗口”。在一般情况下,您可以做的最好的事情是使用EnumWindows() 让所有非子窗口在给定进程上处于活动状态,并尝试使用一些启发式方法来确定哪个是您想要的。幸运的是,大多数进程在大多数情况下可能只运行一个“主”窗口,因此在大多数情况下您应该会获得良好的结果。

【讨论】:

实际上,.NET 会在第一次访问System.Diagnostics.Process.MainWindowHandle 属性时缓存窗口句柄。窗口句柄没有预先存储。它使用 Jerry Coffin 在his answer 中概述的相同算法进行评估。 是的,你是对的。我不知道我从哪里得到这个说法 - 可能只是根据经验假设,即使窗口的枚举顺序不太可能得到保证。【参考方案3】:

虽然可能与您的问题无关,但请查看GetGUIThreadInfo Function。

【讨论】:

【参考方案4】:

只是为了确保您不会混淆 tid(线程 id)和 pid(进程 id):

DWORD pid;
DWORD tid = GetWindowThreadProcessId( this->m_hWnd, &pid);

【讨论】:

这是 OP 想要的反函数。【参考方案5】:

我检查了 .NET 如何确定主窗口。

我的发现表明它也使用了EnumWindows()

这段代码应该类似于 .NET 的方式:

struct handle_data 
    unsigned long process_id;
    HWND window_handle;
;

HWND find_main_window(unsigned long process_id)

    handle_data data;
    data.process_id = process_id;
    data.window_handle = 0;
    EnumWindows(enum_windows_callback, (LPARAM)&data);
    return data.window_handle;


BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam)

    handle_data& data = *(handle_data*)lParam;
    unsigned long process_id = 0;
    GetWindowThreadProcessId(handle, &process_id);
    if (data.process_id != process_id || !is_main_window(handle))
        return TRUE;
    data.window_handle = handle;
    return FALSE;   


BOOL is_main_window(HWND handle)
   
    return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);

【讨论】:

你能解释一下is_main_window背后的逻辑吗?我没有看到使用if (data.process_id != process_id || !IsWindowVisible(handle)) 的区别(在我的测试中)。此外,这种方法需要调整以支持具有多个主窗口(例如 Web 浏览器)的进程 ID。 @CamelCase GetWindow(handle, GW_OWNER) == 0 检查窗口不是owned window(例如对话框或其他东西)。 IsWindowVisible(handle) 检查窗口是可见的还是不隐藏的(很多没有 GUI 的应用程序仍然有一个隐藏的窗口,或者甚至是具有隐藏 GUI 的应用程序,例如在托盘中运行的配置应用程序)。因此,如果窗口可见且没有所有者,则该窗口被视为“主窗口”,这对大多数“主窗口”来说已经足够好。 这是对父母和所有者的一个很好的解释,可能会为很多人清除很多问题,以帮助添加或删除此代码中的逻辑:blogs.msdn.microsoft.com/oldnewthing/20100315-00/?p=14613 在简而言之,没有父窗口的窗口仍然可以拥有所有者并且不是***窗口。 这适用于某些应用程序,但它可能会因更复杂的事情而崩溃。根据此定义,某些应用程序(例如 MSO)具有许多“主”窗口,并且无法首先安全关闭。我发现检查窗口类名是获得大多数应用程序的“主”窗口的好方法。另外,请注意比赛条件。用户可能会在您循环并破坏您的代码时关闭应用程序【参考方案6】:

作为 Hiale 解决方案的扩展,您可以提供不同的或修改的版本,以支持具有多个主窗口的进程。

首先,修改结构以允许存储多个句柄:

struct handle_data 
    unsigned long process_id;
    std::vector<HWND> handles;
;

二、修改回调函数:

BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam)

    handle_data& data = *(handle_data*)lParam;
    unsigned long process_id = 0;
    GetWindowThreadProcessId(handle, &process_id);
    if (data.process_id != process_id || !is_main_window(handle)) 
        return TRUE;
    
    // change these 2 lines to allow storing of handle and loop again
    data.handles.push_back(handle);
    return TRUE;   
 

最后,修改主函数的返回值:

std::vector<HWD> find_main_window(unsigned long process_id)

    handle_data data;
    data.process_id = process_id;
    EnumWindows(enum_windows_callback, (LPARAM)&data);
    return data.handles;

【讨论】:

When you transfer control across stack frames, all the frames in between need to be in on the joke.【参考方案7】:

这是我基于最佳答案使用纯 Win32/C++ 的解决方案。这个想法是将所有需要的东西包装到一个函数中,而不需要外部回调函数或结构:

#include <utility>

HWND FindTopWindow(DWORD pid)

    std::pair<HWND, DWORD> params =  0, pid ;

    // Enumerate the windows using a lambda to process each window
    BOOL bResult = EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL 
    
        auto pParams = (std::pair<HWND, DWORD>*)(lParam);

        DWORD processId;
        if (GetWindowThreadProcessId(hwnd, &processId) && processId == pParams->second)
        
            // Stop enumerating
            SetLastError(-1);
            pParams->first = hwnd;
            return FALSE;
        

        // Continue enumerating
        return TRUE;
    , (LPARAM)&params);

    if (!bResult && GetLastError() == -1 && params.first)
    
        return params.first;
    

    return 0;

【讨论】:

如果应用程序有两个窗口,您可能需要仔细检查窗口是否没有所有者以避免过早退出循环:if (GetWindowThreadProcessId(hwnd, &amp;processId) &amp;&amp; processId == pParams-&gt;second &amp;&amp; GetWindow(hwnd, GW_OWNER) == 0)

以上是关于主窗口中如何获取子窗口某控件句柄?的主要内容,如果未能解决你的问题,请参考以下文章

MFC中怎样获取指定窗口的句柄

如何从进程 ID 获取主窗口句柄?

如何从进程 ID 获取主窗口句柄?

C++中如何获取当前窗口句柄?

在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。

VC TAB控件子对话框如何使用主对话框的成员函数.