NoesisGUI

PropertyMetadata

Introduction

When registering a dependency property, we use a PropertyMetaData object to set some values associated with the property.

First we can defined its default value. This value could probably be stored inside the property, but the default value (and other info that will be explained later) is stored in a metadata because a dependency property can have different metadatas associated with different reflection types. This gives us the potential to override the default value of a property in derived classes. For example:

// DerivedDelayedButton.cpp

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPLEMENT_REFLECTION(DerivedDelayedButton)
{
    Ptr<DependencyData> data = NsMeta<DependencyData>(TypeOf<SelfClass>());
    data->OverrideMetadata<NsFloat32>(DelayedButton::DelayProperty, "Delay",
        PropertyMetaData::Create(5.0f));
}

Instances of the derived class will have a different default value:

Ptr<DelayedButton> btn1 = NsCreateComponent<DelayedButton>();
NsFloat32 delayBtn1 = btn1->GetDelay(); // -> returns 1.0f

Ptr<DerivedDelayedButton> btn2 = NsCreateComponent<DerivedDelayedButton>();
NsFloat32 delayBtn2 = btn2->GetDelay(); // -> returns 5.0f

In addition, other metadata values that have not been modified (overridden) are merged with the metadata associated with the ancestor classes. This other metadata values are: the property changed delegate and the coerce delegate. But PropertyMetaData derived classes can expand property info with more values.

The property changed delegate is a static function that is called every time the real value of the property changes in a DependencyObject. The signature of the function must be the following:

void PropertyChangedDelegate(DependencyObject* d, const DependencyPropertyChangedEventArgs& e);

The function receives the object where the property is changed, and a struct with change info:

  • e.prop: The property that has changed
  • e.oldValue: The value in the dependency object before the change
  • e.newValue: The new value set in the dependency object

Using this info a class can manage changes of its dependency properties and update other values or launch events.

The coerce delegate is a static function that is called whenever a dependency property value is being re-evaluated, or coercion is specifically requested through CoerceValue method. The signature of the function must be the following:

Ptr<Core::BaseComponent> CoerceDelegate(const DependencyObject* d, const void* baseValue);

Coercion interacts with the base value of a dependency property to ensure that some constraints are applied as those constraints exist at the time, but the base value is still retained. The most common use of the coerce delegate is to ensure that a value is between minimum and maximum values. Continuing with the previous example, we can define two more properties, MinimumDelay and MaximumDelay, in our DelayedButton class, and associate a CoerceDelegate to the Delay property:

// DelayedButton.h

class DelayedButton: public Button
{
private:
    static Ptr<Core::BaseComponent> CoerceDelay(const DependencyObject* d, const void* baseValue);
};

// DelayedButton.cpp

