【问题标题】:Force binding in WPFWPF中的强制绑定
【发布时间】:2011-03-22 19:37:08
【问题描述】:

我正在编写测试来检查 XAML 中指定的Binding 元素的正确性。他们到目前为止工作,唯一的问题是我不知道如何正确地强制数据绑定发生。令人惊讶的是,仅仅在DataContext 中设置一些内容是不够的,在您显示控件/窗口之前不会发生绑定。请不要说我正在编写“单元”测试并且我想避免显示任何窗口。

看看下面的代码:

// This is main class in console application where I have all WPF references added
public class Program
{
    [STAThread]
    public static void Main()
    {
        var view = new Window();
        BindingOperations.SetBinding(view, Window.TitleProperty, new Binding("Length"));
        view.DataContext = new int[5];
        //view.Show(); view.Close(); // <-- this is the code I'm trying not to write
        Console.WriteLine(view.Title);
    }
}

在这里,我正在创建一个窗口并将一个数组作为DataContext 放入该窗口。我将Window.Title 绑定到Array.Length,所以我希望看到数字5 打印在控制台中。但是直到我Show 窗口(注释行)我会得到空字符串。如果我取消注释该行,那么我将在控制台输出中收到所需的5

有什么方法可以在不显示窗口的情况下进行绑定?在启动测试时查看大约 20 个窗口非常烦人。

P.S.:我知道我可以让窗口更透明等等,但我正在寻找更优雅的解决方案。

更新 上面的代码是我真正拥有的简化版本。在实际代码中,我收到一个View(一些带有绑定的UIElement)和object ViewModel。我不知道在View 上设置了哪些确切的绑定,但我仍然希望将它们全部初始化。

更新 2:回答有关我测试什么以及为什么测试的问题。我不打算测试BindingBindingBase 等类是否按预期工作,我假设它们正在工作。我正在尝试在我的所有 XAML 文件中测试我是否正确编写了绑定。因为绑定是字符串类型的东西,所以它们在编译期间不会被验证,默认情况下它们只会导致输出窗口中的错误,我偶尔会错过。因此,如果我们从上面举我的例子,如果我们在绑定中打错:{Binding Lengthhh},那么我的测试会通知你没有名称为Lengthhh 的属性可用于绑定。所以我有大约 100 个 XAML 文件,并且对于每个 XAML,我都有一个测试(3-5 行代码),在启动我的测试后,我知道肯定我的解决方案中没有绑定错误。

【问题讨论】:

  • 你试过调用view.UpdateLayout()还是view.InvalidateVisual()?
  • @Aaron,现在试过了,没用。
  • @Snowbear 您是否在标准 WPF 应用程序中尝试过这些不同的方法?
  • @Snowbear 测试视图非常困难。为什么不在 xUnit 或您正在使用的任何测试框架中创建 View 的实例,并根据将 DataContext 设置为给定的 ViewModel 来验证控件是否具有预期值?声明单元测试类似于控制台应用程序并没有任何意义……无论您使用什么测试框架,您都在测试视图。
  • @Snowbear 如果您没有测试绑定中的值是否准确,那么您的测试范围很窄,您现在正在尝试测试 .NET FW,这没有意义。

标签: wpf unit-testing data-binding


【解决方案1】:

调度程序使用 DispatcherPriority.DataBind 更新绑定 - 因此,如果您等待具有 SystemIdle 优先级的虚拟任务,则可以确定任何挂起的数据绑定都已完成。

  try
  {
    this.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));
  }
  catch
  {
    // Cannot perfom this while Dispatcher in suspended mode
  }

【讨论】:

  • 是的,这有帮助
【解决方案2】:

如果你想测试你的观点的正确性,我建议你测试你的观点:-)

为什么不在单元测试中运行 UI,并编写代码在更改数据后检查 UI 内容。

VS2010确实有GUI测试,或者你可以看看Snoop等工具的代码。


