2012-03-07 9 views
9

Quale metodo si usa quando si eseguono il debug di eventi o associazioni WPF?Debug di eventi WPF, associazioni

Ho provato a utilizzare il punto di interruzione, ma sembra che qualcosa non funzioni con il mio XAML o dietro al codice che non raggiunge mai il punto di interruzione.

C'è un modo per vedere quando faccio clic su qualcosa in WPF, quali messaggi di eventi stanno spuntando o non spuntano per capire cosa è andato storto?

risposta

23

Negli ultimi 3 anni di costruzione WPF applicazioni quasi a tempo pieno ho raccolto una serie di reattiva e preventive soluzioni per garantire che tutto lega insieme in modo corretto.

Nota: Ti fornirò un breve riepilogo ora e poi pubblicheremo al mattino (tra 10 ore) con esempi di codice/schermate.

Questi sono i miei strumenti più efficaci:

1) Creare un convertitore che rompe il debugger quando viene eseguito il Convert e ConvertBack. Un modo rapido e utile per assicurarti di avere i valori che ti aspetti. Ho appreso per la prima volta di questo trucco da Bea Stollnitz's blog post.

DebugConverter.cs

public class DebugConverter : IValueConverter 
{ 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (Debugger.IsAttached) 
      Debugger.Break(); 

     return Binding.DoNothing; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (Debugger.IsAttached) 
      Debugger.Break(); 

     return Binding.DoNothing; 
    } 

} 

2) Creare un TraceListener che intercetta eventuali errori. Questo è simile a ciò che si vede nella finestra di output di Visual Studio quando è collegato un debugger. Usando questo metodo posso far sì che il debugger si interrompa quando c'è un'eccezione generata durante un'operazione di bind. Questo è meglio dell'impostazione di PresentationTraceSources.TraceLevel in quanto si applica all'intera applicazione, non al collegamento.

DataBindingErrorLogger.cs

public class DataBindingErrorLogger : DefaultTraceListener, IDisposable 
{ 
    private ILogger Logger; 

    public DataBindingErrorLogger(ILogger logger, SourceLevels level) 
    { 
     Logger = logger; 

     PresentationTraceSources.Refresh(); 
     PresentationTraceSources.DataBindingSource.Listeners.Add(this); 
     PresentationTraceSources.DataBindingSource.Switch.Level = level; 
    } 

    public override void Write(string message) 
    { 
    } 

    public override void WriteLine(string message) 
    { 
     Logger.BindingError(message); 

     if (Debugger.IsAttached && message.Contains("Exception")) 
      Debugger.Break(); 
    } 

    protected override void Dispose(bool disposing) 
    { 
     Flush(); 
     Close(); 

     PresentationTraceSources.DataBindingSource.Listeners.Remove(this); 
    } 

} 

Uso

DataBindingErrorLogger = new DataBindingErrorLogger(Logger, SourceLevels.Warning); 

In quanto sopra, ILogger è uno scrittore NLog registro. Ho una versione più complessa di DefaultTraceListener che può segnalare una traccia completa dello stack e lanciare eccezioni, ma questo sarà sufficiente per iniziare (Jason Bock ha uno article on this extended implementation se lo vuoi implementare da solo, anche se in realtà avrai bisogno di un codice per fallo funzionare).

3) Utilizzare lo strumento di introspezione Snoop WPF per approfondire la visualizzazione e ispezionare gli oggetti dati. Usando Snoop puoi visualizzare la struttura logica della tua vista e modificare i valori in modo interattivo per testare condizioni diverse.

Snoop WPF

Snoop WPF è assolutamente essenziale al tempo iterazione di qualsiasi applicazione WPF. Tra le sue numerose funzionalità, il comando Delve consente di visualizzare in dettaglio il modello di visualizzazione/visualizzazione e di modificare i valori in modo interattivo. Per approfondire una proprietà, fare clic con il tasto destro del mouse per aprire il menu di scelta rapida e selezionare Elimina comando; per tornare indietro di un livello (non-scavare?) c'è un piccolo pulsante ^ nell'angolo in alto a destra. Ad esempio, provare a scavare nella proprietà DataContext.

Edit: Non posso credere che ho appena notato questo, tuttavia c'è una scheda dati di contesto nella finestra Snoop WPF.

DataContext Tab

