2015-08-28 37 views
10

在WPF中,有什么好的方法来调试这样的触发器?在WPF中,如何调试触发器?

<Trigger Property="IsMouseOver" Value="True"> 
    <Setter Property="FontWeight" Value="Bold"/> 
</Trigger> 

理想:

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

回答

24

有一篇关于WPF Mentor的标题为How to debug triggers using Trigger-Tracing(缓存版本here)的优秀文章。

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

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

更新:原始页确实消失 - 幸运我反映了它!

调试触发是一个痛苦的过程:他们在幕后工作, 有无处可放一个断点,并没有调用堆栈来帮助你。 通常采取的方法是基于试验和错误的,并且它几乎总是需要花费比应该花费更长的时间来解决发生了什么问题。

这篇文章介绍了正在采取行动 用于调试触发器允许 您记录所有触发动作与元素一起的新技术:

enter image description here

这是好事,因为它:

  • 帮助你解决各种问题:)
  • 适用于所有类型的触发器:触发器,DataTrigger,MultiTrigger
  • 允许您在输入和/或退出任何触发器时添加断点
  • 易于设置:只需删除一个源文件(TriggerTracing。CS)为您的应用程序和设置这些附加属性的触发是 追踪:

    <Trigger my:TriggerTracing.TriggerName="BoldWhenMouseIsOver" 
        my:TriggerTracing.TraceEnabled="True" 
        Property="IsMouseOver" Value="True"> 
        <Setter Property="FontWeight" Value="Bold"/> 
    </Trigger> 
    

它的工作原理是:利用附加属性,以虚拟动画故事板添加到触发

  • 激活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: 
// <Trigger my:TriggerTracing.TriggerName="BoldWhenMouseIsOver" 
//   my:TriggerTracing.TraceEnabled="True" 
//   Property="IsMouseOver" 
//   Value="True"> 
//  <Setter Property = "FontWeight" Value="Bold"/> 
// </Trigger> 
// 
// As this works on anything that inherits from TriggerBase, it will also work on <MultiTrigger>. 

namespace DebugTriggers 
{ 
#if DEBUG 

    /// <summary> 
    /// Contains attached properties to activate Trigger Tracing on the specified Triggers. 
    /// This file alone should be dropped into your app. 
    /// </summary> 
    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 

     /// <summary> 
     /// Gets the trigger name for the specified trigger. This will be used 
     /// to identify the trigger in the debug output. 
     /// </summary> 
     /// <param name="trigger">The trigger.</param> 
     /// <returns></returns> 
     public static string GetTriggerName(TriggerBase trigger) 
     { 
      return (string)trigger.GetValue(TriggerNameProperty); 
     } 

     /// <summary> 
     /// Sets the trigger name for the specified trigger. This will be used 
     /// to identify the trigger in the debug output. 
     /// </summary> 
     /// <param name="trigger">The trigger.</param> 
     /// <returns></returns> 
     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 

     /// <summary> 
     /// Gets a value indication whether trace is enabled for the specified trigger. 
     /// </summary> 
     /// <param name="trigger">The trigger.</param> 
     /// <returns></returns> 
     public static bool GetTraceEnabled(TriggerBase trigger) 
     { 
      return (bool)trigger.GetValue(TraceEnabledProperty); 
     } 

     /// <summary> 
     /// Sets a value specifying whether trace is enabled for the specified trigger 
     /// </summary> 
     /// <param name="trigger"></param> 
     /// <param name="value"></param> 
     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 
     } 

     /// <summary> 
     /// A dummy storyboard for tracing purposes 
     /// </summary> 
     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; 
      } 
     } 

     /// <summary> 
     /// A custom tracelistener. 
     /// </summary> 
     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 
} 
+2

太感谢你了,辉煌!!!!仅仅指出显而易见的情况,以防万一任何人都不清楚,何时打印“Enter”表示触发条件解析为True,“Exit”表示触发条件解析为False。 – Midas

+0

@Midas很高兴帮助,并感谢澄清! – Contango

+0

我删除了问题和答案顶部的注释 - 因为自己的答案绝对没问题,所以不需要它(您不需要任何注释/免责声明)。 – slugster