Ответ 1
За последние 3 года разработки приложений WPF почти полный рабочий день я собрал множество реактивных и профилактических решений, чтобы гарантировать, что все связывается правильно.
Примечание: Я дам вам краткое резюме, а затем отправлю обратно утром (через 10 часов) с образцами кода/снимками экрана.
Это мои самые эффективные инструменты:
1) Создайте конвертер, который разбивает отладчик при выполнении Convert
и ConvertBack
. Быстрый и полезный способ убедиться, что у вас есть значения, которые вы ожидаете. Я впервые узнал об этом трюке из сообщение в блоге Bea Stollnitz.
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) Создайте TraceListener
, который перехватывает любые ошибки. Это похоже на то, что вы видите в окне вывода Visual Studio при подключении отладчика. Используя этот метод, я могу заставить отладчик сломаться, когда возникает исключение, возникшее во время операции привязки. Это лучше, чем установка PresentationTraceSources.TraceLevel
, так как это относится ко всему приложению, а не к привязке.
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);
}
}
Использование
DataBindingErrorLogger = new DataBindingErrorLogger(Logger, SourceLevels.Warning);
В приведенном выше примере ILogger
является NLog автором журнала. У меня есть более сложная версия DefaultTraceListener
, которая может сообщать о полной трассировке стека и фактически генерировать исключения, но этого будет достаточно, чтобы вы начали (у Джейсона Бока есть статью об этой расширенной реализации, если вы хотите реализовать ее самостоятельно, хотя вам понадобится код, чтобы заставить его работать).
3) Используйте инструмент Snoop WPF, чтобы вникать в ваше представление и проверить ваши объекты данных. С помощью Snoop вы можете просмотреть логическую структуру вашего представления и в интерактивном режиме изменить значения для проверки различных условий.
Snoop WPF абсолютно необходим для времени итерации любого приложения WPF. Среди его многочисленных функций команда Delve позволяет переходить к вашей модели view/view и интерактивно настраивать значения. Чтобы углубиться в свойство, щелкните правой кнопкой мыши, чтобы открыть контекстное меню и выбрать команду "Делить"; чтобы вернуться к уровню (не разбирайтесь?), в верхнем правом углу есть небольшая кнопка ^. Например, попробуйте вникать в свойство DataContext
.
Изменить: Я не могу поверить, что я это заметил, однако в окне Snoop WPF есть вкладка Data Context.
4) Выполняет проверку событий INotifyPropertyChanged
в #DEBUG
. Поскольку система привязки данных полагается на уведомление, когда свойства были изменены, для вашего здравомыслия важно, чтобы вы сообщили, что правильное свойство изменилось. С малой маской отражения вы можете Debug.Assert
, когда что-то не так.
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,