En los 3 últimos años de la construcción de WPF aplicaciones casi a tiempo completo que he recogido una variedad de reactivos y preventivas soluciones para asegurar que todo lo une correctamente.
Nota:
te dará un breve resumen de vez en cuando después de vuelta por la mañana (en 10 horas de tiempo) con ejemplos de código/capturas de pantalla.
Estos son mis herramientas más eficaces:
1) Crear un convertidor que rompe el depurador cuando se ejecuta el Convert
y ConvertBack
. Una forma rápida y útil de garantizar que tenga los valores que espera. La primera vez que supe de este truco fue 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) Crear unTraceListener
que intercepta los errores. Esto es similar a lo que ve en la ventana de salida de Visual Studio cuando tiene un depurador conectado. Usando este método, puedo hacer que el depurador se rompa cuando se produce una excepción durante una operación de enlace. Esto es mejor que configurar PresentationTraceSources.TraceLevel
, ya que se aplica a toda la aplicación, no por enlace.
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);
En lo anterior, es un escritor ILogger
NLog registro. Tengo una versión más compleja de DefaultTraceListener
que puede informar un seguimiento completo de la pila y arrojar excepciones, pero esto será suficiente para comenzar (Jason Bock tiene un article on this extended implementation si desea implementarlo usted mismo, aunque necesitará un código para realmente haz que funcione).
3) Utilice la herramienta de introspección Snoop WPF para profundizar en su vista e inspeccionar sus objetos de datos. Con Snoop puede ver la estructura lógica de su vista y cambiar de forma interactiva los valores para probar diferentes condiciones.
Snoop WPF es absolutamente esencial al tiempo de iteración de cualquier aplicación de WPF. Entre sus muchas funciones, el comando Delve le permite profundizar en su modelo de vista/vista y ajustar de forma interactiva los valores. Para profundizar en una propiedad, haga clic derecho para abrir el menú contextual y seleccione el comando Delve; para volver a subir un nivel (¿no profundizar?), hay un pequeño botón ^ en la esquina superior derecha. Por ejemplo, intente profundizar en la propiedad DataContext
.
Editar: No puedo creer que acabo de notar esto, sin embargo hay una lengüeta de datos Contexto en la ventana Snoop WPF.
4) controles de tiempo de ejecución en INotifyPropertyChanged
eventos en #DEBUG
. Como el sistema de enlace de datos depende de que se le notifique cuando las propiedades han cambiado, es importante para su cordura que esté notificando que la propiedad correcta ha cambiado. Con un poco de magia de reflexión puedes Debug.Assert
cuando algo está mal.
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,
Cualquier pregunta, me dan un grito. – Dennis