【问题标题】:Control's parent handle points to WindowsFormsParkingWindow using p/invoke控件的父句柄使用 p/invoke 指向 WindowsFormsParkingWindow
【发布时间】:2015-12-14 15:37:13
【问题描述】:

考虑对 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 序列,但我认为这是解决问题的一种相当 hack-ish 的方法。

下面提供了 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);
}

【问题讨论】:

  • @HansPassant:请查看我更新的问题,我添加了更多信息,希望现在更清楚。 FindParent 是正在测试的方法,而不是 WinApi。
  • 汉斯已经回答了你的问题。我不明白你为什么不接受这个答案。
  • @DavidHeffernan: You'll have to write your unit test to accommodate this behavior 如何 我可以让它工作,而不是显示/隐藏。如果没有更好的方法,直接说出来 - 是的,不幸的是,显示/隐藏是这里最好的方法。然后在cmets中汉斯问是否This is supposed to test WM_GETTEXT?,表示他一开始没看懂问题。
  • 汉斯回答得很好,你没听懂。我很清楚你需要做什么。我理解汉斯为什么撤回他的帮助。
  • 我的意思是你应该清楚你需要确保父级创建了句柄。

标签: c# winforms winapi pinvoke mstest


【解决方案1】:

当您访问Handle 属性时,需要创建窗口。子窗口需要有一个父窗口,如果还没有创建父窗口,则以停车窗口为父窗口创建子窗口。只有当父窗口被创建时,子窗口才会重新成为父窗口。

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

当你访问button.Handle时,按钮的窗口被创建了,但是由于窗体的窗口还没有创建,所以停放窗口是父窗口。处理此问题的最简单方法是确保在按钮窗口之前创建表单窗口。确保在调用按钮手柄上的GetParent 之前引用form.Handle,例如在您的测试中,您可以颠倒分配顺序:

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

显然,您希望对此代码进行注释,以便将来的读者知道分配顺序很关键。

不过,我确实想知道为什么您觉得有必要进行这样的测试。我无法想象这种测试会揭示您代码中的错误。

【讨论】:

  • 谢谢大卫,这非常有用。实际代码相当漂亮,只是我不得不制作一个简化的测试用例示例,并且变得更难理解。在实际代码中(现在也可以使用),分配的顺序并不重要。在进行任何 WinApi 工作之前,我只是遍历所有句柄并提取它们的值。关于正在测试的内容,正如我提到的,父母/孩子查找是任何人都可能出错的地方。这很简单,但仍有出错的余地。感谢你们,我学到了一些新东西。所以这是值得的。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-27
  • 1970-01-01
  • 2013-07-07
  • 2011-06-12
  • 2010-10-22
  • 1970-01-01
相关资源
最近更新 更多