User avatar
Gazoo
Topic Author
Posts: 30
Joined: 14 Sep 2022, 10:00
Contact:

Dynamically adding a new widget in C++ code, and then using FindName() to get it

01 Oct 2022, 05:50

I had a tricky time finding resources about adding a new UI Element in code, but eventually found this approach:
auto thing = Noesis::MakePtr<Noesis::Rectangle>();
thing->SetName("Rigger");
thing->SetWidth(50);
thing->SetHeight(50);
thing->SetFill(Noesis::Brushes::Red()->Clone());

mXamlGrid->GetChildren()->Add(thing);

auto otherthing = mXamlGrid->FindName("Rigger");
The rectangle appears as expected, and I have 2 related questions:

1. Is this an intended way to add a widget dynamically?
2. Calling FindName() yields a null pointer, which makes me think perhaps the name property I think I'm setting is not the same as 'x:name' I would be setting in an xaml document?

Thanks in advance,
Gazoo
Last edited by Gazoo on 05 Oct 2022, 10:33, edited 1 time in total.
 
User avatar
sfernandez
Site Admin
Posts: 2666
Joined: 22 Dec 2011, 19:20

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

03 Oct 2022, 10:36

Hi,

1) Yes, that code is perfectly valid to dynamically add an element to the UI tree.
2) Setting the name in an element does not register it in the xaml namescope. For that you have to call RegisterName (which is what the xaml parser does when finds an x:Name):
mXamlGrid->RegisterName("Rigger",thing);
Hope this helps.
 
User avatar
Gazoo
Topic Author
Posts: 30
Joined: 14 Sep 2022, 10:00
Contact:

Re: [Solved] Dynamically adding a new widget in C++ code, and then using FindName() to get it

05 Oct 2022, 11:21

That does help! Thank you!

While we're on the subject, short of traversing the traversing the xaml tree myself, is there any other way of getting at elements apart from FindName()?
 
User avatar
maherne
Site Admin
Posts: 13
Joined: 01 Jul 2022, 10:10

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

05 Oct 2022, 14:55

One method I have used in the past to access particular elements in code is to have a boolean attached property which, when set to true on an element, stores that element in a static collection. When changed to false the element is removed from that collection.

The attached property would look something like:
<Button local:ButtonRegistry.Register="True" />
 
User avatar
Gazoo
Topic Author
Posts: 30
Joined: 14 Sep 2022, 10:00
Contact:

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

08 Oct 2022, 08:05

Hey @maherne,

It took me a while to figure out that this behavior you describe is probably not native WPF/xaml functionality. I.e., I'm assuming that you yourself wrote the code that would check for this property and then add it to a collection?
auto button = mXamlGrid->FindName<Noesis::Button>( "glorp" );
button->GetLocalValue( ??? );
I've searched around a bit and I think this is the way to get to the tag property, but GetLocalValue() takes a DependencyProperty, which in turn seems to be crafted via 'DependencyProperty::Create', which in turn takes a template which I couldn't find an example for so far.

If you have any code illustrating how to get at the local value, and even a word or two (or code) about how you build this collection, I'd be all eyes!

Thanks in advance,
Gazoo
 
User avatar
maherne
Site Admin
Posts: 13
Joined: 01 Jul 2022, 10:10

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

10 Oct 2022, 12:41

Yes this is a feature you would need to add to your codebase.

