nikobarli
Topic Author
Posts: 180
Joined: 26 Apr 2017, 06:23

Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

14 Sep 2017, 05:11

I created a dependency property RxInput::Observer and set its metadata as follows:
    class RxInput : public BaseComponent {
    public:
        // Observer DependencyProperty
        static const DependencyProperty* ObserverProperty;
        static RxInput* GetObserver(const DependencyObject* obj);
        static void SetObserver(DependencyObject* obj, RxInput* value);

    public:
        RxInput();
        virtual ~RxInput();

    public:
        static void ObserverPropertyChangedCallback(DependencyObject* obj, const DependencyPropertyChangedEventArgs& args);

   public:

        NS_IMPLEMENT_INLINE_REFLECTION(RxInput, BaseComponent)
        {
            NsMeta<TypeId>("Noesis.RxInput");
            auto data = NsMeta<DependencyData>(TypeOf<SelfClass>());
            data->RegisterProperty<Ptr<RxInput>>(ObserverProperty, "Observer", PropertyMetadata::Create(Ptr<RxInput>(), &RxInput::ObserverPropertyChangedCallback));
        }
    };

Inside XAML, I bind a property to be assigned to this dependency property, and when run, ObserverPropertyChangedCallback is successfully called. However, I don't know how I can get the assigned value from args.newValue. The following codes didn't work. NsDynamicCast<RxInput *>(args.newValue) also didn't work. What is the correct way to do it ?
    void RxInput::ObserverPropertyChangedCallback(DependencyObject* obj, const DependencyPropertyChangedEventArgs& args) {
        // This doesn't work
        auto current = (RxInput *)args.newValue;
        auto old = (RxInput *)args.oldValue;
        
        // I can workaround and get the current value as follows, but I still can get the old value
        // auto current = GetObserver(obj);
    }
Thanks.
 
User avatar
sfernandez
Site Admin
Posts: 2991
Joined: 22 Dec 2011, 19:20

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

15 Sep 2017, 12:49

You have to cast oldValue and newValue to the same type declared in the DependencyProperty, in your case Ptr<RxInput>:
void RxInput::ObserverPropertyChangedCallback(DependencyObject* obj, const DependencyPropertyChangedEventArgs& args) {
  RxInput* old = static_cast<const Ptr<RxInput>*>(args.oldValue)->GetPtr();
  RxInput* current = static_cast<const Ptr<RxInput>*>(args.newValue)->GetPtr();
  ...
}
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

18 Sep 2017, 18:31

We know this is a bit ugly (having to manually cast to the expected type) and usafe. But the alternative would be using boxing and BaseComponent to pass the parameters... This is something we are evaluating for the next major release. But honestly we are not sure about it right now...

What do you think?
 
nikobarli
Topic Author
Posts: 180
Joined: 26 Apr 2017, 06:23

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

19 Sep 2017, 08:28

Hi,

Thanks, I confirmed I can now get the old/new value correctly. Should find that out myself if I carefully look at the PropertyMetadata::Create API carefully ... ;)
We know this is a bit ugly (having to manually cast to the expected type) and usafe. But the alternative would be using boxing and BaseComponent to pass the parameters... This is something we are evaluating for the next major release. But honestly we are not sure about it right now...

What do you think?
Maybe you can provide an API set that provides both type-safety and performance ?
For example,
    // Templatized DependencyPropertyChangedEventArgs
    // Ok to store void * internally, but provide users with type-safe get interface
    template<typename T>
    struct DependencyPropertyChangedEventArgs
    {
    public:
        DependencyProperty* GetDependencyProperty();
        // Haven't tried myself, but I think you can use enabled_if metafunction to switch
        // the behavior when T is a Ptr<...> type or when it is a plain type
        T GetOldValue() { ... };
        T GetNewValue() { ... };
    };

    template<typename T>
    using PropertyChangedDelegate = Core::Delegate<void(DependencyObject* d, const DependencyPropertyChangedEventArgs<T> & e)>;

    template<typename T>
    Ptr<PropertyMetadata> PropertyMetadata::Create(const T& defaultValue, const PropertyChangedDelegate<T>& propChanged) {
        ...
    }


    // testing code below
    void CallbackPtr(DependencyObject* d, const DependencyPropertyChangedEventArgs<Ptr<BaseComponent>> & e) {
        Ptr<BaseComponent> t = e.GetOldValue();
    }

    void CallbackInt(DependencyObject* d, const DependencyPropertyChangedEventArgs<int> & e) {
        int t = e.GetOldValue();
    }

    void test() {
         auto tmp1 = PropertyMetadata::Create<Ptr<BaseComponent>>(Ptr<BaseComponent>(), CallbackPtr);
         auto tmp2 = PropertyMetadata::Create<int>(0, CallbackInt);
    }
    
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

20 Sep 2017, 01:26

