使用Windows API命令关闭子窗口

时间:2011-12-09 01:12:04

标签: windows

- 更新2/29/12感谢大家的帮助。在使用SendMessage()获取所需行为之前,我已经调用了BringWindowToTop()。如果你有更好的方法,请随时在这里发布,但我认为这个问题已经结束。再次感谢!

- 更新12/9/11昨晚我写了一个糟糕的小C#项目来演示。

    private void button1_Click(object sender, EventArgs e)
    {    
        //kill the windows which have our documents open, namely EXCEL!
        Process[] processes = Process.GetProcessesByName("Excel");
        //find excel
        foreach (Process p in processes)
        {
            //hunt down its children
        U     List<IntPtr> Children = new List<IntPtr>();
            Children = GetChildWindows(p.MainWindowHandle);                
            foreach (IntPtr child in Children)
            {
                //see if the child has our document
                foreach (string xo in is_opened_files)
                {                        
                    if (GetText(child).Contains(xo))
                    {
                        //kill excel's children (oh noes!)
                        SendMessage((uint)child, WM_CLOSE, 0, 0);
                    }
                }
            }
        }
        //clean up the list of files we opened
        is_opened_files.Clear();
    }

行为会根据列表对象Children&lt;&gt;的顺序而改变。如果列表的顺序与子窗口的Z顺序匹配,意味着要评估的第一个Child也是最顶层的子窗口,则子接受并处理SendMessage()命令以按预期关闭。如果您更改子项的顺序&lt;&gt;这样就可以按不同的顺序处理项目,只关闭最顶层的子窗口。

我想,重要的是,似乎只有最顶层的孩子会接受并处理这个命令,但我找不到任何关于为什么甚至我们应该期待这种行为的文档。

- 结束更新12/9/11

我只想打破细节。我是使用Windows API的新手,但我认为至少我理解如何正确地进行调用。

我正在使用内置Powerbuilder 11.5的应用程序。我正在从数据库中读取一些blob并将它们写入临时目录中的文件,然后使用ShellExecuteExW来启动该文件。用户可以启动多个文件或不启动文件。这些文件可能是.xls(x),. doc(x),。pdf以及各种文本或图像格式(.txt,.jpg ......等)的混合。

因此,用户可以在各自的应用程序中打开文档,但是当他们关闭我的应用程序时,我需要告诉我为他们打开的所有文档也要关闭。

现在,我正在枚举系统中的所有窗口句柄,类和标题,然后在标题中搜索我为它们打开的文档的名称。我也可以验证ClassName是我感兴趣的东西。我正在使用FindWindowEx,GetWindowText和GetClassName。

找到我的文件名后,我抓取窗口的关联句柄,然后使用带有WM_CLOSE参数值的SendMessage告诉窗口关闭。

在处理SDI应用程序(如acrobat reader,MS Word,MS Paint等)时,这非常有效。使用Excel时,这种情况很糟糕。

问题似乎是,如果用户在Excel中打开了多个文档,则子项只有在最顶层子窗口时才会关闭。

我搜索过并搜索过,无法找到此行为的来源。如果我在SendMessage之前立即调用BringWindowToTop,那么窗口会按预期关闭。否则他们就会留下来。 Microsoft文档似乎暗示将wm_close发送到窗口是关闭它的正确方法,无论其作为子/父或Z顺序的状态如何。

我还尝试使用GetAncestor和GA_PARENT参数值来获取Child的Parent窗口,然后使用SendMessage通知Parent窗口它应该在相关子窗口上执行WM_MDIDESTROY。结果与我将SendMessage WM_CLOSE直接发送给孩子的结果相同。最顶层的孩子会关闭,但就是这样。

我错过了什么?

以下是一些示例代码:

//assume I have already executed the code to
//get the window handle by searching the title for my file name

//define some constants
CONSTANT long WM_MDIDESTROY = 545
CONSTANT long WM_MDIACTIVATE = 546
CONSTANT long WM_CLOSE = 16

//lul_hWnd = handle to child window, assume we have it already
//lul_parenthWnd = handle to immediate ancestor of childWindow

uLong lul_hWnd, lul_parenthWnd
long ll_null

setnull(ll_null)

//Example 1
//this works when the window is an SDI app or an MDI with maximized children
if IsWindow(lul_hWnd) then
    SendMessage(lul_hWnd,WM_CLOSE,ll_null, ll_null) 
end if

//Example 2
//this seems to work with most instances, including for an MDI with multiple children
if IsWindow(lul_hWnd) then
    BringWindowToTop(lul_hWnd)
    SendMessage(lul_hWnd,WM_CLOSE,ll_null,ll_null)
end if

//Example 3
//The result of this code seems to be the same as the first example
if IsChild(lul_parenthWnd,lul_hWnd) then
    SendMessage(lul_parenthWnd, WM_MDIDESTROY, lul_hWnd, ll_null)
end if

//Example 4
//Adding a preceeding MDIACTIVATE message seems to have no effect
if IsChild(lul_parenthWnd,lul_hWnd) then
    SendMessage(lul_parenthWnd, WM_MDIACTIVATE, lul_hWnd, ll_null)
    SendMessage(lul_parenthWnd, WM_MDIDESTROY, lul_hWnd, ll_null)
end if

另外,如果您对如何更好地做到这一点有任何想法,我会全力以赴。我真的不想为用户使用这些文件启动的所有不同应用程序管理OLE服务器。

感谢您的考虑和愉快的编码:)

* edit - 我最初有链接到我在这里列出的函数和参数的所有MSDN库条目,但我被迫删除它们,因为我是新来的。

她的根链接:MSDN WinAPI documentation

2 个答案:

答案 0 :(得分:0)

Haven自己尝试过,但看起来ShellExecuteEx与ShellExecute的做法相同,但为您提供了一个可以与TerminateProcess一起使用的流程句柄。

但是,我同意Harry的评论,即您已杀死用户可能在其中一个多文档应用中打开的任何其他文档。虽然我可能会同意幻想足球池应该被关闭,但如果你的应用程序这样做,我不确定它是不是很好。 (当首席财务官打电话给你时,因为你破坏了他整夜工作的财务报表,并且会在几分钟内公布,但不要说我们没有警告你。)

祝你好运,

特里。

答案 1 :(得分:0)

试试:

[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, Keys wParam, Int32
lParam);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32.dll", EntryPoint = "EnumDesktopWindows", ExactSpelling = false,
CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDelegate
lpEnumCallbackFunction, IntPtr lParam);

public delegate bool EnumDelegate(IntPtr hWnd, int lParam);

// returns a list with handles who you need
public static List<IntPtr> getVisibleProcesses()
{
    var handle = new List<IntPtr>();

    EnumDelegate filter = delegate(IntPtr hWnd, int lParam)
    {
        StringBuilder strbTitle = new StringBuilder(255);
        int nLength = GetWindowText(hWnd, strbTitle, strbTitle.Capacity + 1);
        string strTitle = strbTitle.ToString();

        // adds to list, tasks who appear in the taskbar and contains the string "Excel"
        if (IsWindowVisible(hWnd) && strTitle.Contains("Excel"))
            handle.Add(hWnd);

        return true;
    };
    EnumDesktopWindows(IntPtr.Zero, filter, IntPtr.Zero);

    return handle;
}

private void button1_Click(object sender, EventArgs e)
{
    const uint WM_CLOSE = 0x10;

    foreach (IntPtr h in getVisibleProcesses())
        PostMessage(h, WM_CLOSE, 0, 0);
}