manuel
Topic Author
Posts: 13
Joined: 16 Aug 2022, 10:06

CustomConverter: What is the target type of a DoubleAnimation targeting RenderTransform.X?

11 Jan 2023, 13:49

Hey everyone,

simple question and maybe obvious to answer... but is it? As it is a DOUBLEAnimation, "double" seems to be obvious, but Noesis uses floats in C++ for performance reasons. So I would expect that "float" is the correct answer here. The error message hints towards "Int32" which makes sense because we target a pixel position in screen space. But it isn't?!

I am working on a DoubleAnimation to animate the RenderTransform.X of a ListBox. The way I do it works in a general WPF project, but I can't get it to run in c++. Clearly, my custom converter is the issue because I cannot figure out which type I need to "Noesis::Boxing::Box<>" my return value into. It always returns false with the message: " 'SomeCompany.VisualConcept.MVVM.View.ConvertIndexToRenderTransformX' binding converter failed to convert value '0' (type 'Int32')"

So what type do I need to box my return value into?

As you can see in the following code, I checked for many types, but none of the if conditions are ever true.
class ConvertIndexToRenderTransformX : public Noesis::BaseValueConverter
	{
	public:
		bool TryConvert(Noesis::BaseComponent* value, const Noesis::Type* targetType, Noesis::BaseComponent* parameter,
			Noesis::Ptr<Noesis::BaseComponent>& result) override
		{
			if (targetType == Noesis::TypeOf<bool>()) {
				DEBUG_ERROR("bool");
			}

			if (targetType == Noesis::TypeOf<int8_t>()) {
				DEBUG_ERROR("int8_t");
			}

			if (targetType == Noesis::TypeOf<int16_t>()) {
				DEBUG_ERROR("int16_t");
			}

			if (targetType == Noesis::TypeOf<int32_t>()) {
				DEBUG_ERROR("int32_t");
			}

			if (targetType == Noesis::TypeOf<int64_t>()) {
				DEBUG_ERROR("int64_t");
			}

			if (targetType == Noesis::TypeOf<uint8_t>()) {
				DEBUG_ERROR("uint8_t");
			}

			if (targetType == Noesis::TypeOf<uint16_t>()) {
				DEBUG_ERROR("uint16_t");
			}

			if (targetType == Noesis::TypeOf<uint32_t>()) {
				DEBUG_ERROR("uint32_t");
			}

			if (targetType == Noesis::TypeOf<uint64_t>()) {
				DEBUG_ERROR("uint64_t");
			}

			if (targetType == Noesis::TypeOf<int>()) {
				DEBUG_ERROR("int");
			}

			if (targetType == Noesis::TypeOf<signed int>()) {
				DEBUG_ERROR("signed int");
			}

			if (targetType == Noesis::TypeOf<float>()) {
				DEBUG_ERROR("Float");
			}

			if (targetType == Noesis::TypeOf<double>()) {
				DEBUG_ERROR("double");
			}

			/*if (targetType == Noesis::TypeOf<short>()) {
				DEBUG_ERROR("short");
			}

			if (targetType == Noesis::TypeOf<long>()) {
				DEBUG_ERROR("long");
			}*/

			if (targetType == Noesis::TypeOf<char>()) {
				DEBUG_ERROR("char");
			}

			if (targetType == Noesis::TypeOf<Noesis::String>()) {
				DEBUG_ERROR("Noesis::String");
			}

			if (targetType == Noesis::TypeOf<Noesis::Symbol>()) {
				DEBUG_ERROR("Noesis::Symbol");
			}

			if (targetType == Noesis::TypeOf<Noesis::Point>()) {
				DEBUG_ERROR("Noesis::Point");
			}

			if (targetType == Noesis::TypeOf<Noesis::TransformGroup>()) {
				DEBUG_ERROR("Noesis::TransformGroup");
			}

			if (targetType == Noesis::TypeOf<float>() &&
				Noesis::Boxing::CanUnbox<int>(value)) {
				result = Noesis::Boxing::Box<float>(static_cast<float>(Noesis::Boxing::Unbox<int>(value) * -10.0));
				return true;
			}

			return false;
		}

		bool TryConvertBack(Noesis::BaseComponent* value, const Noesis::Type* targetType, Noesis::BaseComponent* parameter, Noesis::Ptr<Noesis::BaseComponent>& result) override
		{
			ASSERT_UNREACHABLE("ConvertIndexToRenderTransformX::TryConvertBack() Not yet implemented [Manuel]");

			return false;
		}

	private:
		NS_IMPLEMENT_INLINE_REFLECTION_(ConvertIndexToRenderTransformX, Noesis::BaseValueConverter, "SomeCompany.VisualConcept.MVVM.View.ConvertIndexToRenderTransformX")
	};
