Question

I just tried to subscribe to WPF property change events using C++/CLI. I didn't expect this to get difficult.

First I tried to subscribe to a specific property of some window (IsMouseDirectlyOver) and finally succeeded with following code:

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

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

Then I tried to subscribe to any property changes of an object (which is most important to me because my final code must be able to handle property changes by property names). I totally failed on this.

I tried various things but nothing worked. I could not find any C++/CLI examples but according to documentation and C# examples the following seemed to be the most sensible code to me:

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

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

But the compiler tells me by error C2039 that 'PropertyChangedEvent' is no element of 'System::Windows::Window'.

How can I achieve what I want?

Was it helpful?

Solution 2

I had a look on the snoop sources. I modified it and wrote a very, very basic example that works:

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);

And:

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

Looks pretty complicated to me. I have no idea if that binding stuff is really necessary. In fact this can even subscribe to properties that do not have events (like IsMouseOver) and can operate on objects that do not implement INotifyPropertyChanged (like Window). And it does not need any switch/case for properties.

OTHER TIPS

Al mentioned in the comments, your code doesn't work, because there is no PropertyChanged event on Window, it's as simple as that.

What you can do instead is to override the OnPropertyChanged() method, which is present on a Window. In your override, you can do anything you want, including raising PropertyChanged (don't forget to create that event first).

The class PropertyDescriptor (or the derived DependencyPropertyDescriptor) provides a mechanism to add a property change handler by their AddValueChanged method:

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

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

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

Unfortunately the handler doesn't get passed the changed property, so i guess you would have to add different handlers for all properties you want to monitor.


EDIT: You might implement something like the code shown below that uses an anonymous delegate to pass the property name to an appropriate handler. Note however that this is C#, and to my understanding this can't be done in C++/CLI, since there it does not support managed lambdas. Mayby you could wrap a helper class like this in a separate assembly and use it from your C++/CLI code.

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));
    }
}

Now you could write and use such a PropertyChangedHandler like this:

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

private void PropertyChanged(object component, string propertyName)
{
    ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top