Подпишитесь на события PropertyChanged окна в C++/CLI

Я просто пытался подписаться на события изменения свойств WPF, используя C++/CLI. Я не ожидал, что это станет трудным.

Сначала я попытался подписаться на определенное свойство некоторого окна (IsMouseDirectlyOver) и, наконец, преуспел в следующем коде:

void MyClass::DependencyPropertyChanged(Object^ sender, DependencyPropertyChangedEventArgs args)
{
   Debug::WriteLine("DependencyPropertyChanged: "+sender->ToString()+", "+args.Property->Name);
}

window->IsMouseDirectlyOverChanged += gcnew DependencyPropertyChangedEventHandler(this, &MyClass::DependencyPropertyChanged);

Затем я попытался подписаться на любые изменения свойств объекта (что для меня наиболее важно, потому что мой окончательный код должен иметь возможность обрабатывать изменения свойств по именам свойств). Я полностью потерпел неудачу в этом.

Я пробовал разные вещи, но ничего не получалось. Я не смог найти никаких примеров C++/CLI, но, согласно документации и примерам C#, следующий код показался мне наиболее разумным:

window->PropertyChanged += gcnew PropertyChangedEventHandler(this, &MyClass::PropertyChanged);

void MyClass::PropertyChanged(Object^ sender, PropertyChangedEventArgs^ args)
{
   ...
}

Но компилятор сообщает мне об ошибке C2039, что «PropertyChangedEvent» не является элементом «System::Windows::Window».

Как я могу достичь того, чего хочу?


person Silicomancer    schedule 22.12.2012    source источник
comment
Класс WPF Window просто не реализует INotifyPropertyChanged. и, следовательно, не имеет события PropertyChanged.   -  person Clemens    schedule 22.12.2012
comment
Вы действительно хотите получать уведомления об изменении любого свойства в окне?   -  person Clemens    schedule 22.12.2012
comment
Да. Но не только окна. Он также должен работать с любым другим видом объекта визуального дерева.   -  person Silicomancer    schedule 22.12.2012
comment
Хм... нет, я не совсем прав. Мне нужно подписаться на изменения свойства по его имени. На самом деле это не означает, что мне нужно подписаться на все свойства. Приведенный выше механизм казался единственным, который разрешал доступ по имени (с использованием переключателя/регистра по имени в методе обработчика событий). Если есть способ специально подписаться на одно или несколько свойств по имени, это тоже решит мою проблему.   -  person Silicomancer    schedule 22.12.2012


Ответы (3)


Все, что упоминалось в комментариях, ваш код не работает, потому что на Window нет события PropertyChanged, это так просто.

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

person svick    schedule 22.12.2012
comment
Я не могу переопределить метод в окне, так как окно является предопределенным. Я не могу это изменить. Интересно, как реализован такой инструмент, как Snoop. Он отслеживает изменения всех свойств любого объекта WPF, даже окон. Я только что попробовал... Snoop, кажется, может подписаться на все свойства любого объекта. Если INotifyPropertyChanged не реализован каждым объектом WPF, какой механизм использует Snoop? - person Silicomancer; 22.12.2012
comment
Я сделаю так. Но если бы у меня было достаточно знаний о .NET/WPF, чтобы знать, что я ищу, мне, вероятно, не нужно было бы спрашивать. - person Silicomancer; 22.12.2012
comment
Кажется, Snoop использует привязку. Я никогда раньше не использовал Bindings, поэтому в настоящее время я не совсем понимаю, как это работает и решает ли это мою проблему. О боже, это действительно становится сложным. - person Silicomancer; 22.12.2012

Я посмотрел на источники snoop. Я изменил его и написал очень простой пример, который работает:

String^ ownerPropertyName = "IsActive";
DependencyObject^ propertyOwner = window;

DependencyPropertyDescriptor^ ownerPropertyDescriptor = DependencyPropertyDescriptor::FromName(ownerPropertyName, propertyOwner->GetType(), propertyOwner->GetType());
DependencyProperty^ ownerProperty = ownerPropertyDescriptor->DependencyProperty;
Type^ ownerPropertyType = ownerProperty->PropertyType;

DependencyProperty^ myProperty = DependencyProperty::Register(ownerPropertyName, ownerPropertyType, GetType(), gcnew PropertyMetadata(gcnew PropertyChangedCallback(&MyClass::BoundPropertyChangedCallback)));

Binding^ myPropertyToOwnerPropertyBinding = gcnew Binding(ownerPropertyName);
myPropertyToOwnerPropertyBinding->Mode = BindingMode::OneWay;
myPropertyToOwnerPropertyBinding->Source = propertyOwner;
BindingOperations::SetBinding(this, myProperty, myPropertyToOwnerPropertyBinding);

И:

static void BoundPropertyChangedCallback(DependencyObject^ me, DependencyPropertyChangedEventArgs args)
{
   Debug::WriteLine("BoundPropertyChangedCallback: "+args.OldValue+", "+args.NewValue+", "+args.Property->Name);
}

Мне кажется довольно сложным. Я понятия не имею, действительно ли этот обязательный материал необходим. На самом деле это может даже подписаться на свойства, которые не имеют событий (например, IsMouseOver), и может работать с объектами, которые не реализуют INotifyPropertyChanged (например, Window). И ему не нужен переключатель/кейс для свойств.

person Silicomancer    schedule 22.12.2012
comment
Я почти уверен, что читал об этом решении в каком-то сообщении в блоге, но не могу вспомнить, где. В любом случае, это кажется правильным, по крайней мере, когда Снуп делает это именно так. - person Clemens; 23.12.2012

