Dynamically adding a new widget in C++ code, and then using FindName() to get it
I had a tricky time finding resources about adding a new UI Element in code, but eventually found this approach:
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
Code: Select all
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");
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.
-
-
sfernandez
Site Admin
- Posts: 2727
- Joined:
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
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):
Hope this helps.
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):
Code: Select all
mXamlGrid->RegisterName("Rigger",thing);
Re: [Solved] Dynamically adding a new widget in C++ code, and then using FindName() to get it
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()?
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()?
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
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:
The attached property would look something like:
Code: Select all
<Button local:ButtonRegistry.Register="True" />
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
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?
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
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?
Code: Select all
auto button = mXamlGrid->FindName<Noesis::Button>( "glorp" );
button->GetLocalValue( ??? );
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
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
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.
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.
Code: Select all
////////////////////////////////////////////////////////////////////////////////////////////////////
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;
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
Very much appreciate the further details maherne. I've attempted to implement the surrounding code as follows (where ButtonRegistry has become ElementRegistry):
.h file:
.cpp file:
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.:
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?
Intellisense doesn't seem to love it though.
Thanks in advance,
Gazoo
.h file:
Code: Select all
#include "NoesisPCH.h"
struct ElementRegistry {
static Noesis::DependencyProperty const* IsRegisteredProperty;
NS_DECLARE_REFLECTION( ElementRegistry, Noesis::NoParent )
};
Code: Select all
#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;
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.:
Code: Select all
<Button local:ButtonRegistry.Register="True" />
2. I'm also not sure how to define this Dependency property inside the XAML. Like this, perhaps?
Code: Select all
xmlns:other="clr-namespace:ElementRegistry"
Thanks in advance,
Gazoo
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
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".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.:Code: Select all<Button local:ButtonRegistry.Register="True" />
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()).
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.2. I'm also not sure how to define this Dependency property inside the XAML. Like this, perhaps?
Code: Select allxmlns:other="clr-namespace:ElementRegistry"
Code: Select all
xmlns:other="clr-namespace:ElementRegistry;assembly=ElementRegistryAssemblyName"
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
Thank you for the continued help maherne.
With the addition of
I can see via the debugger that the reflection macro code is executed as intended:
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:
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:
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
With the addition of
Code: Select all
Noesis::TypeOf<ElementRegistry>();
Code: Select all
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 ) ) );
}
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:
Code: Select all
|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'.
2. After adding the 'assembly' property, Visual Studio provides me with this error:
Code: Select all
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
Thanks in advance,
Gazoo
Re: Dynamically adding a new widget in C++ code, and then using FindName() to get it
I did change the DependencyProperty name in my code example (without realising), the correct markup is ElementRegistry.IsRegistered.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:
I'm pretty clueless as to why the type isn't being recognized...?Code: Select all|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'.
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.
Code: Select all
<Button ElementRegistry.IsRegistered="True" />
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.2. After adding the 'assembly' property, Visual Studio provides me with this error:
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...?Code: Select allError 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
Who is online
Users browsing this forum: Ahrefs [Bot] and 1 guest