And for reference, the ListBox I am working on:
<ListBox x:Name="EpisodeList">
    <ListBox.RenderTransform>
        <TranslateTransform X="0" Y="0"/>
    </ListBox.RenderTransform>

    <ListBox.Resources>
        <Storyboard x:Key="MoveAnimation">
            <DoubleAnimation To="{Binding SelectedEpisodeIndex, Converter={StaticResource ConvertIndexToRenderTransformX}}" Storyboard.TargetProperty="RenderTransform.X" Storyboard.TargetName="EpisodeList" Duration="00:00:00.250000" FillBehavior="HoldEnd" IsAdditive="True" IsCumulative="True"/>
            <DoubleAnimation To="{Binding SelectedEpisodeIndex, Converter={StaticResource ConvertIndexToRenderTransformY}}" Storyboard.TargetProperty="RenderTransform.Y" Storyboard.TargetName="EpisodeList" Duration="00:00:00.250000" FillBehavior="HoldEnd" IsAdditive="True" IsCumulative="True"/>
        </Storyboard>
    </ListBox.Resources>
    <behaviors:Interaction.Triggers>
        <behaviors:PropertyChangedTrigger Binding="{Binding SelectedEpisodeIndex}">
            <behaviors:ControlStoryboardAction Storyboard="{StaticResource MoveAnimation}"/>
        </behaviors:PropertyChangedTrigger>
    </behaviors:Interaction.Triggers>
</ListBox>
 
User avatar
sfernandez
Site Admin
Posts: 2727
Joined: 22 Dec 2011, 19:20

Re: CustomConverter: What is the target type of a DoubleAnimation targeting RenderTransform.X?

11 Jan 2023, 16:14

Hi Manuel,

The "To" property of a DoubleAnimation is a nullable (in WPF a Nullable<double>, and in Noesis a Nullable<float>).
class NS_GUI_ANIMATION_API DoubleAnimation: public DoubleAnimationBase
{ ...
    /// Gets or sets the animation's ending value
    //@{
    const Nullable<float>& GetTo() const;
    void SetTo(const Nullable<float>& to);
    //@}
... };
So your converter implementation can return a float or a nullptr result.
bool TryConvert(Noesis::BaseComponent* value, const Noesis::Type* targetType, Noesis::BaseComponent* parameter,
  Noesis::Ptr<Noesis::BaseComponent>& result) override
{
  if (targetType == Noesis::TypeOf<Noesis::Nullable<float>>() && Noesis::Boxing::CanUnbox<int>(value)) {
    result = Noesis::Boxing::Box<float>(Noesis::Boxing::Unbox<int>(value) * -10.0f);
    return true;
  }
  return false;
}
Cheers.
 
User avatar
jsantos
Site Admin
Posts: 3640
Joined: 20 Jan 2012, 17:18
Contact:

Re: CustomConverter: What is the target type of a DoubleAnimation targeting RenderTransform.X?

11 Jan 2023, 17:21

By the way you can get the type (and name) of any BaseObject by doing:
obj->GetClassType()->GetName();
 
manuel
Topic Author
Posts: 13
Joined: 16 Aug 2022, 10:06

Re: CustomConverter: What is the target type of a DoubleAnimation targeting RenderTransform.X?

13 Jan 2023, 10:40

Thanks to both of you. That helped a lot. I totally forget about the Nullables. Though I am a bit confused why I do check for a Nullable<float> but do only return Box<float> and not Box<Nullable<float>>. I checked both and cannot spot a difference, though I do not make use of NULL.

And none of the following returns the string "Box<float>" which isn't very insightful:
targetType->GetClassType()->GetName()		returns "TypeClass"
value->GetClassType()->GetName()		returns "Boxed<Int32>"
result->GetClassType()->GetName()		returns "Boxed<Int32>"
 
User avatar
sfernandez
Site Admin
Posts: 2727
Joined: 22 Dec 2011, 19:20

Re: CustomConverter: What is the target type of a DoubleAnimation targeting RenderTransform.X?

13 Jan 2023, 14:30

Boxing a nullable will return null if it has no value or will box the underlying type:
Ptr<BoxedValue> boxed1 = Boxing::Box(Nullable<float>()); // boxed1 will be nullptr
Ptr<BoxedValue> boxed2 = Boxing::Box(Nullable<float>(123.0f)); // boxed2 will store a simple float
So having a float value it doesn't matter if you box it as Nullable<float> or float, as the result will be the same.

Regarding the type names, for the targetType, as it is already a type, you want to get its name directly:
targetType->GetName() // should return "Nullable<Single>" in this case

Who is online

Users browsing this forum: No registered users and 0 guests