Yes, problem is that the DependencyPropertyChangedEventArgs you receive cannot be a template, so you would need to cast it to the templatized version. And that would be equally unsafe. With boxing you get type safety though:
void RxInput::ObserverPropertyChangedCallback(DependencyObject* obj, const DependencyPropertyChangedEventArgs& args) {
{
    // args.newValue and args.oldValue have BaseComponent* static type
    assert(CanUnbox<int>(args.newValue));
    assert(CanUnbox<int>(args.oldValue));

    int newValue = Unbox<int>(args.newValue);
    int oldValue = UnBox<int>(args.oldValue);
}
 
nikobarli
Topic Author
Posts: 180
Joined: 26 Apr 2017, 06:23

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

21 Sep 2017, 02:53

Yes, problem is that the DependencyPropertyChangedEventArgs you receive cannot be a template, so you would need to cast it to the templatized version.
I think it is OK if the framework does the casting (internally). The framework can make sure the casting always succeed.

From the user perspective, as long as he can register a strongly-typed callback, and get a strongly-typed DependencyPropertyChangedEventArgs, then he is guaranteed the type safety.

I experimented a bit here, by creating a templated thin wrapper around DependencyPropertyChangedEventArgs. GetOldValue/GetNewValue will return U* if the template type is T = Ptr<U>, and return T otherwise (e.g. when T=int,string etc). The casting is done by the wrapper, so the user doesn't need to care about it.
    // some template toolings
    template<typename T> struct is_component : std::false_type {};
    template<typename T> struct is_component<Ptr<T>> : std::true_type { typedef T internal_type; };
    template<typename T> struct remove_Ptr { typedef T type; };
    template<typename T> struct remove_Ptr<Ptr<T>> : remove_Ptr<T> {};

    // A templated wrapper for DependencyPropertyChangedEventArgs
    template<typename T>
    class DependencyPropertyChangedEventArgsWrapper {
    private:
        DependencyPropertyChangedEventArgs args;
    public:
        DependencyPropertyChangedEventArgsWrapper(const DependencyPropertyChangedEventArgs & _args): args(_args) { }

        const DependencyProperty* GetDependencyProperty() const { return args.prop; }
        
        // GetOldValue enabled for T = Ptr<...>
        template<typename U = T>
        typename std::enable_if<is_component<U>::value, typename remove_Ptr<U>::type>::type * GetOldValue() const {
            auto ret = static_cast<const T*>(args.oldValue)->GetPtr();
            return ret;
        }
        
        // GetOldValue enabled for T != Ptr<...>
        template<typename U = T>
        typename std::enable_if<!is_component<U>::value, U>::type GetOldValue() const {
            auto ret = *static_cast<const T*>(args.oldValue);
            return ret;
        }

        // GetNewValue enabled for T = Ptr<...>
        template<typename U = T>
        typename std::enable_if<is_component<U>::value, typename remove_Ptr<U>::type>::type * GetNewValue() const {
            auto ret = static_cast<const T*>(args.newValue)->GetPtr();
            return ret;
        }

        // GetNewValue enabled for T != Ptr<...>
        template<typename U = T>
        typename std::enable_if<!is_component<U>::value, U>::type GetNewValue() const {
            auto ret = *static_cast<const T*>(args.newValue);
            return ret;
        }
    };
Then I provide an API to register strongly-typed callback. Internally, it is still using the original API (using Delegate with DependencyPropertyChangedEventArgs as parameter).
Note that currently it doesn't compile because NoesisGUI cannot handle lambda that captures non-trivial objects. Can be worked-around, but for simplicity, I just let it as it is.
    // A templated version of PropertyChangedDelegate
    template<typename T>
    using PropertyChangedDelegateT = Core::Delegate<void (DependencyObject* d, const DependencyPropertyChangedEventArgsWrapper<T>& e)>;

    // API to create property metadata, passing a templated version of  PropertyChangedDelegate
    template<typename T>
    Ptr<PropertyMetadata> CreatePropertyMetadata(const DependencyProperty * prop, const T& defaultValue, const PropertyChangedDelegateT<T>& propChanged) {
        // this currently doesn't work, because NoesisGUI cannot handle lambda yet ...
        PropertyMetadata::PropertyChangedDelegate innerCallback = [propChanged](DependencyObject* obj, const DependencyPropertyChangedEventArgs& args) { 
            DependencyPropertyChangedEventArgsWrapper<T> wrapper(args);
            propChanged(obj, wrapper);
        };
        return PropertyMetadata::Create<T>(defaultValue, innerCallback);
    }
Then, as far as the user concern, he will just register a strongly-typed callback and handle a strongly-typed DependencyPropertyChangedEventArgs.
    // Strongly-typed callback
    void PropertyChangedCallbackForObserver(DependencyObject* obj, const DependencyPropertyChangedEventArgsWrapper<Ptr<RxInput>> & args) {
        // strongly-typed oldValue and newValue
        auto oldValue = args.GetOldValue();
        auto newValue = args.GetNewValue();
    }

    NS_IMPLEMENT_REFLECTION(RxInput)
    {
        NsMeta<TypeId>("Noesis.RxInput");
        auto data = NsMeta<DependencyData>(TypeOf<SelfClass>());
        data->RegisterProperty<Ptr<RxInput>>(ObserverProperty, "Observer", CreatePropertyMetadata<Ptr<RxInput>>(ObserverProperty, Ptr<RxInput>(), PropertyChangedCallbackForObserver));
    }
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

22 Sep 2017, 13:16

Thanks for this! It is really cool. I will revisit it again whenever we I have time for it. We don't plan this change for the upcoming 2.1 version.
 
nikobarli
Topic Author
Posts: 180
Joined: 26 Apr 2017, 06:23

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

25 Sep 2017, 02:44

We don't plan this change for the upcoming 2.1 version.
Ok. I think we can live without it for now. The point is, I think you don't need to sacrifice the performance by forcing boxing/unboxing of non-component values.

BTW, How about the lambda support ? Is it coming soon ?
 
User avatar
jsantos
Site Admin
Posts: 3918
Joined: 20 Jan 2012, 17:18
Contact:

Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs

26 Sep 2017, 21:04

BTW, How about the lambda support ? Is it coming soon ?
Yes, we plan to solve that in the next version.

Who is online

Users browsing this forum: Google [Bot] and 18 guests