Control的父句柄使用p / invoke指向WindowsFormsParkingWindow

时间:2015-12-14 15:37:13

标签: c# winforms winapi pinvoke mstest

考虑WinApi功能的以下单元测试:

public class WinApiTest
{
  [TestMethod]
  public void WinApiFindFormTest_SimpleNesting()
  {
    var form = new Form();
    form.Text = @"My form";

    var button = new Button();
    button.Text = @"My button";

    form.Controls.Add(button);
    //with below line commented out, the test fails
    form.Show();

    IntPtr actualParent = WinApiTest.FindParent(button.Handle);
    IntPtr expectedParent = form.Handle;

    //below 2 lines were added for debugging purposes, they are not part of test
    //and they don't affect test results
    Debug.WriteLine("Actual: " + WinApi.GetWindowTitle(actualParent));
    Debug.WriteLine("Expected: " + WinApi.GetWindowTitle(expectedParent));

    Assert.AreEqual(actualParent, expectedParent);
  }

  //this is a method being tested
  //please assume it's located in another class
  //I'm not trying to test winapi
  public static IntPtr FindParent(IntPtr child)
  {
    while (true)
    {
      IntPtr parent = WinApi.GetParent(child);
      if (parent == IntPtr.Zero)
      {
        return child;
      }
      child = parent;
    }
  }
}

问题是要使其工作,我必须显示表单,即执行form.Show(),否则,它会失败并显示此输出:

Actual: WindowsFormsParkingWindow
Expected: My form
Exception thrown: 'Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException' in Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll

我读到了这个神秘的 WindowsFormsParkingWindow ,只有在没有指定父级的情况下它似乎才有意义。所以没有父母的所有控件都存在于此窗口下。但是,在我的情况下,button被明确指定为form控件的一部分。

问题:是否有正确的方法让此测试通过?我正在尝试测试FindParent方法。在真正的单元测试精神中,不应该突然在用户面前弹出任何东西。可以执行ShowHide序列,但我认为这是一种解决问题的方法。

下面提供了WinApi类的代码 - 它没有给问题增加太多价值,但是如果你绝对必须看到它,那么它(主要部分来自this answer on SO):

public class WinApi
{
  /// <summary>
  ///  Get window title for a given IntPtr handle.
  /// </summary>
  /// <param name="handle">Input handle.</param>
  /// <remarks>
  ///  Major portition of code for below class was used from here:
  ///  https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption
  /// </remarks>
  public static string GetWindowTitle(IntPtr handle)
  {
    if (handle == IntPtr.Zero)
    {
      throw new ArgumentNullException(nameof(handle));
    }
    int length = WinApi.SendMessageGetTextLength(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
    if (length > 0 && length < int.MaxValue)
    {
      length++; // room for EOS terminator
      StringBuilder windowTitle = new StringBuilder(length);
      WinApi.SendMessageGetText(handle, WM_GETTEXT, (IntPtr)windowTitle.Capacity, windowTitle);
      return windowTitle.ToString();
    }
    return String.Empty;
  }

  const int WM_GETTEXT = 0x000D;
  const int WM_GETTEXTLENGTH = 0x000E;

  [DllImport("User32.dll", EntryPoint = "SendMessage")]
  private static extern int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
  [DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
  private static extern IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
  [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  public static extern IntPtr GetParent(IntPtr hWnd);
}

1 个答案:

答案 0 :(得分:2)

当您访问Handle属性时,需要创建窗口。子窗口需要具有父窗口,并且如果尚未创建父窗口,则创建子窗口,其中停放窗口作为其父窗口。只有在创建父窗口时,子窗口才会重新成为父级。

IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;

当您访问button.Handle时,会创建按钮的窗口,但由于尚未创建窗体的窗口,因此停放窗口是父窗口。处理此问题的最简单方法是确保在按钮窗口之前创建窗体的窗口。在按钮的句柄上调用form.Handle之前,请务必参考GetParent,例如在测试中,您可以颠倒分配顺序:

IntPtr expectedParent = form.Handle;
IntPtr actualParent = WinApiTest.FindParent(button.Handle);

显然你想要评论这段代码,以便未来的读者知道作业的顺序是至关重要的。

我确实不知道为什么你觉得有必要做这样的测试。我无法想象这种测试揭示了代码中的错误。