Отладка событий WPF, привязок

Какой метод вы используете при отладке событий или привязок WPF?

Я попытался использовать точку останова, но мне кажется, что что-то не так с моим XAML или за кодом, который он никогда не попадает в точку останова.

Есть ли способ увидеть, когда я нажимаю что-то в WPF, какие сообщения о событиях появляются или не появляются, чтобы понять, что пошло не так?

Ответ 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

Snoop WPF абсолютно необходим для времени итерации любого приложения WPF. Среди его многочисленных функций команда Delve позволяет переходить к вашей модели view/view и интерактивно настраивать значения. Чтобы углубиться в свойство, щелкните правой кнопкой мыши, чтобы открыть контекстное меню и выбрать команду "Делить"; чтобы вернуться к уровню (не разбирайтесь?), в верхнем правом углу есть небольшая кнопка ^. Например, попробуйте вникать в свойство DataContext.

Изменить: Я не могу поверить, что я это заметил, однако в окне Snoop WPF есть вкладка Data Context.

DataContext Tab

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,

Ответ 2

У вас активен режим просмотра выходных данных? Это покажет некоторые ошибки связывания. PresentationTraceSources.TraceLevel="High" покажет больше информации. Это может быть ошибка до того, как она достигнет точки останова. Установите точку останова в конструкторе, чтобы увидеть, как он работает.

Ответ 3

Добавление "сквозного" конвертера по привязке иногда может помочь, позволяя вам поместить точку останова в конвертер, который будет вытаскиваться, когда будет обновление привязки. Он также позволяет видеть, что значения передаются в обоих направлениях посредством привязки из параметра значения Convert и 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.
    }
}

Если вы можете получить доступ к элементу управления по имени, то в окне Window.xaml.cs вы можете проверить состояние привязок в элементе управления, используя:

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

просмотр "be" в отладчике может помочь (иногда привязки получают reset/ломаются на определенных операциях).