C# 将顶级窗口覆盖在另一个窗口上

C# 将顶级窗口覆盖在另一个窗口上,c#,.net,winforms,winapi,C#,.net,Winforms,Winapi,在我目前正在开发的软件产品中,我们有几个3D视图控件。似乎需要在这些三维视图的顶部有覆盖信息。我不想谈太多的背景细节,因为这不是重点,但这里是我们面临的限制: 我们必须使用两个不同的三维视图控件 我们没有他们的源代码 它们嵌入在Windows窗体控件中,而我们自己所有这些控件周围的GUI都在Windows窗体中 我们使用.NETFramework3.5SP1和Windows7 我们希望能够在这些3D视图上显示各种覆盖信息和控件,因为我们通常通过在大屏幕上显示全屏3D视图来演示我们的产品,而不

在我目前正在开发的软件产品中,我们有几个3D视图控件。似乎需要在这些三维视图的顶部有覆盖信息。我不想谈太多的背景细节,因为这不是重点,但这里是我们面临的限制:

  • 我们必须使用两个不同的三维视图控件
  • 我们没有他们的源代码
  • 它们嵌入在Windows窗体控件中,而我们自己所有这些控件周围的GUI都在Windows窗体中
  • 我们使用.NETFramework3.5SP1和Windows7
我们希望能够在这些3D视图上显示各种覆盖信息和控件,因为我们通常通过在大屏幕上显示全屏3D视图来演示我们的产品,而不是显示具有必要信息和控件的GUI

回到过去,我们只使用一种类型的3D视图,我通过各种涉及反射的黑客手段,成功地钩住了我自己用DirectX编写的覆盖窗口系统(基于WorldWind.NET覆盖窗口小部件,3D视图当时确实基于WorldWind)。3D视图产品的下一个版本对渲染核心代码进行了巨大的更改,当然这些黑客不兼容(是的,我已经准备好了,我知道:-))。此外,由于其他产品的不同需求,我们现在正在使用另一种类型的三维视图,以及封闭源代码

我要强调的是,我们没有它们的源代码,因为我们无法访问渲染循环,因此无法连接为3D引擎制作的窗口系统,例如CEGUI(自己搜索,我还不允许发布太多超链接,抱歉)

因此,我有以下想法:既然我们的3D视图嵌入到winforms控件中,为什么我们不在普通winforms中编写覆盖控件,并将其覆盖到3D视图之上呢?此解决方案的优点是巨大的:

  • 这将与两个三维视图兼容,使我们能够在需要时跨引擎重用覆盖
  • 我们将能够重用已经为GUI的其余部分开发的自定义控件或表单。事实上,这是一个相当大的项目,我们开始有一个相当大的控制库
唯一轻微的(!)问题是,我们希望能够管理覆盖半透明,就像我在DirectX中使用以前的系统一样。我们负担不起完全不透明的覆盖层,因为它会使视图过于混乱。想象一下一个几乎看不见的覆盖物,例如当鼠标悬停在上面时,它变得更加不透明

Windows提供了在其他窗口或控件中包含子窗口的可能性(Win32 API在窗口和控件之间并没有真正的区别,据我所知,这几乎是一个MFC/WinForms抽象),但由于它不是顶级窗口,我们无法调整这些窗口的半透明性,因此我们不能使用它。我看到,这在Windows8上是可能的,但是切换到Windows8在短期内是不可能的,因为我们的软件部署在很多运行Windows7的机器上

于是,我开始了一场激烈的谷歌搜索,讨论如何解决这样一个问题。看来我必须将顶层窗口“奴役”到我的三维视图控件。我已经在winforms中直接尝试过类似的方法,让控件拥有一个表单(不是父表单,有一个明显的区别,在之前链接的MS页面中阅读),并在屏幕上“跟踪”它的移动。正如预期的那样,这是可行的,但问题很难克服。最重要的是剪辑问题。如果所有者控件的父窗体更改了其大小,则覆盖窗体仍然完整显示。在我的示例中,我有一个带有菜单的简单表单和一个包含日历的黑色面板(用于显示子控件和自有控件之间的剪辑差异)。我将包含属性网格的无边界表单“奴役”到黑色面板。它成功地跟随了表单的移动,但是如果我收缩主表单,我会得到以下结果:

请注意,日历是如何正确剪裁的(子窗口),而覆盖层是如何剪裁的(所有窗口)。在最小化/恢复主窗体时,我也会得到奇怪的效果。事实上,我的覆盖在最小化时消失,但在恢复时,它只是在主窗体的恢复动画发生时“生成”。但这不是一个问题,我想可以通过处理适当的事件(但哪些事件?)来解决

据我所知,我必须使用win32 API调用和钩子自己处理至少一些剪辑。我已经开始记录我自己,但这是相当复杂的事情。Win32 API真是一团糟,而我是一位通过伟大的.NET框架介绍Windows编程的前Unix开发人员,我对Win32没有任何实际经验,因此我真的不知道从哪里开始,也不知道如何让自己成为这片丛林中的一条道路

因此,如果有一位winapi大师路过,或者如果有人在上述限制条件下有其他想法来实现我的目标,我很乐意阅读:-)

提前感谢你,我为自己是一个只为提问而订阅的stackoverflow“leecher”而道歉,但我的工作站上没有直接的互联网接入(是的,说真的,我必须去一台特定的计算机上),所以参与这个伟大的社区对我来说并不是那么容易

最后,这里是我的示例代码(如果需要,可以使用设计器代码):


使用
区域
属性可以轻松实现剪裁。每个窗口都可以有一个关联的
区域
对象,该对象定义窗口渲染约束:

static void ManualClipping(Control clipRegionSource, Form formToClip)
{
    var rect = clipRegionSource.DisplayRectangle;
    rect = clipRegionSource.RectangleToScreen(rect);
    rect = formToClip.RectangleToClient(rect);
    rect = Rectangle.Intersect(rect, formToClip.ClientRectangle);
    if(rect == formToClip.ClientRectangle)
    {
        formToClip.Region = null;
    }
    else
    {
        formToClip.Region = new Region(rect);
    }
}
用法:

/* ... */
parentForm.SizeChanged += (s, ee) => ManualClipping(panel1, form);
form.Show(this.panel1);

您已经订阅了LocationChanged,只需订阅ClientSizeChanged,以便知道何时调整覆盖的大小。摆脱最小化/还原动画需要pinvoking DwmSetWindowAttribute()。是的,但如果父窗体大小更改,并不意味着我要更改
/* ... */
parentForm.SizeChanged += (s, ee) => ManualClipping(panel1, form);
form.Show(this.panel1);