有一篇關於WPF Mentor的標題爲How to debug triggers using Trigger-Tracing(緩存版本here)的優秀文章。
我已經無數次地使用它來調試觸發器,對於任何在專業級別使用WPF的人來說,這是一項了不起的技術。
不幸的是,到源代碼的鏈接被部分破壞了,所以我在SO上鏡像以防原始文章消失。
更新:原始頁確實消失 - 幸運我反映了它!
調試觸發是一個痛苦的過程:他們在幕後工作, 有無處可放一個斷點,並沒有調用堆棧來幫助你。 通常採取的方法是基於試驗和錯誤的,並且它幾乎總是需要花費比應該花費更長的時間來解決發生了什麼問題。
這篇文章介紹了正在採取行動 用於調試觸發器允許 您記錄所有觸發動作與元素一起的新技術:
這是好事,因爲它:
- 幫助你解決各種問題:)
- 適用於所有類型的觸發器:觸發器,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
}
太感謝你了,輝煌!!!!僅僅指出顯而易見的情況,以防萬一任何人都不清楚,何時打印「Enter」表示觸發條件解析爲True,「Exit」表示觸發條件解析爲False。 – Midas
@Midas很高興幫助,並感謝澄清! – Contango
我刪除了問題和答案頂部的註釋 - 因爲自己的答案絕對沒問題,所以不需要它(您不需要任何註釋/免責聲明)。 – slugster