////////////////////////////////////////////////////////////////////////////////////////////////////
Ptr<Core::BaseComponent> DelayedButton::CoerceDelay(const DependencyObject* d, const void* baseValue)
{
    const DelayedButton* btn = NsStaticCast<const DelayedButton*>(d);
    NsFloat32 newValue = *static_cast<const NsFloat32*>(baseValue);

    newValue = std::max(btn->GetMinimumDelay(), std::min(newValue, btn->GetMaximumDelay()));

    return Boxing::Box<NsFloat32>(newValue);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPLEMENT_REFLECTION(DelayedButton)
{
    Ptr<DependencyData> data = NsMeta<DependencyData>(TypeOf<SelfClass>());
    data->RegisterProperty<NsFloat32>(DelayProperty, "Delay", PropertyMetaData::Create(1.0f,
        MakeDelegate(&DelayedButton::CoerceDelay)));
    data->RegisterProperty<NsFloat32>(MinimumDelayProperty, "MinimumDelay",
        PropertyMetaData::Create(0.0f));
    data->RegisterProperty<NsFloat32>(MaximumDelayProperty, "MaximumDelay",
        PropertyMetaData::Create(10.0f));
}

But we also need to ensure that minimum value is lesser than maximum value, so we apply coerce functions to that properties too:

// DelayedButton.h

class DelayedButton: public Button
{
private:
    static Ptr<Core::BaseComponent> CoerceMin(const DependencyObject* d, const void* baseValue);
    static Ptr<Core::BaseComponent> CoerceMax(const DependencyObject* d, const void* baseValue);
};

// DelayedButton.cpp

////////////////////////////////////////////////////////////////////////////////////////////////////
Ptr<Core::BaseComponent> DelayedButton::CoerceMin(const DependencyObject* d, const void* baseValue)
{
    const DelayedButton* btn = NsStaticCast<const DelayedButton*>(d);
    NsFloat32 newValue = *static_cast<const NsFloat32*>(baseValue);

    NsFloat32 maxValue = btn->GetMaximumDelay();
    if (newValue > maxValue)
    {
        newValue = maxValue;
    }

    return Boxing::Box<NsFloat32>(newValue);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
Ptr<Core::BaseComponent> DelayedButton::CoerceMax(const DependencyObject* d, const void* baseValue)
{
    const DelayedButton* btn = NsStaticCast<const DelayedButton*>(d);
    NsFloat32 newValue = *static_cast<const NsFloat32*>(baseValue);

    NsFloat32 minValue = btn->GetMinimumDelay();
    if (newValue < minValue)
    {
        newValue = minValue;
    }

    return Boxing::Box<NsFloat32>(newValue);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPLEMENT_REFLECTION(DelayedButton)
{
    Ptr<DependencyData> data = NsMeta<DependencyData>(TypeOf<SelfClass>());
    data->RegisterProperty<NsFloat32>(DelayProperty, "Delay", PropertyMetaData::Create(1.0f,
        MakeDelegate(&DelayedButton::CoerceDelay)));
    data->RegisterProperty<NsFloat32>(MinimumDelayProperty, "MinimumDelay",
        PropertyMetaData::Create(0.0f, MakeDelegate(&DelayedButton::CoerceMin)));
    data->RegisterProperty<NsFloat32>(MaximumDelayProperty, "MaximumDelay",
        PropertyMetaData::Create(10.0f, MakeDelegate(&DelayedButton::CoerceMax)));
}

And finally we must check if minimum and maximum values change to update current delay value:

// DelayedButton.h

class DelayedButton: public Button
{
private:
    static void OnMinMaxChanged(DependencyObject* d, const DependencyPropertyChangedEventArgs& e);
};

// DelayedButton.cpp

////////////////////////////////////////////////////////////////////////////////////////////////////
void DelayedButton::OnMinMaxChanged(DependencyObject* d, const DependencyPropertyChangedEventArgs& e)
{
    DelayProperty->CoerceValue(d);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPLEMENT_REFLECTION(DelayedButton)
{
    Ptr<DependencyData> data = NsMeta<DependencyData>(TypeOf<SelfClass>());
    data->RegisterProperty<NsFloat32>(DelayProperty, "Delay", PropertyMetaData::Create(1.0f,
        MakeDelegate(&DelayedButton::CoerceDelay)));
    data->RegisterProperty<NsFloat32>(MinimumDelayProperty, "MinimumDelay",
        PropertyMetaData::Create(0.0f, MakeDelegate(&DelayedButton::OnMinMaxChanged),
        MakeDelegate(&DelayedButton::CoerceMin)));
    data->RegisterProperty<NsFloat32>(MaximumDelayProperty, "MaximumDelay",
        PropertyMetaData::Create(10.0f, MakeDelegate(&DelayedButton::OnMinMaxChanged),
        MakeDelegate(&DelayedButton::CoerceMax)));
}

WPF Compatibility

http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.aspx


Properties:
Name Sup Comments
CoerceValueCallback Yes Renamed to CoerceDelegate
DefaultValue Yes  
PropertyChangedCallback Yes Renamed to PropertyChangedDelegate
© 2017 Noesis Technologies