4) controlli Runtime sui INotifyPropertyChanged eventi in #DEBUG. Poiché il sistema Data Binding si basa sulla notifica quando le proprietà sono cambiate, è importante per la tua sanità mentale che tu stia avvisando che la proprietà corretta è cambiata. Con un po 'di riflessione magica puoi fare Debug.Assert quando qualcosa non va.

PropertyChangedHelper.cs

public static class PropertyChangedHelper 
{ 
    #if DEBUG 
    public static Dictionary<Type, Dictionary<string, bool>> PropertyCache = new Dictionary<Type, Dictionary<string, bool>>(); 
    #endif 

    [DebuggerStepThrough] 
    public static void Notify(this INotifyPropertyChanged sender, PropertyChangedEventHandler eventHandler, string propertyName) 
    { 
     sender.Notify(eventHandler, new PropertyChangedEventArgs(propertyName), true); 
    } 

    [DebuggerStepThrough] 
    public static void Notify(this INotifyPropertyChanged sender, PropertyChangedEventHandler eventHandler, string propertyName, bool validatePropertyName) 
    { 
     sender.Notify(eventHandler, new PropertyChangedEventArgs(propertyName), validatePropertyName); 
    } 

    [DebuggerStepThrough] 
    public static void Notify(this INotifyPropertyChanged sender, PropertyChangedEventHandler eventHandler, PropertyChangedEventArgs eventArgs) 
    { 
     sender.Notify(eventHandler, eventArgs, true); 
    } 

    [DebuggerStepThrough] 
    public static void Notify(this INotifyPropertyChanged sender, PropertyChangedEventHandler eventHandler, PropertyChangedEventArgs eventArgs, bool validatePropertyName) 
    { 
     #if DEBUG 
     if (validatePropertyName) 
      Debug.Assert(PropertyExists(sender as object, eventArgs.PropertyName), String.Format("Property: {0} does not exist on type: {1}", eventArgs.PropertyName, sender.GetType().ToString())); 
     #endif 

     // as the event handlers is a parameter is actually somewhat "thread safe" 
     // http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx 
     if (eventHandler != null) 
      eventHandler(sender, eventArgs); 
    } 

    #if DEBUG 
    [DebuggerStepThrough] 
    public static bool PropertyExists(object sender, string propertyName) 
    { 
     // we do not check validity of dynamic classes. it is possible, however since they're dynamic we couldn't cache them anyway. 
     if (sender is ICustomTypeDescriptor) 
      return true; 

     var senderType = sender.GetType();  
     if (!PropertyCache.ContainsKey(senderType)) 
      PropertyCache.Add(senderType, new Dictionary<string,bool>()); 

     lock (PropertyCache) 
     { 
      if (!(PropertyCache[senderType].ContainsKey(propertyName))) 
      { 
       var hasPropertyByName = (senderType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) != null); 
       PropertyCache[senderType].Add(propertyName, hasPropertyByName); 
      } 
     } 

     return PropertyCache[senderType][propertyName]; 
    } 
    #endif 

} 

HTH,

+0

Tutte le domande, mi danno un grido. – Dennis

1

Hai l'uscita di vista attiva. Ciò mostrerà alcuni errori vincolanti. PresentationTraceSources.TraceLevel = "Alto" mostrerà più informazioni. Potrebbe verificarsi un errore prima che arrivi al punto di interruzione. Imposta un punto di interruzione nel costruttore solo per vederlo funzionare.

1

L'aggiunta di un convertitore "pass-through" su un'associazione può a volte aiutare consentendo di inserire un punto di interruzione nel convertitore che verrà tirato quando si verifica un aggiornamento obbligatorio. Permette anche di vedere i valori che stanno passando in entrambe le direzioni attraverso il binding dal parametro valore Convert e ConvertBack.

public class PassthroughConverter : IValueConverter { 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 
     return value; // Breakpoint here. 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { 
     return value; // Breakpoint here. 
    } 
} 

Se è possibile accedere a un controllo per nome allora nella vostra Window.xaml.cs è possibile controllare lo stato del binding sul controllo utilizzando:

BindingExpression be = comboMyCombo.GetBindingExpression(ComboBox.IsEnabledProperty); 

guardando 'essere' nel debugger può aiutare (a volte i binding vengono ripristinati/interrotti su determinate operazioni).