付近の質問に答えようとしているときに、「 WPFバインディングの単体テスト 」という次の質問がありました。
WPF Data Bindingの配線設定が間違っている(または正しく配線されたものが壊れた)場合に見つける最良の方法は何ですか?
単体テストのアプローチは、ジョエルの「スプリンターを取り除くために腕を引き裂く」ようなものに思えますが、これを検出するオーバーヘッドの少ない簡単な方法を探しています。
誰もがWPFを使用してデータバインディングに大きな力を注いでいるようです。そのメリットはあります。
見つけることができる最高の...
誰もが常に出力ウィンドウを監視してバインディングエラーを探すことができないため、Option#2が気に入りました。これはあなたのApp.Configにこれを追加します
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<sources>
<source name="System.Windows.Data" switchName="SourceSwitch" >
<listeners>
<add name="textListener" />
</listeners>
</source>
</sources>
<switches>
<add name="SourceSwitch" value="All" />
</switches>
<sharedListeners>
<add name="textListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="GraveOfBindErrors.txt" />
</sharedListeners>
<trace autoflush="true" indentsize="4"></trace>
</system.diagnostics>
</configuration>
適切な正規表現スキャンスクリプトと組み合わせて、関連情報を抽出します。これは、出力フォルダーのGraveOfBindErrors.txtでときどき実行できます。
System.Windows.Data Error: 35 : BindingExpression path error: 'MyProperty' property not found on 'object' ''MyWindow' (Name='')'. BindingExpression:Path=MyProperty; DataItem='MyWindow' (Name=''); target element is 'TextBox' (Name='txtValue2'); target property is 'Text' (type 'String')
.NET 3.5では、特定のデータバインディングに関するトレース情報を具体的に出力する新しい方法が導入されました。
これは、すべてのバインディングまたはデータプロバイダーに適用できる新しいSystem.Diagnostics.PresentationTraceSources.TraceLevel添付プロパティを介して行われます。次に例を示します。
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;Assembly=WindowsBase"
Title="Debug Binding Sample"
Height="300"
Width="300">
<StackPanel>
<TextBox Name="txtInput" />
<Label>
<Label.Content>
<Binding ElementName="txtInput"
Path="Text"
diag:PresentationTraceSources.TraceLevel="High" />
</Label.Content>
</Label>
</StackPanel>
</Window>
これにより、Visual Studioの出力ウィンドウに特定のバインディングのトレース情報のみが配置され、トレース構成は必要ありません。
WPFインスペクターのトリガーデバッグ機能を使用できます。 codeplexからツールをダウンロードして、実行中のアプリに添付するだけです。また、ウィンドウの下部にバインドエラーが表示されます。非常に便利なツール!
ここに示すソリューションを使用して、バインディングエラーをネイティブの例外に変換します。 http://www.jasonbock.net/jb/Default.aspx?blog=entry.0f221e047de740ee90722b248933a28d
ただし、WPFバインディングの通常のシナリオは、ユーザー入力をターゲットタイプに変換できない場合に例外をスローすることです(たとえば、整数フィールドにバインドされたTextBox。非数値文字列の入力はFormatExceptionになります。入力した値が大きすぎると、OverflowExceptionが発生します)。同様のケースは、ソースプロパティのセッターが例外をスローする場合です。
これを処理するWPFの方法は、ValidatesOnExceptions = trueおよびValidationExceptionRuleを使用して、提供された入力が正しくないことをユーザーに通知します(例外メッセージを使用)。
ただし、これらの例外は出力ウィンドウにも送信されるため、BindingListenerによって「キャッチ」され、エラーが発生します。明らかに、必要な動作ではありません。
したがって、これらの場合に例外をスローしないようにBindingListener
クラスを拡張しました。
private static readonly IList<string> m_MessagesToIgnore =
new List<String>()
{
//Windows.Data.Error 7
//Binding transfer from target to source failed because of an exception
//Normal WPF Scenario, requires ValidatesOnExceptions / ExceptionValidationRule
//To cope with these kind of errors
"ConvertBack cannot convert value",
//Windows.Data.Error 8
//Binding transfer from target to source failed because of an exception
//Normal WPF Scenario, requires ValidatesOnExceptions / ExceptionValidationRule
//To cope with these kind of errors
"Cannot save value from target back to source"
};
public override void WriteLine(string message)の変更行:
....
if (this.InformationPropertyCount == 0)
{
//Only treat message as an exception if it is not to be ignored
if (!m_MessagesToIgnore.Any(
x => this.Message.StartsWith(x, StringComparison.InvariantCultureIgnoreCase)))
{
PresentationTraceSources.DataBindingSource.Listeners.Remove(this);
throw new BindingException(this.Message,
new BindingExceptionInformation(this.Callstack,
System.DateTime.Parse(this.DateTime),
this.LogicalOperationStack, int.Parse(this.ProcessId),
int.Parse(this.ThreadId), long.Parse(this.Timestamp)));
}
else
{
//Ignore message, reset values
this.IsFirstWrite = true;
this.DetermineInformationPropertyCount();
}
}
}
トリガーを効果的にデバッグ/トレースするための便利なテクニックを紹介します。これにより、アクション対象の要素とともにすべてのトリガーアクションを記録できます。
http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html
これは非常に役に立ちましたが、このファイルを読み取るためにMicrosoftがSDKで提供しているユーティリティがあることを、これが便利だと思う人に追加したかったのです。
ここにあります: http://msdn.Microsoft.com/en-us/library/ms732023.aspx
トレースファイルを開くには
1.コマンドウィンドウを使用してWCFのインストール場所(C:\ Program Files\Microsoft SDKs\Windows\v6.0\Bin)に移動してService Trace Viewerを起動し、SvcTraceViewer.exeと入力します。 (ただし、\ v7.0\Binにあります)
注:Service Trace Viewerツールは、.svclogと.stvprojの2つのファイルタイプに関連付けることができます。コマンドラインで2つのパラメーターを使用して、ファイル拡張子を登録および登録解除できます。
/ register:ファイル拡張子 ".svclog"および ".stvproj"の関連付けをSvcTraceViewer.exeに登録します
/ unregister:ファイル拡張子 ".svclog"および ".stvproj"とSvcTraceViewer.exeの関連付けを登録解除します
1. Service Trace Viewerが起動したら、[ファイル]をクリックし、[開く]をポイントします。トレースファイルが保存されている場所に移動します。
2.開きたいトレースファイルをダブルクリックします。
注:Shiftキーを押しながら複数のトレースファイルをクリックして、それらを同時に選択して開きます。 Service Trace Viewerは、すべてのファイルのコンテンツをマージし、1つのビューを表示します。たとえば、クライアントとサービスの両方のトレースファイルを開くことができます。これは、構成でメッセージロギングとアクティビティ伝播を有効にしている場合に便利です。この方法で、クライアントとサービス間のメッセージ交換を調べることができます。複数のファイルをビューアにドラッグするか、[プロジェクト]タブを使用することもできます。詳細については、プロジェクトの管理セクションを参照してください。
3.開いているコレクションに追加のトレースファイルを追加するには、[ファイル]をクリックし、[追加]をポイントします。開いたウィンドウで、トレースファイルの場所に移動し、追加するファイルをダブルクリックします。
また、ログファイルのフィルタリングに関しては、次のリンクが非常に役立つことがわかりました。
特定のトレースレベルですべてのWPFトレースを有効にする純粋なプログラムによる方法を探している私のような人のために、これを実行するコードを以下に示します。参照用に、この記事に基づいています: WPFのトレースソース 。
App.configファイルを変更する必要はなく、レジストリを変更する必要もありません。
これは、いくつかのスタートアップの場所(アプリなど)での使用方法です。
....
#if DEBUG
WpfUtilities.SetTracing();
#endif
....
次に、ユーティリティコードを示します(デフォルトでは、すべての警告をデフォルトトレースリスナーに送信します)。
public static void SetTracing()
{
SetTracing(SourceLevels.Warning, null);
}
public static void SetTracing(SourceLevels levels, TraceListener listener)
{
if (listener == null)
{
listener = new DefaultTraceListener();
}
// enable WPF tracing
PresentationTraceSources.Refresh();
// enable all WPF Trace sources (change this if you only want DataBindingSource)
foreach (PropertyInfo pi in typeof(PresentationTraceSources).GetProperties(BindingFlags.Static | BindingFlags.Public))
{
if (typeof(TraceSource).IsAssignableFrom(pi.PropertyType))
{
TraceSource ts = (TraceSource)pi.GetValue(null, null);
ts.Listeners.Add(listener);
ts.Switch.Level = levels;
}
}
}