Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
I created a dependency property RxInput::Observer and set its metadata as follows:
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 ?
Thanks.
Code: Select all
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));
}
};
Code: Select all
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);
}
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
You have to cast oldValue and newValue to the same type declared in the DependencyProperty, in your case Ptr<RxInput>:
Code: Select all
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();
...
}
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
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?
What do you think?
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
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 ...
For example,
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 ...
Maybe you can provide an API set that provides both type-safety and performance ?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?
For example,
Code: Select all
// 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);
}
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
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:
Code: Select all
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);
}
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
I think it is OK if the framework does the casting (internally). The framework can make sure the casting always succeed.Yes, problem is that the DependencyPropertyChangedEventArgs you receive cannot be a template, so you would need to cast it to the templatized version.
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.
Code: Select all
// 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;
}
};
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.
Code: Select all
// 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);
}
Code: Select all
// 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));
}
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
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.
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
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.We don't plan this change for the upcoming 2.1 version.
BTW, How about the lambda support ? Is it coming soon ?
Re: Cannot properly use NewValue/OldValue of DependencyPropertyChangedEventArgs
Yes, we plan to solve that in the next version.BTW, How about the lambda support ? Is it coming soon ?
Who is online
Users browsing this forum: No registered users and 64 guests