编辑以下评论:

如果您只想测试一些简单的绑定,请尝试编写一个静态代码测试,该测试使用视图模型上的反射和 XAML 上的正则表达式作为构建后事件运行。在 VM 上添加属性或使用配置文件,以便您的测试知道哪个视图接收哪个视图模型作为 DataContext。将视图模型中的属性名称和类型与视图中的绑定字符串进行比较(自动在 XAML 中搜索这些),如果字符串不匹配,则抛出异常(从而导致构建失败)。

如果您的绑定更复杂(转换器、多重绑定等),实现起来可能会更复杂。

【讨论】:

  • 重点是我可以进行全视图测试,但是每个视图都需要很长时间,而且这些测试需要一些时间来维护。我试图避免这种情况。我有每个视图包含 1 行的测试,我可以在添加 ViewModel 的视图和界面后立即添加它们,这些测试涵盖了我所做的大约 90% 的错误。这对我来说已经足够了,我不想多花 20 倍的时间去发现额外的 5-8% 的错误。感谢关于 VS2010 测试的想法,我在没有 vs2010 时实现了我的解决方案,所以我还没有看过。会的。
  • 以这种方式绑定一个相当复杂的地方,我有几个地方嵌套了绑定 - 这些地方对于“正则表达式”方法来说会很复杂。但我认为你给了我一个好主意,但我正在考虑遍历视觉树而不是正则表达式。另请参阅我在问题下方的评论。我对如何force bindings to happen 更加好奇。我已经在这里提出了足够多的好主意来改进我的测试。
【解决方案3】:

我认为你应该先设置DataContext,然后再做Binding,例如:

view.DataContext = new int[5];
BindingOperations.SetBinding(view, Window.TitleProperty, new Binding("Length"));

我不确定这是否是您的一般问题的真正解决方案,但它适用于这种情况。

【讨论】:

  • 它可以工作,但我不能在真实代码中做到这一点。在我的真实代码中(参见我的第一次更新),我收到了一个 View 已经包含所有绑定。绑定是从 XAML 创建的,XAML 被解析为在构造函数中调用的 InitializeComponents 的一部分,因此在设置绑定之前无法获取 View
【解决方案4】:

我不相信 Window 的绑定不会在不调用 ShowShowDialog 的情况下运行,因为这是它与 UI 消息循环/调度程序关联的唯一方式。

最好的办法是将其设置为尽可能不显眼,可能会使用扩展方法来清理:

public static void PokeWindowDispatcher(this Window window)
{
    window.WindowState = WindowState.Minimized;
    window.ShowInTaskbar = false;
    window.Visibility = Visibility.None;

    using (var wait = new ManualResetEvent())
    {
        Action<object, RoutedEventArgs> loaded = (sender, e) => wait.Set();
        window.Loaded += loaded;
        try
        {
            window.Show();
            wait.WaitOne();
        }
        finally
        {
            window.Loaded -= loaded;
            window.Close();
        }
    }
}

【讨论】:

  • 绑定可以在没有Show的情况下工作,您可以查看Gimno的方法。用他的代码binding 工作,虽然我他的解决方案不适合我。
  • 您可以尝试使用Dispatcher.PushFrame() 推送一个新的DispatcherFrame,但我不相信如果没有为您的Window 制作HWND,那么会发生任何事情。考虑到这一点,我认为为了对绑定进行最高保真度测试,您需要运行 Window 以完成加载,以便它们依赖的任何资源(静态/动态)也被加载。只是我的 2 美分。
  • 我想我应该澄清一下,我的意思是 Window 不可能在没有ShowShowDialog 的情况下执行它自己的绑定。通过手动创建或 frobbing,您可以获得单独的绑定来执行。
【解决方案5】:

