【问题标题】:How can I force a redraw of a process whoʼs parent is a panel-control?如何强制重绘父级是面板控件的进程?
【发布时间】:2019-06-11 08:35:28
【问题描述】:

我的任务是创建一个应用程序启动器,它同时托管 Winforms 和 WPF 应用程序,并通过稍微不同的方法 Web 应用程序。向用户显示他们可以启动的应用程序列表,这些应用程序在启动时被“捕获”,并通过使用 SetParent 使面板成为进程 MainWindowHandle 的父级,将其保存在表单的面板内。该位似乎运行良好,启动时的应用程序被捕获并显示在给定面板中。

我遇到的一个特殊问题是,并非所有被捕获的应用程序都乐于在面板中开始绘制自己。它似乎与那些基于 WPF 的应用程序是隔离的,但这并不能保证。

实际上发生的情况是,如果 WPF 应用程序启动,它会被捕获并移动到面板,并且面板将保持空白,直到我单击面板,此时应用程序将愉快地重新绘制自己。从现在开始,应用程序似乎很乐意根据需要重新绘制自己而无需干预。

基本上我现在束手无策,尝试了以下 User32 本机和 .NET 方法; 无效(在表单上、面板上、承载这两者的选项卡控件上) 更新 刷新 带有过多参数的 SendMessage,包括尝试“你不应该这样做”WM_PAINT。 带有 UpdateNow 和 Invalidate 标志的 RedrawWindow。

以上都没有任何明显的区别,只有当我物理单击面板或移动窗口时,包含的应用程序才会运行并重新绘制自身。

有没有其他人制作过类似的东西并且有解决重绘/重绘问题的方法?我搜索了整个 Google/Bing/Duck Duck Go 领域,试图寻找答案,但无济于事。

希望你们当中有人能给出答案。

以下代码代表了大部分功能,它启动一个进程并捕获进程主窗口的句柄,并将其父级设置为沼泽标准 WinForms 窗口上的面板控件。我可能应该指出,沼泽标准 WinForms 窗口本身是“托管”在使用 (EasyTabs) 库的应用程序中。我相信这不会造成任何问题。

例子:

// Try to acquire a the process.
ProcessStartInfo startInfo = new ProcessStartInfo(path);

try
{
    startInfo.UseShellExecute = false;
    startInfo.CreateNoWindow = true;
    startInfo.WindowStyle = ProcessWindowStyle.Normal;

    Process.StartInfo = startInfo;
    Process.EnableRaisingEvents = true;
    Process.Exited += TabManagerContainerForm_ProcessExited;

    Process.Start();

    if (Process != null)
    {
        // Wait until the process has created a main window or exited.
        while (Process.MainWindowHandle == IntPtr.Zero && !Process.HasExited)
        {
            Thread.Sleep(100);
            Process.Refresh();
        }

        if (!Process.HasExited) // We have acquired a MainWindowHandle
        {
            // Capture the Process's main window and show it inside the applicationPanel panel control.
            SetParent(Process.MainWindowHandle, applicationPanel.Handle);

            // Change the captured Process's window to one without the standard chrome. Itʼs provided by our tabbed application.
            SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)WindowStyles.WS_VISIBLE); 
        }
        else // Process has exited.
        {
            if (Process.MainWindowHandle == IntPtr.Zero) Log.Information("{0} failed to execute.", Process.ProcessName);

            throw new FailedProcessException(string.Format("{0} failed to execute.", path));
        }
    }
    else
    {
        throw new Exception(string.Format("Invalid path: {0}", path));
    }
}
catch (Exception ex) when (!(ex is FailedProcessException)) // Catch everything but FailedProcessExceptions. FPEs are simply passed up the chain.
{
    Log.Error(ex.Message);
    throw;
}

【问题讨论】:

  • 如果您给出一个代码示例来说明您所做的事情,将更容易理解您究竟在哪里卡住并需要帮助。没有具体示例的长问题不会引起太多关注(也许也缩短了答案)。
  • 我会看看我能做什么。代码方面,它分布在多个文件中,并且不容易拆卸。我看看能不能做一个更简单的 POC 例子。
  • 仅供参考:我尝试了另一个使用标准 WinForms 选项卡控件和选项卡页的示例,并得到了相同的结果。因此,试图捕获进程的主窗口并将其嵌入选项卡似乎是徒劳的。可能会有答案,但目前我还没有找到答案。
  • 我更新了我的答案。它可能会解决您的标签问题

标签: c# winforms process


【解决方案1】:

总结:

将应用程序附加到当前不可见选项卡中的Panel 时,似乎会出现此问题。

解决方案:

在对SetWindowLong的调用中添加WS_CHILD标志:

SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_CHILD));

详情:

我尝试复制问题中的示例(不使用EasyTabs)。我使用了一个简单的Form 和一个Panel。通过按下按钮,我调用了一个简单的 WPF 应用程序并将其附加到 Panel。它工作正常,它立即呈现。我发现的唯一问题是 WPF 窗口的位置是随机的。我通过像这样调用SetWindowPos(使用pinvoke)来修复它:

SetWindowPos(Process.MainWindowHandle, IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.IgnoreResize);

然后,我尝试使用带有两个选项卡的TabControl,每个选项卡都包含一个Panel 和两个按钮,每个按钮在按下时都会向Panels 之一启动不同的WPF 应用程序。我发现当标签(TabPage)不是可见选项卡时,会出现问题 - 在单击Panel之前,启动的应用程序不可见。我通过在对SetWindowLong 的调用中添加WS_CHILD 标志解决了这个问题。我不知道为什么,但它有效...

我的代码:

// Capture the Process's main window and show it inside the applicationPanel panel control.
SetParent(Process.MainWindowHandle, applicationPanel.Handle);

// Change the captured Process's window to one without the standard chrome. Itʼs provided by our tabbed application.
SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_CHILD));

// Change the Process's window position to the top-left corner of the panel
SetWindowPos(Process.MainWindowHandle, IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.IgnoreResize);

【讨论】:

  • 谢谢以利亚胡。感谢您对此的帮助。我尝试了你的建议,它确实有效。但是现在存在一个问题,即以相同方式启动的 WinForms 应用程序不再显示。 WS_CHILD 标志修复了 WPF 应用程序的显示,但它会导致 WinForms 应用程序的显示中断。在我的例子中,我使用记事本作为我的示例 WinForms 应用程序。前进一步,后退一步。
猜你喜欢
  • 2019-01-09
  • 1970-01-01
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多