2

在我目前正在开发的软件产品中,我们有几个 3D 视图控件。似乎需要在这些 3D 视图之上有叠加信息。我不会过多介绍背景细节,因为这不是重点,但这里是我们面临的限制:

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

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

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

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

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

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

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

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

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

剪辑问题截图

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

据我了解,我必须自己处理至少一些剪辑,使用 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);
    }
}
4

1 回答 1

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);
于 2013-01-03T13:20:13.390 回答