我遇到了同样的问题,从六个字母变量给了我一个想法。这很简单。 我在 WinForms 应用程序中使用 WPF,所以我使用 ElementHost 控件在 WinForms 控件上托管 Wpf 控件。要强制 WinForms 控件初始化,您只需读取 Handle 的值(实际上是 Windows HWND),这将强制控件完全初始化自身,包括子 ElementHost 和所有 Wpf 绑定工作。 我没有尝试对纯 Wpf 控件执行相同的操作。但是您可以轻松地使用 ElementHost 来初始化您的 Wpf 控件,如下所示:

var el = new ElementHost();
var p = new TextBlock();
p.DataContext = new { Data = "1234" };
p.SetBinding(TextBlock.TextProperty, "Data");
el.Child = p;
var t = el.Handle;
Debug.Assert(p.Text == "1234");

PS:发现,如果您首先设置 DataContext 然后才强制创建句柄(就像我的示例一样),一切都会更好。但是,我认为,这对你来说已经是这样了,所以应该不是问题。

【讨论】:

  • 谢谢你的回答,我明天试试。但我认为这里有一些误解。我知道如何强制绑定发生,但目前我正在寻找一种比创建我现在正在做的单独窗口更干净的方法。您的方法对我来说似乎与创建新窗口相同。并对您的 PS 发表评论:我已经在 Gimno 的 回答中发表了评论,我无法提前设置 DataContext
  • 是的,用我的方法你必须创建一个控件,但你不必显示它。实际上,在发布此答案后,我自己为 Wpf 视图编写了一堆单元测试,并且一切正常。至于设置DataContext。在您的示例中,您在测试代码中设置了 DataContext。我的意思是,您只应在设置 DataContext 后立即请求 ElementHost 句柄,而不是显示窗口。完成此操作后,所有绑定都会按预期工作和更新。
【解决方案6】:

您是否尝试过使用 IsDataBound

http://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.isdatabound.aspx

也可以看看这个:

System.Windows.Interop.WindowInteropHelper helper = new System.Windows.Interop.WindowInteropHelper(view).EnsureHandle();

http://msdn.microsoft.com/en-us/library/system.windows.interop.windowinterophelper.ensurehandle.aspx

我的另一个问题是,您为什么要对已经经过技术测试的东西进行 UNIT 测试?顺便说一句,我不是在批评,只是想更好地理解一点。

【讨论】:

  • 嗨,您认为IsDataBound 对我有什么帮助?我知道它没有被绑定,我需要强制它。不,WindowInteropHelper 没有帮助,没有填充绑定。关于您的最后一个问题,请参阅问题本身中的更新 #2,我不尝试测试 MS 绑定类,我正在测试我在视图中的绑定是否正确。
  • 你有一个很好的观点,所以经过艰苦的研究(只是开玩笑),我没有找到明确的答案,但显然没有简单的方法来实现这一点。看看这个。 wintellect.com/cs/blogs/jlikness/archive/2010/08/05/…
  • 现在你可以改变你处理问题的方式,而不是假设你会输入正确的东西。为什么不用静态类为每个 ViewModel 编写一个元数据类,然后在绑定时绑定它,这样只要在元数据类上正确键入它就知道它是正确的。这有帮助吗?
  • 这也是一个有趣的想法,但这需要为每个 VM 添加一个类(T4 可能会有所帮助),这对我来说太多了。我现在有点停止改进这种方法,但是当我回到它时,我会记住你的想法。感谢分享。
【解决方案7】:

不确定,但也许这样的方法会起作用?

view.GetBindingExpression(Window.TitleProperty).UpdateTarget();

【讨论】:

  • 这无济于事,因为在实际代码中我不知道在 View 上设置了哪些绑定,请参阅我的更新
猜你喜欢
  • 1970-01-01
  • 2013-05-09
  • 2014-12-02
  • 2010-10-03
  • 2013-09-01
  • 2014-07-05
  • 2011-08-06
  • 2019-08-17
  • 1970-01-01
相关资源
最近更新 更多