Reputation: 70
In the software products I'm currently working on, we have several 3D View controls. Appeared the need to have overlay information on top of these 3D Views. I won't go into too much background details, because it is not the point, but here are the constraints we face :
We want to be able to display various overlay informations and controls on top of these 3D views, because we usually demo our products by showing full screen 3D views on big screens, and not show our GUIs, which have the necessary information and controls.
Back in the days we used only one type of 3D view, I managed, via various hacks involving reflection, to hook my own overlay window system written in DirectX (based on WorldWind .NET overlay widgets, the 3D view was indeed based on WorldWind at the time). The next version of the 3D View product made huge changes to the rendering core code and of course made these hacks incompatible (yeah, I had it coming, I know :-)). Moreover, we are now using, because of different needs in other products, another type of 3D View, closed source as well.
I emphasize the fact that we don't have the source code for them because we can't have access to the rendering loop and therefore cannot hook a windowing system made for 3D Engines, such as CEGUI (search for it yourself, I'm not allowed to post much hyperlinks yet, sorry).
Consequently, I had the following idea : since our 3D Views are embedded in winforms controls, why don't we code our overlay controls in plain winforms, and overlay it on top of the 3D views? The advantages of this solution are huge :
The only slight (!) problem is that we want to be able to manage overlay transluency, like I did with my former system in DirectX. We can't afford fully opaque overlays, because it would clutter the view too much. Imagine something like a barely visible overlay, becoming more opaque when the mouse is hovering over it for example.
Windows offer the possibility to have child windows inside other windows or controls (Win32 API doesn't really make a difference between windows and controls, this is pretty much a MFC/WinForms abstraction as I understood it), but since it is not top-level windows, we cannot adjust the transluency of these, so this is not something we can use. I saw here, that this is however possible on Windows 8, but switching to windows 8 is not possible anytime soon, because our software is deployed on quite a few machines, running 7.
So I started an intense googling session on how could I work around such a problem. It appears I must "enslave" top level windows to my 3D View controls. I already tried out something like that directly in winforms, having a form owned (not parented, there is a clear distinction, read about it in the previously linked MS page) by a control, and "following" its movements on screen. As expected, it kind of worked, but the issues are difficult to overcome. The most important is a clipping issue. If the parent form of the owner control changes its size, the overlay form is still shown in full. In my sample, I have a simple form with a menu, and a black panel containing a calendar (to show clipping differences between child controls and owned ones). I "enslaved" a borderless form containing a property grid to the black panel. It successfully follows the forms movements, but if I shrink the main form, I get this :
Note how the calendar is clipped correctly (child window), and the overlay is not (owned window). I also get weird effects when minimizing/restoring the main form. Indeed, my overlay disappears when minimizing, but when restoring, it just "spawns" while the restoring animation of the main form is occuring. But this is less of an issue, and I guess can be worked around by handling proper events (but which ones?).
From what I understood, I must handle at least some of the clipping myself, using win32 API calls and hooks. I already begun to document myself, but it is quite complicated stuff. The Win32 API being a real mess, and myself being a former Unix developer introduced to Windows programming via the great .NET framework, I don't have any real experience in Win32, and therefore don't really know where to begin, and how to make myself a path in this jungle...
So if a winapi guru is passing by, or if someone has some other idea to achieve my goals given the constraints above, I'll be glad to read about it :-)
Thanks in advance, and apologies for being such a stackoverflow "leecher" by subscribing only to ask a question, but I don't have no direct internet access on my workstation (yeah, for real, I have to go to a specific computer for this), so participating in this great community is not that easy for me.
Finally, here is my sample code (designer code available if you ask) :
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);
}
}
Upvotes: 2
Views: 1800
Reputation: 34437
Clipping can be easily implemented using Region
property. Each window can have an associated Region
object, which defines window rendering constraints:
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);
}
}
usage:
/* ... */
parentForm.SizeChanged += (s, ee) => ManualClipping(panel1, form);
form.Show(this.panel1);
Upvotes: 1