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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32275445/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:22:45  来源:igfitidea点击:

In WPF, how to debug triggers?

c#.netwpfdatatrigger

提问by Contango

In WPF, what are some good approaches to debug a trigger such as this?

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

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

Ideally:

理想情况下:

  • If the trigger has been hit, I would like a message to be written to the Debugwindow within Visual Studio;
  • If the trigger is hit, I want Visual Studio to hit a breakpoint in my C# code.
  • 如果触发器已被触发,我希望将一条消息写入DebugVisual Studio 中的窗口;
  • 如果触发了触发器,我希望 Visual Studio 在我的 C# 代码中命中一个断点。

回答by Contango

There is an excellent article on WPF Mentor by entitled How to debug triggers using Trigger-Tracing(cached version here).

有一篇关于 WPF Mentor 的优秀文章,标题为How to debug triggers using Trigger-Tracing(cached version here)。

I've used it innumerable times to debug into triggers, it's an amazing technique for anybody that uses WPF at a professional level.

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

Unfortunately, the link to the source code is partially broken, so I am mirroring this on SO in case the original article disappears.

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

Update: the original page diddisappear - lucky I mirrored it!

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

Debugging triggers is a painful process: they work behind the scenes, there's nowhere to put a breakpoint and no call-stack to help you. The usual approach taken is trial and error based and it nearly always takes longer than it should to work out what's going wrong.

This post describes a new technique for debugging triggers allowing you to log all trigger actions along with the elements being acted upon:

enter image description here

It's good because it:

  • helps you fix all manner of problems :)
  • works on all types of trigger: Trigger, DataTrigger, MultiTrigger etc.
  • allows you to add breakpoints when any trigger is entered and/or exited
  • is easy to set up: just drop one source file (TriggerTracing.cs) into your app and set these attached properties to the trigger to be traced:

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

    and also add the mynamespace with xmlns:my="clr-namespace:DebugTriggers".

It works by:

  • using attached properties to add dummy animation storyboards to the trigger
  • activating WPF animation tracing and filtering the results to only the entries with the dummy storyboards

调试触发器是一个痛苦的过程:它们在幕后工作,无处放置断点,也没有调用堆栈来帮助您。通常采用的方法是基于反复试验,并且几乎总是需要比应该更长的时间来找出问题所在。

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

在此处输入图片说明

这很好,因为它:

  • 帮助您解决各种问题:)
  • 适用于所有类型的触发器:触发器、数据触发器、多触发器等。
  • 允许您在进入和/或退出任何触发器时添加断点
  • 很容易设置:只需将一个源文件 (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 动画跟踪并将结果过滤为仅包含虚拟故事板的条目

Code:

代码:

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
}