Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In WPF, how to debug triggers?

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

<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;
  • If the trigger is hit, I want Visual Studio to hit a breakpoint in my C# code.
like image 602
Contango Avatar asked Aug 28 '15 16:08

Contango


People also ask

How many types of triggers are there in WPF?

Basically, there are 3 types of triggers, they are: Property Trigger. Data Trigger. Event Trigger.

What is style trigger in WPF?

The WPF styling and templating model enables you to specify triggers within your Style. Essentially, triggers are objects that enable you to apply changes when certain conditions (such as when a certain property value becomes true , or when an event occurs) are satisfied.


Video Answer


1 Answers

There is an excellent article on WPF Mentor by entitled 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.

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

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:

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 my namespace 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

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 } 
like image 145
Contango Avatar answered Sep 30 '22 11:09

Contango