Como detectar a binding de dados WPF quebrada?

Enquanto tentava responder a uma pergunta na vizinhança ‘ Unit Testing WPF Bindings ‘, tive a seguinte pergunta:
Qual é a melhor maneira de encontrar se você tem configuração de fiação de dados de binding WPF incorretamente (ou você acabou de quebrar algo que foi ligado corretamente)?

Embora a abordagem de testes unitários pareça ser como a de Joel “arrancando seu arm para remover uma lasca” … Estou procurando maneiras menos fáceis de detectar isso.

Todos parecem ter se comprometido muito com a vinculação de dados com o WPF .. e isso tem seus méritos.

No .NET 3.5, foi introduzida uma nova maneira de especificamente fornecer informações de rastreamento sobre ligações de dados específicas.

Isso é feito através da nova propriedade anexada System.Diagnostics.PresentationTraceSources.TraceLevel que você pode aplicar a qualquer associação ou provedor de dados. Aqui está um exemplo:

       

Isso colocará informações de rastreio para apenas essa binding específica na janela de saída do Visual Studio, sem qualquer configuração de rastreamento necessária.

O melhor que pude encontrar …

Como posso depurar as ligações do WPF? de Beatriz Stollnitz

Como nem sempre é possível manter um olho na janela de saída procurando erros de vinculação, adorei a opção nº 2. Que é adicionar isso ao seu App.Config

 < ?xml version="1.0" encoding="utf-8" ?>                   

Combine isso com um bom script de varredura regex para extrair informações relevantes, que você pode executar ocasionalmente no GraveOfBindErrors.txt em sua pasta de saída

 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') 

Você pode usar o recurso de debugging do triggersdor do Inspetor do WPF. Basta baixar a ferramenta do codeplex e anexá-la ao aplicativo em execução. Ele também mostra erros de binding na parte inferior da janela. Ferramenta muito útil!

insira a descrição da imagem aqui

Eu uso a solução apresentada aqui para transformar os erros de binding em exceções nativas: http://www.jasonbock.net/jb/Default.aspx?blog=entry.0f221e047de740ee90722b248933a28d

No entanto, um cenário normal nas ligações do WPF é lançar exceções caso a input do usuário não possa ser convertida para o tipo de destino (por exemplo, um TextBox vinculado a um campo inteiro; a input de uma string não numérica resulta em uma FormatException, input de número que é muito grande resulta em um OverflowException). Um caso semelhante ocorre quando o Setter da propriedade de origem lança uma exceção.

A maneira do WPF de lidar com isso é via ValidatesOnExceptions = true e ValidationExceptionRule para sinalizar ao usuário que a input fornecida não está correta (usando a mensagem de exceção).

No entanto, essas exceções também são enviadas para a janela de saída e, portanto, ‘capturadas’ pelo BindingListener, resultando em um erro … claramente não é o comportamento que você deseja.

Portanto, eu expandi a class BindingListener para NÃO lançar uma exceção nestes casos:

 private static readonly IList m_MessagesToIgnore = new List() { //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" }; 

Linhas modificadas em override pública 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(); } } } 

Aqui está uma técnica útil para depurar / rastrear triggers efetivamente. Ele permite que você registre todas as ações do acionador junto com o elemento que está sendo executado:

http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html

Isso foi muito útil para nós, mas eu queria adicionar para aqueles que acham isso útil que existe um utilitário que a Microsoft fornece com o SDK para ler este arquivo.

Encontrado aqui: http://msdn.microsoft.com/pt-br/library/ms732023.aspx

Para abrir um arquivo de rastreamento

1.Inicie o Visualizador de Rastreio de Serviço usando uma janela de comando para navegar até o local de instalação do WCF (C: \ Arquivos de Programas \ Microsoft SDKs \ Windows \ v6.0 \ Bin) e digite SvcTraceViewer.exe. (embora tenhamos encontrado o nosso em \ v7.0 \ Bin)

Nota: A ferramenta Service Trace Viewer pode associar-se a dois tipos de arquivo: .svclog e .stvproj. Você pode usar dois parâmetros na linha de comando para registrar e cancelar o registro das extensões de arquivo.

/ register: registre a associação de extensões de arquivo “.svclog” e “.stvproj” com SvcTraceViewer.exe

/ unregister: cancela o registro da associação de extensões de arquivo “.svclog” e “.stvproj” com SvcTraceViewer.exe

1.Quando o Service Trace Viewer é iniciado, clique em File e, em seguida, aponte para Open. Navegue até o local onde seus arquivos de rastreamento estão armazenados.

2.Clique duas vezes no arquivo de rastreamento que você deseja abrir.

Nota: Pressione SHIFT enquanto clica em vários arquivos de rastreamento para selecioná-los e abri-los simultaneamente. O Service Trace Viewer mescla o conteúdo de todos os arquivos e apresenta uma visualização. Por exemplo, você pode abrir arquivos de rastreamento do cliente e do serviço. Isso é útil quando você ativa o log de mensagens e a propagação de atividades na configuração. Dessa maneira, você pode examinar a troca de mensagens entre o cliente e o serviço. Você também pode arrastar vários arquivos para o visualizador ou usar a guia Projeto. Veja a seção Gerenciando o Projeto para mais detalhes.

3. Para adicionar arquivos de rastreamento adicionais à coleção que está aberta, clique em Arquivo e aponte para Adicionar. Na janela que é aberta, navegue até o local dos arquivos de rastreamento e clique duas vezes no arquivo que deseja adicionar.

Além disso, quanto à filtragem do arquivo de log, achamos este link extremamente útil:

http://msdn.microsoft.com/pt-br/library/ms751526.aspx

Para qualquer um, como eu, que esteja procurando uma maneira programática pura de habilitar todos os Rastreamentos do WPF em um determinado nível de rastreamento, aqui está um trecho de código que faz isso. Para referência, é baseado neste artigo: fonts de rastreamento no WPF .

Ele não requer uma alteração no arquivo app.config e não requer alteração no registro.

É assim que eu uso, em algum lugar de boot (App, etc.):

 .... #if DEBUG WpfUtilities.SetTracing(); #endif .... 

E aqui está o código do utilitário (por padrão, ele envia todos os avisos para o ouvinte de rastreamento padrão):

 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; } } }