文章目录
- 一、场景
- 1、WPF控件
- 2、集成ActiViz或者VTK
- 二、问题
- 1、需求
- 2、空域问题
- 三、解决方案
- 1、用WindowsFormsHost包裹住ElementHost,然后将WPF的控件放在ElementHost职中:
- 2、用Window或者Popup去悬浮
- 3、使用第三方库Microsoft.DwayneNeed(这也是网上出现较多的答案)
- 四、最新解决方案
- 1、步骤
- 2、需要用到的库
- 3、关键代码如下:
- 4、运行结果预览:
- 5、源码链接
- 五、总结
一、场景
1、WPF控件
众所周知,由于WPF的底层绘制原理不同,WPF的大多数控件都不具备句柄,具备句柄的也就只有那么几种:Window、Popup、ContextMenu等。
2、集成ActiViz或者VTK
在集成VTK时: 一般选择WindowsFormsHost作为载体,将WinForm具备句柄的控件:如Panel,嵌入WPF布局中。然后将Panel的句柄传给C++。
在集成ActiViz时: 需要以RenderWindowControl来承接三维渲染内容,同样一般选择WindowsFormsHost作为载体,将RenderWindowControl放入其中,然后渲染三维内容。
二、问题
1、需求
如果需求是在三维布局界面,悬浮一些控件,且控件的背景色可以设置成透明。
2、空域问题
你会发现悬浮的WPF控件,始终在WindowsFormsHost之下,也就是说WindowsFormsHost所承载的内容始终置顶。这就是WPF由来已久的空域问题。
三、解决方案
1、用WindowsFormsHost包裹住ElementHost,然后将WPF的控件放在ElementHost职中:
<wf:WindowsFormsHost>
<wf:ElementHost>
<Grid>
</Grid>
</wf:ElementHost>
</wf:WindowsFormsHost>
如此,WPF的控件可置顶。但是会产生一下问题:
①WPF的控件绑定需要重新指定数据源
②较多的WindowsFormsHost会增加界面刷新的负担
③较多的WindowsFormsHost处理不慎的话,会出现严重的内存泄漏问题
④WindowsFormsHost所包裹的WPF控件无法透明背景,影响界面美观
2、用Window或者Popup去悬浮
优点:可悬浮于WindowsFormsHost之上、可透明背景
缺点:
①会产生其他显示问题,处理复杂:Popup永远置顶,在锁屏或者弹窗时,Popup始终在最上方。当然用Windows的API重写Popup,可取消置顶,但任然在不同环境可能有取消置顶失败的情况出现。
②Popup和Window作为单独的窗口,已经不再和主页面为一个整体,在页面切换和页面移动时,需要手动更新Popup,Window的显隐和位置,处理较为麻烦。
③Popup的使用在窗口关闭时,存在内存泄漏问题。
3、使用第三方库Microsoft.DwayneNeed(这也是网上出现较多的答案)
<xmlns:interop=clr-namespace:Microsoft.DwayneNeed.Interop;assembly=Microsoft.DwayneNeed></xmlns>
<airspace:AirspaceDecorator AirspaceMode="Redirect" IsInputRedirectionEnabled="True" IsOutputRedirectionEnabled="True">
<WindowsFormsHost Name="FormsHost">
<winform:WebBrowser>
</WindowsFormsHost></airspace:AirspaceDecorator>
以上方式,的确可以实现控件悬浮置顶,且控件背景透明。但经过本人实测,会出现以下不可控问题:
①在切换界面布局,或者刷新界面时会产生不可控的BUG,难以解决
②刷新界面时,存在严重的性能问题
③占用资源过多,三维界面的鼠标交互异常卡顿
四、最新解决方案
基于最新的ActiViz 9.3的版本,可以自己实现一个RenderWindowControl来解决空域问题。
1、步骤
①将D3D11Image控件封装到控件,通过DirectX 纹理映射来绘制图像
②将D3D11Image控件DirectN获取到系统能支持的控件句柄
③将该句柄设置上下文方式,设置到vtkWin32OpenGLDXRenderWindow中:
RenderWindow.SetD3DDeviceContext§;
④重写鼠标交互
2、需要用到的库
DirectNCore
Kitware.mummy.Runtime
Kitware.VTK
Microsoft.Wpf.Interop.DirectX
3、关键代码如下:
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using DirectN;
using Kitware.VTK;
using Microsoft.Wpf.Interop.DirectX;
namespace WpfD3D11Interop_Net6._0
{
internal class MyRenderWindowControl : Image
{
public vtkWin32OpenGLDXRenderWindow RenderWindow;
public vtkRenderWindowInteractor Interactor;
private bool _readyForRendering;
private IComObject<ID3D11Device> _device;
private IComObject<ID3D11Texture2D>? _d3d11Texture;
private D3D11Image _d3d11ImageSource;
public MyRenderWindowControl()
{
_d3d11ImageSource = new D3D11Image();
base.Source = (ImageSource)(object)_d3d11ImageSource;
_d3d11ImageSource.OnRender = new Action<nint, bool>(Render);
D3D11_CREATE_DEVICE_FLAG val = (D3D11_CREATE_DEVICE_FLAG)32;
RenderWindow = vtkWin32OpenGLDXRenderWindow.New();
RenderWindow.ShowWindowOff();
RenderWindow.SetColorTextureFormat(87u);
Extensions.WithComPointer<ID3D11DeviceContext>(ID3D11DeviceExtensions.GetImmediateContext(_device), (Action<IntPtr>)delegate (nint p)
{
RenderWindow.SetD3DDeviceContext(p);
});
RenderWindow.Initialize();
RenderWindow.Render();
RenderWindow.RegisterSharedTexture();
Interactor = vtkRenderWindowInteractor.New();
Interactor.EnableRenderOff();
RenderWindow.SetInteractor(Interactor);
Interactor.RenderEvt += Interactor_RenderEvt;
vtkInteractorStyleTrackballCamera interactorStyle = vtkInteractorStyleTrackballCamera.New();
Interactor.SetInteractorStyle(interactorStyle);
base.MouseMove += RenderWindowControl_MouseMove;
base.MouseDown += RenderWindowControl_MouseDown;
base.MouseUp += RenderWindowControl_MouseUp;
base.MouseWheel += RenderWindowControl_MouseWheel;
base.KeyDown += RenderWindowControl_KeyDown;
base.KeyUp += RenderWindowControl_KeyUp;
base.Focusable = true;
base.IsHitTestVisible = true;
base.Loaded += RenderWindowControl_Loaded;
base.SizeChanged += RenderWindowControl_SizeChanged;
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void RenderWindowControl_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (!base.IsFocused)
{
Focus();
}
if (e.Delta < 0)
{
Interactor.MouseWheelBackwardEvent();
}
else if (e.Delta > 0)
{
Interactor.MouseWheelForwardEvent();
}
}
private void RenderWindowControl_MouseDown(object sender, MouseButtonEventArgs e)
{
Point position = e.GetPosition(this);
Interactor.SetEventPosition((int)position.X, (int)position.Y);
CaptureMouse();
if (!base.IsFocused)
{
Focus();
}
if (e.ChangedButton == MouseButton.Left)
{
Interactor.LeftButtonPressEvent();
}
if (e.ChangedButton == MouseButton.Right)
{
Interactor.RightButtonPressEvent();
}
if (e.ChangedButton == MouseButton.Middle)
{
Interactor.MiddleButtonPressEvent();
}
}
private void RenderWindowControl_MouseUp(object sender, MouseButtonEventArgs e)
{
Point position = e.GetPosition(this);
Interactor.SetEventPosition((int)position.X, (int)position.Y);
ReleaseMouseCapture();
if (e.ChangedButton == MouseButton.Left)
{
Interactor.LeftButtonReleaseEvent();
}
if (e.ChangedButton == MouseButton.Right)
{
Interactor.RightButtonReleaseEvent();
}
if (e.ChangedButton == MouseButton.Middle)
{
Interactor.MiddleButtonReleaseEvent();
}
}
private void RenderWindowControl_MouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(this);
Interactor.SetEventPosition((int)position.X, (int)position.Y);
Interactor.MouseMoveEvent();
}
private void RenderWindowControl_KeyUp(object sender, KeyEventArgs e)
{
SetInteractorKeyEventInformation(e);
Interactor.KeyReleaseEvent();
e.Handled = true;
}
private void RenderWindowControl_KeyDown(object sender, KeyEventArgs e)
{
SetInteractorKeyEventInformation(e);
Interactor.KeyPressEvent();
string text = e.Key.ToString();
if (text.Length == 1 && text.All(char.IsLetter))
{
Interactor.CharEvent();
}
e.Handled = true;
}
protected void SetInteractorKeyEventInformation(KeyEventArgs e)
{
bool flag = (Keyboard.GetKeyStates(Key.Capital) & KeyStates.Toggled) == KeyStates.Toggled;
int ctrl = (((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) ? 1 : 0);
int num = (((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) ? 1 : 0);
int altKey = (((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) ? 1 : 0);
int repeatcount = (e.IsRepeat ? 1 : 0);
string text = e.Key.ToString();
sbyte keycode = 0;
if (text.Length == 1)
{
if (text.All(char.IsLetter) && num == 0 && !flag)
{
text = text.ToLower();
}
keycode = (sbyte)text[0];
}
Interactor.SetKeyEventInformation(ctrl, num, keycode, repeatcount, text);
Interactor.SetAltKey(altKey);
}
private void Interactor_RenderEvt(vtkObject sender, vtkObjectEventArgs e)
{
_readyForRendering = true;
}
private void Render(nint surface, bool isNewSurface)
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
if (isNewSurface)
{
nint num = default(nint);
ComObject.From<IDXGIResource>(ComObject.QueryObjectInterface<ID3D11Texture2D>((object)ComObject.From<ID3D11Texture2D>((IntPtr)surface, true), true), true).Object.GetSharedHandle(out num);
object obj = default(object);
_device.Object.OpenSharedResource((IntPtr)num, typeof(ID3D11Resource).GUID, out obj);
nint num2 = ComObject.QueryObjectInterface(obj, typeof(ID3D11Texture2D).GUID, true);
_d3d11Texture = (IComObject<ID3D11Texture2D>?)(object)ComObject.From<ID3D11Texture2D>((IntPtr)num2, true);
_readyForRendering = true;
}
if (_readyForRendering)
{
RenderWindow.Lock();
RenderWindow.Render();
RenderWindow.Unlock();
Extensions.WithComPointer<ID3D11Texture2D>(_d3d11Texture, (Action<IntPtr>)delegate (nint p)
{
RenderWindow.BlitToTexture(p, IntPtr.Zero);
});
}
}
private void CompositionTarget_Rendering(object? sender, EventArgs e)
{
_d3d11ImageSource.RequestRender();
_readyForRendering = false;
}
private void RenderWindowControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
_readyForRendering = true;
RenderWindow.SetSize((int)base.ActualWidth, (int)base.ActualHeight);
_d3d11ImageSource.SetPixelSize((int)base.ActualWidth, (int)base.ActualHeight);
}
private void RenderWindowControl_Loaded(object sender, RoutedEventArgs e)
{
if (PresentationSource.FromVisual(this) is HwndSource hwndSource)
{
_d3d11ImageSource.WindowOwner = hwndSource.Handle;
}
RenderWindow.SetSize((int)base.DesiredSize.Width, (int)base.DesiredSize.Height);
_d3d11ImageSource.SetPixelSize((int)base.DesiredSize.Width, (int)base.DesiredSize.Height);
}
}
}
4、运行结果预览:
5、源码链接
踩坑创作不易,白嫖党勿扰:
https://download.csdn.net/download/weixin_48083386/89480594
五、总结
困扰了C#集成VTK很久的空域问题,终于在ActiViz9.3推出了vtkWin32OpenGLDXRenderWindow类之后,得到解决。其解决原理与QT集成VTK的原理一致,都用到了DirectX绘制,并重写交互。本人亲测,目前无任何负面影响。