问题描述
用matlab生成了多个图,每个图一个窗口,想要将这些图嵌入到我的程序中;
我的程序是wpf的程序,使用了windowsFormsHost
内部嵌套了一个Panel
来装图像窗口。
采用了边生成图像,边找图像窗口,边嵌套进Panel的方法,假定一共有四个图像窗口,存在如下异常情况:
- 刚找到第一个图像窗口并移动到Panel中的时候位置是正确的,但是生成第二个图像的时候,第一个窗口的位置总是发生了向下的位移;
- 有时会出现生成下一个窗口时,上一个窗口发生向下位移的情况(从第三个窗口生成计);
- 有时会出现生成下一个窗口时,第一个窗口继续发生向下位移的情况(从第三个窗口生成计);
- 有时会出现在生成第四个窗口时,第三个窗口消失了的情况。
放张图吧:
红色的部分是Panel所在的位置,可以看到第一个图的位置明显向下移动了。
多次测试发现,每个图会发生什么样的位移变化,是一件看上去随机的事件,
之前有出现过如果在图片窗口全都生成之后依次将窗口移入到Panel中时,不出问题的情况,然而多次测试之后,发现又位移了,
好像有点玄学。
测试代码
添加Panel:
for (int i = 1; i < 5; i++)
{
WindowsFormsHost wfh = new WindowsFormsHost();
wfh.Child = new System.Windows.Forms.Control();
wfh.Width = 350;
wfh.Height = 350;
wfh.VerticalAlignment = VerticalAlignment.Top;
wfh.HorizontalAlignment = HorizontalAlignment.Left;
wfh.Margin = new Thickness(0, (i - 1) * 400, 0, 0);
wfh.Background = Brushes.Blue;
System.Windows.Forms.Panel p = new System.Windows.Forms.Panel();
p.Name = string.Format("{0}", i);
p.BackColor = System.Drawing.Color.Red;
p.Width = (int)wfh.Width;
p.Height = (int)wfh.Height;
p.Location = new System.Drawing.Point(0, 0);
wfh.Child.Controls.Add(p);
testGrid.Children.Add(wfh);
ps.Add(p);
}
Thread t = new Thread(WorkThread);
t.IsBackground = true;
t.Start();
生成并移动窗口:
这样写是会出现上面的问题的
private void WorkThread()
{
for (int i = 1; i < 5; i++)
{
// d1是matlab导出的dll,用来生成一张图
d1.Class1 class1 = new Class1();
class1.d1(string.Format("{0}", i));
IntPtr figure = IntPtr.Zero;
while (figure == IntPtr.Zero)
{
figure = FindWindow("SunAwtFrame", string.Format("{0}", i));
if (figure != IntPtr.Zero)
{
new Thread(() => {
Dispatcher.Invoke(() =>
{
SetParent(figure, ps[i - 1].Handle);
});
}).Start();
var style = GetWindowLong(figure, GWL_STYLE);
// 设置窗体风格
SetWindowLong(figure, GWL_STYLE, style & ~WS_CAPTION & ~WS_THICKFRAME);
MoveWindow(figure, 0, 0, ps[i - 1].Width, ps[i - 1].Height, true);
}
Thread.Sleep(1300);
}
}
}
解决方案
将窗口全部生成出来之后,再倒序移动到Panel中,并且移动要稍有一定的时间检测,经测试,100ms能够达到要求,
如果不留有时间检测,会出现最后一个窗口失踪的问题。
这种方案我做过多次测试,包括在我的程序界面移动的情况下,或者滚动条滚动的情况下,都有稳定的表现效果,
这是我希望的模样:
修正代码:
private void WorkThread()
{
for (int i = 1; i < 5; i++)
{
d1.Class1 class1 = new Class1();
class1.d1(string.Format("{0}", i));
}
for(int i = 4; i > 0; i --)
{
IntPtr figure = IntPtr.Zero;
while (figure == IntPtr.Zero)
{
figure = FindWindow("SunAwtFrame", string.Format("{0}", i));
if (figure != IntPtr.Zero)
{
new Thread(() => {
Dispatcher.Invoke(() =>
{
SetParent(figure, ps[i - 1].Handle);
});
}).Start();
var style = GetWindowLong(figure, GWL_STYLE);
// 设置窗体风格
SetWindowLong(figure, GWL_STYLE, style & ~WS_CAPTION & ~WS_THICKFRAME);
MoveWindow(figure, 0, 0, ps[i - 1].Width, ps[i - 1].Height, true);
}
Thread.Sleep(100);
}
}
}
windowsAPI:
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
private const int GWL_STYLE = -16;
private const int WS_CAPTION = 0x00C00000;
private const int WS_THICKFRAME = 0x00040000;
private const int WS_SYSMENU = 0X00080000;
[DllImport("user32")]
private static extern int GetWindowLong(System.IntPtr hwnd, int nIndex);
[DllImport("user32")]
private static extern int SetWindowLong(System.IntPtr hwnd, int index, int newLong);
[DllImport("user32")]
private static extern int InvalidateRect(System.IntPtr hwnd, object rect, bool bErase);
/// <summary>最大化窗口,最小化窗口,正常大小窗口
/// nCmdShow:0隐藏,3最大化,6最小化,5正常显示
/// </summary>
[DllImport("user32.dll", EntryPoint = "ShowWindow")]
public static extern int ShowWindow(IntPtr hwnd, int nCmdShow);
我做了这么多努力,要把这个功能做出来的目的,还是希望能够调用matlab画三维坐标图,
如果有其他可行的在wpf中画三维坐标点的方法,我一定立即抛弃这个方案。
看上去viewport3D,OpenGl还有Direct 3D实在是有点学习成本了,
毕竟我只是想画个三维坐标系,搞这些3D的东西,我还得考虑缩放,移动,旋转,刷新的时候是不是会卡顿,不单单只是显示出来的问题,
matlab画图可真是太漂亮了。
这个方案还有个很大的弊端,就是wpf中使用winform控件,必然会导致winform控件置于顶层,这其实是个很大的麻烦。
嵌套窗口的这部分代码来自网络