将多个外部窗体移动到多个Winform Panel中时上方窗体位置异常

问题描述

用matlab生成了多个图,每个图一个窗口,想要将这些图嵌入到我的程序中;
我的程序是wpf的程序,使用了windowsFormsHost内部嵌套了一个Panel来装图像窗口。
采用了边生成图像,边找图像窗口,边嵌套进Panel的方法,假定一共有四个图像窗口,存在如下异常情况:

  1. 刚找到第一个图像窗口并移动到Panel中的时候位置是正确的,但是生成第二个图像的时候,第一个窗口的位置总是发生了向下的位移;
  2. 有时会出现生成下一个窗口时,上一个窗口发生向下位移的情况(从第三个窗口生成计);
  3. 有时会出现生成下一个窗口时,第一个窗口继续发生向下位移的情况(从第三个窗口生成计);
  4. 有时会出现在生成第四个窗口时,第三个窗口消失了的情况。

放张图吧:

红色的部分是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 X实在是有点学习成本了,
毕竟我只是想画个三维坐标系,搞这些3D的东西,我还得考虑缩放,移动,旋转,刷新的时候是不是会卡顿,不单单只是显示出来的问题,
matlab画图可真是太漂亮了。
这个方案还有个很大的弊端,就是wpf中使用winform控件,必然会导致winform控件置于顶层,这其实是个很大的麻烦。

嵌套窗口的这部分代码来自网络

原文地址:https://www.cnblogs.com/yutou2016/p/14792848.html