【问题标题】:In WPF, how to debug triggers?在 WPF 中,如何调试触发器?
【发布时间】:2015-11-23 08:59:37
【问题描述】:

在 WPF 中,有哪些好的方法可以调试这样的触发器?

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

理想情况下:

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

【问题讨论】:

    标签: c# .net wpf datatrigger


    【解决方案1】:

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

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

    不幸的是,源代码的链接已部分损坏,因此我将其镜像到 SO 上,以防原始文章消失。

    更新:原来的页面确实消失了 - 幸运的是我镜像了它!

    调试触发器是一个痛苦的过程:它们在幕后工作, 没有地方可以放置断点,也没有调用堆栈来帮助你。这 通常采用的方法是基于反复试验的,而且几乎总是 花费比它应该更长的时间来解决问题所在。

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

    这很好,因为它:

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

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

      并且还将my 命名空间与xmlns:my="clr-namespace:DebugTriggers" 添加。

    它的工作原理:

    • 使用附加属性将虚拟动画故事板添加到触发器
    • 激活 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
    }
    

    【讨论】:

    • 非常感谢,太棒了!!!!只是要指出一点,以防任何人都无法立即清楚,当它打印“Enter”时,表示触发条件解析为 True,“Exit”表示触发条件解析为 False。
    • @Midas 很高兴为您提供帮助,感谢您的澄清!
    • @slugster 我同意,这是现在公认的做法。情况并非总是如此,几年前,除非我提出此免责声明,否则我常常会被否决。
    • 别忘了用xmlns:my="clr-namespace:DebugTriggers"添加“my”命名空间。
    • 要获得更有意义的信息,请将TriggerTraceStoryboardType 枚举中的EnterExit 重命名为TriggerCondition_TrueTriggerCondition_False 之类的名称。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多