This snippet shows the implementation of an attached dependency property which adds and removes Buttons from a static HashMap based on a boolean value, using the Button's Name as a key.
////////////////////////////////////////////////////////////////////////////////////////////////////
static void OnIsRegisteredChanged(DependencyObject* obj, const DependencyPropertyChangedEventArgs& e)
{
    const Button* button = DynamicCast<Button*>(obj);
    if (button)
    {
        bool isRegistered = e.NewValue<bool>();
        if (isRegistered)
        {
            // gRegisteredButtonMap is a static HashMap<String, Button*>
            gRegisteredButtonMap.Insert(button->GetName(), button);
        }
        else		
        {
            gRegisteredButtonMap.Erase(button->GetName());
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPLEMENT_REFLECTION(ButtonRegistry)
{
    DependencyData* data = NsMeta<DependencyData>(TypeOf<SelfClass>());
    data->RegisterProperty<bool>(IsRegisteredProperty, "IsRegistered",
        PropertyMetadata::Create(false, PropertyChangedCallback(OnIsRegisteredChanged)));
}

////////////////////////////////////////////////////////////////////////////////////////////////////
const DependencyProperty* ButtonRegistry::IsRegisteredProperty;
 
User avatar
Gazoo
Topic Author
Posts: 30
Joined: 14 Sep 2022, 10:00
Contact:

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

16 Oct 2022, 08:14

Very much appreciate the further details maherne. I've attempted to implement the surrounding code as follows (where ButtonRegistry has become ElementRegistry):

.h file:
#include "NoesisPCH.h"

	struct ElementRegistry {

		static Noesis::DependencyProperty const* IsRegisteredProperty;

		NS_DECLARE_REFLECTION( ElementRegistry, Noesis::NoParent )
	};
.cpp file:
#include "ElementRegistry.h"

	Noesis::HashMap<Noesis::String, Noesis::Button*> gRegisteredButtonMap;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	static void OnIsRegisteredChanged( Noesis::DependencyObject* obj, const Noesis::DependencyPropertyChangedEventArgs& e )
	{
		Noesis::Button* button = Noesis::DynamicCast<Noesis::Button*>( obj );
		if (button)
		{
			bool isRegistered = e.NewValue<bool>();
			if (isRegistered)
			{
				// gRegisteredButtonMap is a static HashMap<String, Button*>
				gRegisteredButtonMap.Insert( button->GetName(), button );
			}
			else
			{
				gRegisteredButtonMap.Erase( button->GetName() );
			}
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	NS_IMPLEMENT_REFLECTION( ElementRegistry )
	{
		Noesis::DependencyData* data = NsMeta<Noesis::DependencyData>( Noesis::TypeOf<SelfClass>() );
		data->RegisterProperty<bool>( IsRegisteredProperty, "IsRegistered",
			Noesis::PropertyMetadata::Create( false, Noesis::PropertyChangedCallback( OnIsRegisteredChanged ) ) );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	Noesis::DependencyProperty const* ElementRegistry::IsRegisteredProperty;
Two things I think are missing for the code to work:

1. How to register this behavior with Noesis. In the Noesis sample code base I've only come across complete custom controls with their own DependencyProperties, but not a solitary one as your example makes use of, i.e.:
<Button local:ButtonRegistry.Register="True" />
where the Button, I believe, is just a standard element which were attaching a custom Dependency property to. I think the piece I'm missing is the class to have the ElementRegistry inherit from. I thought perhaps no parent was acceptable, but Noesis::RegisterComponent() will not accept this.

2. I'm also not sure how to define this Dependency property inside the XAML. Like this, perhaps?
xmlns:other="clr-namespace:ElementRegistry"
Intellisense doesn't seem to love it though.

Thanks in advance,
Gazoo
 
User avatar
maherne
Site Admin
Posts: 13
Joined: 01 Jul 2022, 10:10

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

17 Oct 2022, 12:16

1. How to register this behavior with Noesis. In the Noesis sample code base I've only come across complete custom controls with their own DependencyProperties, but not a solitary one as your example makes use of, i.e.:
<Button local:ButtonRegistry.Register="True" />
Dependency properties can be defined in any class, even those with no parent. Any DependencyProperty can also be attached to any DependencyObject derived type, though most are not designed for this use. Those that are designed to be used with other derived types are called "attached properties".

You can register your non-component class with our reflection system by calling Noesis::TypeOf<ButtonRegistry>() before it's first use (in the same place you would call Noesis::RegisterComponent()).

2. I'm also not sure how to define this Dependency property inside the XAML. Like this, perhaps?
xmlns:other="clr-namespace:ElementRegistry"
This namespace definition is valid for Noesis, however WPF/Blend requires that you specify an assembly if the namespace is not in the application assembly, e.g.
xmlns:other="clr-namespace:ElementRegistry;assembly=ElementRegistryAssemblyName"
 
User avatar
Gazoo
Topic Author
Posts: 30
Joined: 14 Sep 2022, 10:00
Contact:

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

18 Oct 2022, 11:49

Thank you for the continued help maherne.

With the addition of
Noesis::TypeOf<ElementRegistry>();
I can see via the debugger that the reflection macro code is executed as intended:
	NS_IMPLEMENT_REFLECTION( ElementRegistry )
	{
		Noesis::DependencyData* data = NsMeta<Noesis::DependencyData>( Noesis::TypeOf<SelfClass>() );
		data->RegisterProperty<bool>( IsRegisteredProperty, "IsRegistered",
			Noesis::PropertyMetadata::Create( false, Noesis::PropertyChangedCallback( OnIsRegisteredChanged ) ) );
	}
I'm still having 2 issues I'd like to further inquire about:

1. The OnIsRegisteredChanged() is never executed. I wonder if the issue is that the string in the above code is "IsRegistered", but the earlier provided xaml sample code uses the following property "other:ElementRegistry.Register="True"". I tried changing this to "other:ElementRegistry.IsRegistered="True"" with no luck.

Checking the log output I see the following when parsing my xaml:
|error  | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] DockingOverlayTab.xaml(12): Unknown member Run.Language
|info   | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[34] 'DockingOverlayTab.xaml' loaded
|info   | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[34] 'DockingOverlayTabV2.xaml' loaded
|error  | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] <memory>(35): Unknown type 'ElementRegistry.ElementRegistry'.
|error  | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] <memory>(35): Unknown type 'ElementRegistry.ElementRegistry'.
I'm pretty clueless as to why the type isn't being recognized...?

2. After adding the 'assembly' property, Visual Studio provides me with this error:
Error	XDG0008	The name "ElementRegistry" does not exist in the namespace "clr-namespace:ElementRegistry;assembly=ElementRegistryAssemblyName".	noesis_sandbox_gui	C:\Code\cinder_noesis_sandbox\resources\GuiLayout\TestingAnimation.xaml	35	
and refuses to render the designer-xaml. I'm assuming the issue is that the connection between the C++ code and the designer read xaml isn't there. Is this one of those cases where I have to create an empty dummy .cs file to satisfy the designer...?

Thanks in advance,
Gazoo
 
User avatar
maherne
Site Admin
Posts: 13
Joined: 01 Jul 2022, 10:10

Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it

19 Oct 2022, 12:14

1. The OnIsRegisteredChanged() is never executed. I wonder if the issue is that the string in the above code is "IsRegistered", but the earlier provided xaml sample code uses the following property "other:ElementRegistry.Register="True"". I tried changing this to "other:ElementRegistry.IsRegistered="True"" with no luck.

Checking the log output I see the following when parsing my xaml:
|error | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] DockingOverlayTab.xaml(12): Unknown member Run.Language
|info | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[34] 'DockingOverlayTab.xaml' loaded
|info | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[34] 'DockingOverlayTabV2.xaml' loaded
|error | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] <memory>(35): Unknown type 'ElementRegistry.ElementRegistry'.
|error | 2022-10-18.20:45:42 PMP::`anonymous-namespace'::LogNoesis[40] <memory>(35): Unknown type 'ElementRegistry.ElementRegistry'.
I'm pretty clueless as to why the type isn't being recognized...?
I did change the DependencyProperty name in my code example (without realising), the correct markup is ElementRegistry.IsRegistered.

As to why the type is not recognised, the NS_IMPLEMENT_REFLECTION( ElementRegistry ) macro places that type in the application namespace, no custom namespace is required here e.g.
<Button ElementRegistry.IsRegistered="True" />
2. After adding the 'assembly' property, Visual Studio provides me with this error:
Error	XDG0008	The name "ElementRegistry" does not exist in the namespace "clr-namespace:ElementRegistry;assembly=ElementRegistryAssemblyName".	noesis_sandbox_gui	C:\Code\cinder_noesis_sandbox\resources\GuiLayout\TestingAnimation.xaml	35	
and refuses to render the designer-xaml. I'm assuming the issue is that the connection between the C++ code and the designer read xaml isn't there. Is this one of those cases where I have to create an empty dummy .cs file to satisfy the designer...?
That is correct, you need to create a dummy .cs file containing the same class name and DependecyProperty, in order to use the designer. We are working on our own editor, which will provide a better experience and remove the need for dummy classes.

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot], Semrush [Bot] and 2 guests