在WPF中,如何调试触发器?

在WPF中,调试触发器的一些好方法是什么?

   

理想的情况是:

  • 如果触发器被命中,我想在Visual Studio中将一条消息写入Debug窗口;
  • 如果触发器被命中,我希望Visual Studio在我的C#代码中命中断点。

WPF Mentor上有一篇很好的文章,名为“ 如何使用触发器跟踪调试触发器” ( 此处为缓存版本)。

我已经无数次地使用它来调试触发器,对于任何在专业级别使用WPF的人来说,这都是一项了不起的技术。

不幸的是,源代码的链接部分被破坏了,所以我在SO上镜像它以防原始文章消失。

更新:原始页面确实消失了 – 幸运的是我反映了它!

调试触发器是一个痛苦的过程:它们在幕后工作,没有地方放置断点,也没有调用堆栈来帮助你。 通常的方法是基于试验和错误,并且它几乎总是需要比解决出错的时间更长的时间。

本文描述了一种用于调试触发器的新技术,允许您记录所有触发器操作以及正在执行的元素:

在此处输入图像描述

它很好,因为它:

  • 帮助您解决各种问题:)
  • 适用于所有类型的触发器:Trigger,DataTrigger,MultiTrigger等。
  • 允许您在输入和/或退出任何触发器时添加断点
  • 很容易设置:只需将一个源文件(TriggerTracing.cs)拖放到您的应用程序中,并将这些附加属性设置为要跟踪的触发器:

        

它的工作原理是:

  • 使用附加属性将虚拟动画故事板添加到触发器
  • 激活WPF动画跟踪并将结果过滤到仅包含虚拟故事板的条目

码:

 using System.Diagnostics; using System.Windows; using System.Windows.Markup; using System.Windows.Media.Animation; // Code from http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html // No license specified - this code is trimmed out from Release build anyway so it should be ok using it this way // HOWTO: add the following attached property to any trigger and you will see when it is activated/deactivated in the output window // TriggerTracing.TriggerName="your debug name" // TriggerTracing.TraceEnabled="True" // Example: //  //  //  // // As this works on anything that inherits from TriggerBase, it will also work on . namespace DebugTriggers { #if DEBUG ///  /// Contains attached properties to activate Trigger Tracing on the specified Triggers. /// This file alone should be dropped into your app. ///  public static class TriggerTracing { static TriggerTracing() { // Initialise WPF Animation tracing and add a TriggerTraceListener PresentationTraceSources.Refresh(); PresentationTraceSources.AnimationSource.Listeners.Clear(); PresentationTraceSources.AnimationSource.Listeners.Add(new TriggerTraceListener()); PresentationTraceSources.AnimationSource.Switch.Level = SourceLevels.All; } #region TriggerName attached property ///  /// Gets the trigger name for the specified trigger. This will be used /// to identify the trigger in the debug output. ///  /// The trigger. ///  public static string GetTriggerName(TriggerBase trigger) { return (string)trigger.GetValue(TriggerNameProperty); } ///  /// Sets the trigger name for the specified trigger. This will be used /// to identify the trigger in the debug output. ///  /// The trigger. ///  public static void SetTriggerName(TriggerBase trigger, string value) { trigger.SetValue(TriggerNameProperty, value); } public static readonly DependencyProperty TriggerNameProperty = DependencyProperty.RegisterAttached( "TriggerName", typeof(string), typeof(TriggerTracing), new UIPropertyMetadata(string.Empty)); #endregion #region TraceEnabled attached property ///  /// Gets a value indication whether trace is enabled for the specified trigger. ///  /// The trigger. ///  public static bool GetTraceEnabled(TriggerBase trigger) { return (bool)trigger.GetValue(TraceEnabledProperty); } ///  /// Sets a value specifying whether trace is enabled for the specified trigger ///  ///  ///  public static void SetTraceEnabled(TriggerBase trigger, bool value) { trigger.SetValue(TraceEnabledProperty, value); } public static readonly DependencyProperty TraceEnabledProperty = DependencyProperty.RegisterAttached( "TraceEnabled", typeof(bool), typeof(TriggerTracing), new UIPropertyMetadata(false, OnTraceEnabledChanged)); private static void OnTraceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var triggerBase = d as TriggerBase; if (triggerBase == null) return; if (!(e.NewValue is bool)) return; if ((bool)e.NewValue) { // insert dummy story-boards which can later be traced using WPF animation tracing var storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Enter); triggerBase.EnterActions.Insert(0, new BeginStoryboard() { Storyboard = storyboard }); storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Exit); triggerBase.ExitActions.Insert(0, new BeginStoryboard() { Storyboard = storyboard }); } else { // remove the dummy storyboards foreach (TriggerActionCollection actionCollection in new[] { triggerBase.EnterActions, triggerBase.ExitActions }) { foreach (TriggerAction triggerAction in actionCollection) { BeginStoryboard bsb = triggerAction as BeginStoryboard; if (bsb != null && bsb.Storyboard != null && bsb.Storyboard is TriggerTraceStoryboard) { actionCollection.Remove(bsb); break; } } } } } #endregion private enum TriggerTraceStoryboardType { Enter, Exit } ///  /// A dummy storyboard for tracing purposes ///  private class TriggerTraceStoryboard : Storyboard { public TriggerTraceStoryboardType StoryboardType { get; private set; } public TriggerBase TriggerBase { get; private set; } public TriggerTraceStoryboard(TriggerBase triggerBase, TriggerTraceStoryboardType storyboardType) { TriggerBase = triggerBase; StoryboardType = storyboardType; } } ///  /// A custom tracelistener. ///  private class TriggerTraceListener : TraceListener { public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) { base.TraceEvent(eventCache, source, eventType, id, format, args); if (format.StartsWith("Storyboard has begun;")) { TriggerTraceStoryboard storyboard = args[1] as TriggerTraceStoryboard; if (storyboard != null) { // add a breakpoint here to see when your trigger has been // entered or exited // the element being acted upon object targetElement = args[5]; // the namescope of the element being acted upon INameScope namescope = (INameScope)args[7]; TriggerBase triggerBase = storyboard.TriggerBase; string triggerName = GetTriggerName(storyboard.TriggerBase); Debug.WriteLine(string.Format("Element: {0}, {1}: {2}: {3}", targetElement, triggerBase.GetType().Name, triggerName, storyboard.StoryboardType)); } } } public override void Write(string message) { } public override void WriteLine(string message) { } } } #endif }