将顶层窗口叠加在另一个窗口之上

时间:2013-01-03 12:46:16

标签: c# .net winforms winapi

在我目前正在处理的软件产品中,我们有几个3D视图控件。似乎需要在这些3D视图之上提供叠加信息。我不会进入太多的背景细节,因为它不是重点,但这是我们面临的限制:

  • 我们必须使用两个不同的3D视图控件
  • 我们没有他们的源代码
  • 它们嵌入在Windows窗体控件中,围绕这些控件的所有自己的GUI都在Windows窗体中
  • 我们使用.NET Framework 3.5SP1和Windows 7

我们希望能够在这些3D视图之上显示各种叠加信息和控件,因为我们通常通过在大屏幕上显示全屏3D视图来演示我们的产品,而不是显示我们的GUI,它们具有必要的信息和控件。

在我们只使用一种类型的3D视图的日子里,我通过涉及反射的各种黑客来管理我自己的用DirectX编写的覆盖窗口系统(基于WorldWind .NET覆盖小部件,3D视图确实基于在当时的WorldWind上)。 3D View产品的下一个版本对渲染核心代码进行了大量更改,当然也使这些黑客不兼容(是的,我知道了,我知道:-))。此外,我们现在正在使用,因为其他产品的不同需求,另一种类型的3D视图,闭源也是如此。

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

因此,我有以下想法:由于我们的3D视图嵌入在winforms控件中,为什么我们不在简单的winforms中编码叠加控件,并将其叠加在3D视图之上?这个解决方案的优势是巨大的:

  • 这将与两个3D视图兼容,使我们能够在需要时重复使用引擎上的叠加层
  • 我们将能够重用我们已经为GUI的其余部分开发的自定义控件或表单。实际上,这是一个非常大的项目,我们开始拥有相当多的控制库。

唯一的轻微(!)问题是我们希望能够管理叠加半透明度,就像我在DirectX中使用以前的系统一样。我们无法承受完全不透明的叠加层,因为它会使视图过于混乱。想象一下像一个几乎看不见的叠加层,当鼠标悬停在它上面时变得更加不透明。

Windows提供了在其他窗口或控件中使用子窗口的可能性(Win32 API在窗口和控件之间并没有真正区别,这几乎是我理解的MFC / WinForms抽象),但是它不是顶级窗口,我们无法调整这些的透明度,所以这不是我们可以使用的东西。我看到here,但这在Windows 8上是可行的,但是很快就无法切换到Windows 8,因为我们的软件部署在很多机器上,运行7。

所以我开始了一个激烈的谷歌搜索会议,我怎么能解决这个问题。看来我必须“奴役”#34;顶级窗口到我的3D视图控件。我已经在winforms中直接尝试了类似的东西,通过控件拥有一个表单(不是父级,有明显的区别,在先前链接的MS页面中读取它),并且"跟随"它在屏幕上的动作。正如所料,它有点奏效,但问题很难克服。最重要的是裁剪问题。如果所有者控件的父窗体更改其大小,则叠加窗体仍会完整显示。在我的示例中,我有一个带有菜单的简单表单,以及一个包含日历的黑色面板(用于显示子控件和拥有控件之间的剪辑差异)。我被奴役了#34;包含黑色面板属性网格的无边框表单。它成功地跟随表单移动,但如果我收缩主表单,我得到这个:

Clipping issue screenshot

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

根据我的理解,我必须使用win32 API调用和钩子自己处理至少一些剪辑。我已经开始记录自己,但这是相当复杂的事情。 Win32 API是一个真正的混乱,我自己是一个以前的Unix开发人员通过伟大的.NET框架介绍到Windows编程,我没有任何真正的Win32经验,因此不知道在哪里开始吧,以及如何让自己成为丛林中的一条道路......

因此,如果一个winapi大师路过,或者如果有人有其他想法在上述限制条件下实现我的目标,我会很高兴看到它: - )

提前致谢,并且对于成为这样一个stackoverflow" leecher"而道歉。订阅只是问一个问题,但我的工作站上没有直接的互联网访问权限(是的,真的,我必须去特定的计算机),所以参与这个伟大的社区不是那样的对我来说很容易。

最后,这是我的示例代码(如果你问,可以使用设计师代码):

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    Point _myLoc;

    private void formToolStripMenuItem_Click(object sender, EventArgs e)
    {
        var ctrl = new PropertyGrid();
        var obj = this.panel1;
        ctrl.SelectedObject = obj;
        var form = new Form();
        ctrl.Dock = DockStyle.Fill;
        form.Controls.Add(ctrl);
        form.Opacity = 0.7;
        var rect = obj.RectangleToScreen(obj.DisplayRectangle);
        form.StartPosition = FormStartPosition.Manual;
        form.Location = new Point(rect.Left + 10, rect.Top + 10);

        var parentForm = this;
        _myLoc = parentForm.Location;
        form.FormBorderStyle = FormBorderStyle.None;

        parentForm.LocationChanged += (s, ee) => {
            int deltaX = parentForm.Location.X - _myLoc.X;
            int deltaY = parentForm.Location.Y - _myLoc.Y;
            var loc = form.Location;
            form.Location = new Point(loc.X + deltaX, loc.Y + deltaY);
            _myLoc = parentForm.Location;
        };
        form.Show(this.panel1);
    }
}

1 个答案:

答案 0 :(得分:1)

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

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);