WPF: PropertyChangedCallback срабатывает только один раз

у меня есть пользовательский элемент управления, который предоставляет DependencyProperty под названием VisibileItems Каждый раз, когда это свойство обновляется, мне нужно вызвать другое событие. Для этого я добавил FrameworkPropertyMetadata с событием PropertyChangedCallback.

по какой-то причине это событие вызывается только один раз и не запускается при следующем изменении VisibleItems.

XAML:

<cc:MyFilterList VisibleItems="{Binding CurrentTables}"  />

CurrentTables является DependencyProperty на MyViewModel. CurrentTables часто меняется. Я могу привязать другой элемент управления WPF к CurrentTables, и я вижу изменения в пользовательском интерфейсе.

вот как я связал VisibleItems с PropertyChangedCallback

public static readonly DependencyProperty VisibleItemsProperty =
    DependencyProperty.Register(
    "VisibleItems",
    typeof(IList),
    typeof(MyFilterList),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(VisiblePropertyChanged))

    );

public IList VisibleItems {
    get { return (IList)GetValue(VisibleItemsProperty); }
    set { SetValue(VisibleItemsProperty, value); }
}

шагнув в VisiblePropertyChanged, я вижу, что он запускается при первом наборе CurrentTables. но не последующие разы.

обновление

поскольку некоторые из вас задали вопрос о том, как изменяются CurrentTables, он полностью назначается на перемене:

OnDBChange()...
CurrentTables = new List<string>(MainDatabaseDataAdapter.GetTables(this.SelectedServer, this.SelectedDatabase));

эта строка вызывается при каждом изменении, но мой обработчик VisiblePropertyChanged вызывается только в первый раз.

обновление

если я назначаю VisibleItems напрямую, обработчик вызывается каждый раз!

TestFilterList.VisibleItems = new List<string>( Enumerable.Range(1, DateTime.Now.Second).ToList().Select(s => s.ToString()).ToList() );

Итак, похоже, что проблема связана с DependencyProperty (VisibleItems), наблюдающим за другим DependencyProperty (CurrentTables). Каким-то образом привязка работает при первом изменении свойства, но не на последующие? Попытка проверить эту проблему с помощью snoop, как некоторые из вас предложили.

4 ответов


вы устанавливаете "локальное" значение (т. е. присваиваете непосредственно задатчику свойств зависимостей) свойству зависимостей, которое также имеет OneWay привязка к нему? Если это так, установка локального значения удалит привязку, как указано в обзор свойств зависимостей MSDN:

привязки рассматриваются как локальное значение, что означает, что если вы установите другое локальное значение, вы устраните привязку.

зависимость механизм свойств не имеет многого другого, что он может сделать, когда его просят сохранить локальное значение в свойстве зависимостей. Он не может отправить значение через привязку, потому что привязка "указывает" неправильный путь. После установки локального значения он больше не показывает значение, полученное из привязки. Поскольку он больше не показывает значение из привязки, он удаляет привязку.

после привязки нет,PropertyChangedCallback больше не будет вызываться, когда свойство source для привязки изменяется ее значение. Возможно, поэтому обратный вызов не вызывается.

если вы установите привязку TwoWay, у системы привязки есть место для хранения "локального" значения, которое вы установили: в свойстве источника привязки. В этом случае нет необходимости устранять привязку, поскольку механизм свойств зависимостей может хранить значение в свойстве source.

эта ситуация не вызывает переполнения стека, поскольку следующее бывает:

  • свойство зависимостей получает "локальное" значение.
  • механизм свойств зависимостей отправляет значение "назад" по привязке к свойству источника,
  • свойство Source устанавливает значение свойства и срабатывает PropertyChanged,
  • механизм свойств зависимостей получает PropertyChanged событие, проверяет новое значение исходного свойства, обнаруживает, что оно не изменилось и больше ничего не делает.

ключевым моментом здесь является то, что если вы стреляете PropertyChanged событие на недвижимость значение которого не изменилось, либо PropertyChangedCallbackS для свойств зависимостей, привязанных к вашему свойству, не будет вызываться.

для простоты я игнорировала IValueConverters в вышеуказанном. Если у вас есть преобразователь, убедитесь, что он правильно преобразует значения в обоих направлениях. Я также предположил, что свойство на другом конце является свойством view-model для объекта, реализующего INotifyPropertyChanged. Могло быть другое свойство зависимостей в исходный конец привязки. Механизм свойств зависимостей также может справиться с этим.

как это происходит, WPF (и Silverlight) не содержат обнаружения переполнения стека. Если в PropertyChangedCallback, вы устанавливаете значение свойства зависимости, чтобы отличаться от его нового значения (например, путем увеличения целочисленного свойства или добавления строки к строковому свойству), вы получите переполнение стека.


у меня такая же проблема в моем коде и Люк прав. Я вызвал SetValue mystake внутри PropertyChangedCallback, вызывая потенциальный бесконечный цикл. WPF предотвратить это отключение молча обратный вызов !!

мой WPF UserControl является

PatchRenderer

мое свойство C# - Примечание:

    [Description("Note displayed with star icons"), 
    Category("Data"),
    Browsable(true), 
    EditorBrowsable(EditorBrowsableState.Always),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public int Note
    {
        get { return (int)GetValue(NoteProperty); }
        set { SetValue(NoteProperty, value); /* don't put anything more here */ }
    }

мое свойство WPF

public static readonly DependencyProperty 
        NoteProperty = DependencyProperty.Register("Note",
        typeof(int), typeof(PatchRenderer),
        new PropertyMetadata(
            new PropertyChangedCallback(PatchRenderer.onNoteChanged)
            ));

    private static void onNoteChanged(DependencyObject d,
               DependencyPropertyChangedEventArgs e)
    {
        // this is the bug: calling the setter in the callback
        //((PatchRenderer)d).Note = (int)e.NewValue;

        // the following was wrongly placed in the Note setter.
        // it make sence to put it here.
        // this method is intended to display stars icons
        // to represent the Note
        ((PatchRenderer)d).UpdateNoteIcons();
    }

если вы просто инстанцировать MyFilterList и set VisibleItems через такой код:

var control = new MyFilterList();
control.VisibleItems = new List<string>();
control.VisibleItems = new List<string>();

вы, вероятно, увидите, что PropertyChangedCallback происходит каждый раз. Это означает, что проблема связана с привязкой, а не с обратным вызовом. Убедитесь, что у вас нет ошибок привязки, вы поднимаете PropertyChanged, и вы не нарушаете привязку (например, установив VisibleItems в коде)


у вас может возникнуть проблема, когда содержимое коллекции изменяется, но не фактический экземпляр. в этом случае вы захотите использовать ObservableCollection и сделать что-то вроде этого:

public static readonly DependencyProperty VisibleItemsProperty =
    DependencyProperty.Register(
    "VisibleItems",
    typeof(IList),
    typeof(MyFilterList),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(VisibleItemsChanged)));

    private static void VisibleItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var myList = d as MyFilterList;
        if (myList == null) return;

        myList.OnVisibleItemsChanged(e.NewValue as IList, e.OldValue as IList);
    }

    protected virtual void OnVisibleItemsChanged(IList newValue, IList oldValue)
    {
        var oldCollection = oldValue as INotifyCollectionChanged;
        if (oldCollection != null)
        {
            oldCollection.CollectionChanged -= VisibleItems_CollectionChanged;
        }
        var newCollection = newValue as INotifyCollectionChanged;
        if (newCollection != null)
        {
            newCollection.CollectionChanged += VisibleItems_CollectionChanged;
        }
    }