Класс PropertyDescriptor (или производный DependencyPropertyDescriptor) предоставляет механизм для добавления обработчика изменения свойства по их AddValueChanged:

DependencyPropertyDescriptor^ propertyDescriptor = DependencyPropertyDescriptor::FromName(
    "ActualWidth", component->GetType(), component->GetType());

propertyDescriptor->AddValueChanged(component, gcnew EventHandler(ActualWidthChanged));
...

static void ActualWidthChanged(Object^ component, EventArgs^ e)
{
    ...
}

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


РЕДАКТИРОВАТЬ: вы можете реализовать что-то вроде кода, показанного ниже, который использует анонимный делегат для передачи имени свойства соответствующему обработчику. Однако обратите внимание, что это C #, и, насколько я понимаю, это невозможно сделать в C++/CLI, поскольку там он не поддерживает управляемые лямбда-выражения. Возможно, вы могли бы обернуть такой вспомогательный класс в отдельную сборку и использовать его из своего кода C++/CLI.

public delegate void PropertyChangedHandler(object component, string propertyName);

public static class DependencyPropertyDescriptorExt
{
    public static void AddPropertyChangedHandler(
        this object component, string propertyName, PropertyChangedHandler handler)
    {
        var propertyDescriptor = DependencyPropertyDescriptor.FromName(
            propertyName, component.GetType(), component.GetType());
        propertyDescriptor.AddValueChanged(component, (o, e) => handler(o, propertyName));
    }
}

Теперь вы можете написать и использовать такой PropertyChangedHandler следующим образом:

this.AddPropertyChangedHandler("ActualHeight", PropertyChanged);
...

private void PropertyChanged(object component, string propertyName)
{
    ...
}
person Clemens    schedule 22.12.2012
comment
Это работает. Но, как вы сказали, жаль, что я не могу отличить исходное свойство. Конечно, я мог бы добавить отдельный обработчик для каждого свойства, но я надеялся избежать необходимой оргии переключателей/кейсов (или чего-то подобного). Возможность создания экземпляра DependencyPropertyDescriptor с использованием имен свойств не имеет большого значения, если обработчик также не знает исходное свойство. - person Silicomancer; 22.12.2012
comment
Я вижу здесь много кода C#, вопрос в C++/CLI. Хотя перевод в большинстве случаев прост, вам потребуется явный объект состояния вместо анонимного делегата — синтаксис делегата C++ еще не используется с .NET. - person Ben Voigt; 22.12.2012
comment
Вы правы. Это всегда тяжелая борьба, чтобы перевести этот материал C# в C++/CLI. Но эй... Я очень рад любой помощи, даже если это касается кода C# ;-) - person Silicomancer; 22.12.2012
comment
@BenVoigt Я думаю, вы могли бы поместить тип делегата и объявления вспомогательного класса в отдельную сборку и ссылаться на нее из C ++. - person Clemens; 22.12.2012
comment
@user1421332 user1421332 Есть ли какая-то особая причина, по которой вы возитесь с C++? Программирование WPF было бы значительно проще, если бы вы использовали C#. - person Clemens; 22.12.2012
comment
Я выбрал C++/CLI, потому что всю жизнь был программистом на C++. Мне казалось это очевидным. Я также новичок в .NET/WPF, и я пытался сохранить низкий порог входа. Приведенный выше синтаксис совершенно нов для меня. Я понятия не имею, как перевести его на C++/CLI. - person Silicomancer; 22.12.2012
comment
Я программирую на C++ около 15 лет и очень рад, что смог оставить его позади. Для меня C# намного лучше языка, и я могу только порекомендовать его изучить. Для WPF это чем-то похоже на естественный язык программирования. Но не только WPF. С ним почти ничего нельзя сделать. - person Clemens; 22.12.2012
comment
Я буду иметь это в виду для моего следующего проекта. Но для моего первого и текущего проекта мне нужно продолжить работу с C++/CLI... включая мою текущую проблему: -/ - person Silicomancer; 22.12.2012
comment
C# не превосходит, он оптимизирован для упрощения некоторых распространенных случаев. Но он не имеет ничего общего с мощностью и гибкостью, например. Шаблоны С++. - person Ben Voigt; 23.12.2012
comment
@BenVoigt Я сказал для меня это лучше. Это всего лишь мое личное мнение, и мне все равно, какой язык вы предпочитаете. - person Clemens; 23.12.2012
comment
Больше нет предложений по исходной проблеме? ;-) - person Silicomancer; 23.12.2012
comment
@ user1421332: Вы можете использовать DependencyPropertyDescriptor, как предлагает этот ответ. Просто в C++/CLI это будет выглядеть иначе. - person Ben Voigt; 23.12.2012
comment
Завтра я попытаюсь сначала понять синтаксис C#. Теперь мне нужно немного поспать :-) - person Silicomancer; 23.12.2012
comment
После прочтения этой статьи я почти уверен, что это решение для анонимного делегата не может быть легко реализовано в C++/CLI, поскольку оно не поддерживает управляемые лямбда-выражения. Вы не можете использовать управляемые типы в лямбда-выражениях C++/CLI и, следовательно, не можете использовать что-то вроде PropertyChangedHandler^ в теле анонимного метода. - person Clemens; 23.12.2012
comment
Как жаль. Так что не буду ломать голову по этому поводу. В любом случае, это решение с привязкой к свойству (см. мои последние ответы), похоже, решает мою проблему... если у кого-то есть лучшее (менее сложное) решение, сообщите мне! - person Silicomancer; 23.12.2012