【发布时间】:2013-01-03 12:46:15
【问题描述】:
在我目前正在开发的软件产品中,我们有几个 3D 视图控件。似乎需要在这些 3D 视图之上有叠加信息。我不会过多介绍背景细节,因为这不是重点,但这里是我们面临的限制:
- 我们必须使用两个不同的 3D 视图控件
- 我们没有它们的源代码
- 它们嵌入在 Windows 窗体控件中,我们围绕这些控件的所有 GUI 都在 Windows 窗体中
- 我们使用 .NET Framework 3.5SP1 和 Windows 7
我们希望能够在这些 3D 视图之上显示各种叠加信息和控件,因为我们通常通过在大屏幕上显示全屏 3D 视图来演示我们的产品,而不是显示具有必要信息和控制。
在我们只使用一种 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 抽象),但因为它是不是顶级窗口,我们无法调整这些的透明度,所以这不是我们可以使用的。我看到here,虽然这在 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);
}
}
【问题讨论】:
-
您已经订阅了 LocationChanged,只需订阅 ClientSizeChanged 即可知道何时调整叠加层的大小。摆脱最小化/恢复动画需要调用 DwmSetWindowAttribute()。
-
是的,但是如果父表单大小发生变化并不意味着我要更改叠加层的大小:) 无论如何感谢 pinvoke 提示,我会调查一下!