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 è 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.
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,
Tutte le domande, mi danno un grido. – Dennis