简体   繁体   English

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

[英]In WPF, how to debug triggers?

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 Debug window within Visual Studio;如果触发器已被触发,我希望将一条消息写入 Visual Studio 中的Debug窗口;
  • If the trigger is hit, I want Visual Studio to hit a breakpoint in my C# code.如果触发了触发器,我希望 Visual Studio 在我的 C# 代码中命中一个断点。

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 did disappear - 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:这篇文章描述了一种调试触发器的新技术,允许您记录所有触发器操作以及正在执行的元素:

在此处输入图片说明

It's good because it:这很好,因为它:

  • helps you fix all manner of problems :)帮助您解决各种问题:)
  • works on all types of trigger: Trigger, DataTrigger, MultiTrigger etc.适用于所有类型的触发器:触发器、DataTrigger、MultiTrigger 等。
  • 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:很容易设置:只需将一个源文件 (TriggerTracing.cs) 放入您的应用程序,并将这些附加属性设置为要跟踪的触发器:

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

    and also add the my namespace with xmlns:my="clr-namespace:DebugTriggers" .并使用xmlns:my="clr-namespace:DebugTriggers"添加my命名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激活 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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM