MarcusM-PDX
Topic Author
Posts: 7
Joined: 31 May 2023, 10:24

Custom TypeConverter for Wrapper to String

15 Aug 2024, 16:55

NOTE: A few posts down I show a working example of a TypeConverter, for those of you in the future looking for one. :)

Greetings there, I'm attempting to write a TypeConverter in C++ that will allow me to bind a particular wrapper into Strings. There's a bit of sanity there, I promise.

Below is a sanitized/simplified form of the wrapper. In essence, life is made much easier if we have a singular wrapper which, if given a LocalizationKey stores the localized text generated from that key, but also it has the ability to have text applied that isn't supposed to be localized (ex: a user typed in a name for something). The actual functionality of this wrapper is much more than this, but this is the pertinent aspects of the wrapper for this usecase. I should further clarify, while yes, I CAN just always bind through the Wrapper into OutputText, the design requirements from the rest of my team prohibit this. (In short they want to do {Binding Group.Name} and not {Binding Group.Name.OutputText}.)
class LocalizationWrapper final : public CNotifyPropertyChangedBase
{
	void SetLocalizationKey( SomeKey ) { OutputText = LocalizationMagic(SomeKey); }
	void SetUnlocalizedText( SomeText ) { OutputText = SomeText; }
	
	explicit operator Noesis::String() const { return _OutputText; } // See Note 1.
	
	Noesis::String LocalizationKey;
	Noesis::String OutputText;
}
Note 1: This part works fine inside of my own code, where I may well be trying to do something like "Noesis::String Tester = LoclizationWrapper("TestText");". The problem is that there are various automated bits within Noesis itself that basically says "The left side is type 'Noesis::String' and the right side is type 'LocalizationWrapper', I'm not going to try this without a TypeConverter. Binding failed.".

Below is the simplified example of the TypeConverter inside the header.
class LocalizationWrapperTypeConverter : public Noesis::TypeConverter
{
	bool CanConvertFrom( const Noesis::Type* type ) const override;
	bool CanConvertTo...
	bool TryConvertFrom...
	bool TryConvertFromString...
	bool TryConvertTo...
	bool TryConvertToString...
	
	NS_DECLARE_REFLECTION( Noesis::TypeMetaData<Noesis::TypeConverterMetaData>( "LocalizationWrapperTypeConverter", Noesis::TypeConverter ) // See Note 2.
}
Note 2: This line fails to compile with a message of "Class 'Noesiss::TypeMetaData' may not have a template argument list.'.

When I expand out the macro, the error arises from the following line:
typedef Noesis::TypeMetaData<Noesis::TypeConverterMetaData>( "LocalizationWrapperTypeConverter" ) SelfClass;
The compiler REALLY does not want "SelfClass" to exist there. But if I keep the line and remove that, then it seems happy (if theoretically wrong?). But then, with some further mucking about with the code in the .CPP file once I expand out the NS_IMPLEMENT_REFLECTION() macro, I can get the .h/.cpp file to compile in isolation, but when trying to compile the project as a whole, the complaint that I get is that "StaticFillClassType does not exist in global namespace", at the location in TypeClassCreator.inl where it's trying to execute the templated "void TypeClassCreator::Fill(Type* type)", specifically "ClassT::StaticFillClassType(helper);" line.

So any help that can be provided on this issue would be amazing!

As a further related topic, my understanding is that there's no actual way to declare within LocalizationWrapper that every instance of it ever created has the TypeConverter, but instead the requirement is that in other classes/wrappers that want to use it, the following is done:
void OtherWrapper::AdditionalReflection( Noesis::TypeClassCreator& Helper )
{
	Helper.Prop( "TextVariable", &OtherWrapper::GetTextVariable ).Meta<Noesis::TypeConverterMetaData>( "LocalizationWrapperTypeConverter" );
}
Meaning, that if I have SomeWrapper, ExtraWrapper, MoreWrapper, and they all utilize the LocalizationWrapper, I need to remember to add that ".Meta<...>(...);" bit at the end each time. Is that correct?

Thanks again to any who respond!
Last edited by MarcusM-PDX on 19 Aug 2024, 15:31, edited 2 times in total.
 
User avatar
sfernandez
Site Admin
Posts: 3106
Joined: 22 Dec 2011, 19:20

Re: Custom TypeConverter for Wrapper to String

16 Aug 2024, 18:18

You are not providing the expected params to this macro NS_DECLARE_REFLECTION( Noesis::TypeMetaData... ). You should specify there your type and its base type:
NS_DECLARE_REFLECTION( LocalizationWrapperTypeConverter, Noesis::TypeConverter )
In order to be automatically associated with the LocalizationWrapper type, the converter should be registered in the reflection with the following name:
NS_IMPLEMENT_REFLECTION( LocalizationWrapperTypeConverter, "Converter<LocalizationWrapper>")
Then you have to register your converter in the factory so it can be found and created by name:
Noesis::RegisterComponent<LocalizationWrapperTypeConverter>();
At this point when a binding receives a value of type LocalizationWrapper, it can find your LocalizationWrapperTypeConverter and use it to automatically convert to string (or any type it supports).

But you can also use this converter explicitly in a property by using the TypeConverterMetaData:
NS_IMPLEMENT_REFLECTION(SomeClass, "MyNamespace.SomeClass")
{
  NsProp("SomeProperty", &SomeClass::GetSomeProperty, &SomeClass::SetSomeProperty).
    .Meta<TypeConverterMetaData>("Converter<LocalizationWrapper>");
  ...
}
Please let me know if you have further questions.
 
MarcusM-PDX
Topic Author
Posts: 7
Joined: 31 May 2023, 10:24

Re: Custom TypeConverter for Wrapper to String

19 Aug 2024, 15:30

That did the trick! Thanks!
 
MarcusM-PDX
Topic Author
Posts: 7
Joined: 31 May 2023, 10:24

Re: Custom TypeConverter for Wrapper to String

19 Aug 2024, 15:53

Here's the complete TypeConverter Example.

In this example, I have a wrapper that is enclosing various Strings but is not itself a String, and I want to be able to do a situation where I bind the Wrapper to something like a TextBox.Text field.

Below is the example LocalizationWrapper which needs the TypeConverter. I don't include the various Noesis-macros in this part because nothing interesting about this example is happening here. It's just here to show the approximate idea of the scenario.
class LocalizationWrapper final : CNotifyPropertyChangedBase
{
	void SetLocalizationKey( SomeKey ) { OutputText = LocalizationMagic( SomeKey ); }
	void SetUnlocalizedText( SomeText ) { OutputText = SomeText; }
	
	explicit operator Noesis::String() const { return _OutputText; }
	
	Noesis::String LocalizationKey;
	Noesis::String OutputText;
	
	// Missing Boilerplate Macros (nothing necessary for the TypeConverter's use is missing).
}
This is for the TypeConverter's header file.
class LocalizationTypeConverter : public Noesis::TypeConverter
{
public:
	bool CanConvertFrom( const Noesis::Type* type ) const override;
	bool CanConvertTo( const Noesis::Type* type ) const override;
	bool TryConvertFrom ( BaseComponent* object, Noesis::Ptr<BaseComponent>& result ) const override;
	bool TryConvertFromString( const char* str, Noesis::Ptr<BaseComponent>& result ) const override;
	bool TryConvertTo( BaseComponent* object, const Noesis::Type::* type, Noesis::Ptr<BaseComponent>& result ) const override;
	bool TryConvertToString( BaseComponent* object, Noesis::BaseString& result ) const override;
	
	NS_DECLARE_REFLECTION( LocalizationTypeConverter, Noesis::TypeConverter )
}
For the TypeConverter's CPP file.
NS_IMPLEMENT_REFLECTION( LocalizationTypeConverter, "Converter<LocalizationWrapper>" ) // TAKE NOTE! The <> encloses the Type you want the TypeConverter to apply to!
{
}

bool LocalizationTypeConverter::CanConvertFrom( const Noesis::Type* type ) const
{
	bool SelfCheck = ( type == Noesis::TypeOf<LocalizationWrapper>() );
	bool NoesisStyleString = ( type == Noesis::TypeOf<Noesis::String>() );
	return SelfCheck || NoesisStyleString;
}

bool LocalizationTypeConverter::CanConvertTo( const Noesis::Type* type ) const
{
	bool SelfCheck = ( type == Noesis::TypeOf<LocalizationWrapper>() );
	bool NoesisStyleString = ( type == Noesis::TypeOf<Noesis::String>() );
	return SelfCheck || NoesisStyleString;
}

bool LocalizationTypeConverter::TryConvertFrom( BaseComponent* object, Noesis::Ptr<BaseComponent>& result ) const
{
	if ( object == nullptr )
	{
		// During a new window opening, there may be a frame where Bindings are passing Null instead of actual objects.
		// This might be a problem with my code specifically and is unnecessary for yours, but I figured I'd save you some diagnosing time just in case.
		result = Noesis::Boxing::Box<LocalizationWrapper*>( Noesis::MakePtr<LocalizationWrapper>( "" ) );
		return true;
	}
	else if ( Noesis::Boxing::CanUnbox<Noesis::String>( object ) )
	{
		const Noesis::String UnboxedString = Noesis::Boxing::Unbox<Noesis::String>( object );
		result = Noesis::Boxing::Box<LocalizationWrapper*>( Noesis::MakePtr<LocalizationWrapper>( UnboxedString.Begin() ) );
		return true;
	}
	return false;
}

bool LocalizationTypeConverter::TryConvertFromString( const char* str, Noesis::Ptr<BaseComponent>& result ) const
{
	result = Noesis::MakePtr<LocalizationTypeWrapper>( str );
	return true;
}

bool LocalizationTypeConverter::TryConvertTo( BaseComponent* object, const Noesis::Type* type, Noesis::Ptr<BaseComponent>& result ) const
{
	if ( type == Noesis::TypeOf<Noesis::String>() )
	{
		const LocalizationWrapper* TempLoc = Noesis::DynamicCast<LocalizationWrapper*>( object );
		result = Noesis::Boxing::Box<Noesis::String>( TempLoc->GetLocalizedText().GetCharPtr() );
		return true;
	}
	return false;
}

bool LocalizationTypeConverter::TryConvertToString( BaseComponent* object, Noesis::BaseString& result ) const
{
	result.Assign( object->ToString() );
	return true;
}

As always don't forget to register it!
Noesis::RegisterComponent<LocalizationTypeConverter>();
Noesis::RegisterComponent<LocalizationWrapper>();
The above situation should automatically ensure that whenever the LocalizationWrapper is being bound to something, if necessary, it will use the TypeConverter we've written. But an alternate way to forcefully assign it if somehow it's getting overwritten/intercepted by another Converter is below.
// This part is optional!
NS_IMPLEMENT REFLECTION( SomeClass, "MyNamespace.SomeClass" )
{
	NsProp( "SomeProperty", &SomeClass::GetSomeProperty, &SomeClass::SetSomeProperty ).Meta<TypeConverterMetaData>( "Converter<LocalizationWrapper>" ); // Note that this is the Wrapper type, not the TypeConverter.
}
 
User avatar
jsantos
Site Admin
Posts: 4063
Joined: 20 Jan 2012, 17:18
Contact:

Re: Custom TypeConverter for Wrapper to String

21 Aug 2024, 12:49

Thanks for sharing the example! We should definitely improve the documentation regarding how to register and user converters.

Who is online

Users browsing this forum: No registered users and 2 guests