Page 1 of 1

Implementing Custom Markup Extension in C++

Posted: 14 Aug 2017, 07:33
by nikobarli
Hi, sorry, I am stucked again when trying to translate the following WPF Markup extension code:

#original code is here: http://www.mobilemotion.eu/?p=1837
MarkupExtension.PNG
XAML
    <Grid Background="White" MouseDown="{local:EventBinding MouseDownCommand}">
    </Grid>
I tried to create an EventBindingExtension class inheriting from BaseComponent and IMarkupExtension as follows (still empty).
    class EventBindingExtension : public BaseComponent, public IMarkupExtension
    {
    public:
        EventBindingExtension() {
        }
        EventBindingExtension(const NsChar * command) {
        }

        virtual Ptr<Core::BaseComponent> ProvideValue(const void* context) override {
            return nullptr;
        }

        NS_IMPLEMENT_INTERFACE_FIXUP

        NS_IMPLEMENT_INLINE_REFLECTION(EventBindingExtension, BaseComponent)
        {
            NsMeta<TypeId>("EventBinding");
        }
    };
When I ran the code (just to make sure NoesisGUI can find EventBindingExtension class and instatiate it), NoesisGUI complained with the following error:
Gui\Core\Src\XamlNode.cpp:573:Can't convert 'MouseDownCommand' into a 'EventBinding' object (@10,4)
Debugging showed that NoesisGUI instantiated EventBindingExtension using the default constructor, while I am expecting it used the second constructor. Do I have to register the second constructor ? How should I do that ? I have tried to comb through the reflection related headers to find APIs to register constructor but couldn't find ones. And documentation is still showing old (pre 2.0) APis (NsCtr).

If possible, could you show me the full C++ version of the EventBindingExtension ?

Thanks.

Re: Implementing Custom Markup Extension in C++

Posted: 16 Aug 2017, 16:48
by sfernandez
Our architecture only permits creating objects specified in a XAML using a default constructor (it is how our ComponentFactory works).
What you want to do is solved by exposing the command as a property of the markup extension and specify that property as the content property of that type (using the ContentPropertyMetaData in the reflection).

You also missed to specify the interface implementation in the reflection block by using NsImpl<T> macro.

Another thing to take into account is that we don't support directyly assigning delegates in xaml to events, but you can do the connection inside ProvideValue (similar to the code I suggested in viewtopic.php?f=3&t=1147&sid=bc63861558 ... 7208ee4fd7).

Find the corresponding code here:
struct EventBindingExtension: public BaseComponent, public IMarkupExtension
{
    EventBindingExtension() { }

    const NsChar* GetCommandName() const { return commandName.c_str(); }
    void SetCommandName(const NsChar* name) { commandName = name; }

    Ptr<BaseComponent> ProvideValue(const void* context) override
    {
        auto provider = (const XamlReaderProvider*)context;
        auto element = NsDynamicCast<UIElement*>(provider->GetTargetObject());
        if (element != 0)
        {
            auto eventId = provider->GetPropertyName();
            auto type = element->GetClassType();

            // look in UIElementData for the routed event
            auto event = FindRoutedEvent(type, eventId);
            if (event != 0)
            {
                // TODO: connect event with a delegate that could resolve
                // commandName from element DataContext                 
                //element->AddHandler(event, ...);
            }
        }

        return nullptr;
    }

    NS_IMPLEMENT_INTERFACE_FIXUP

private:
    // ...
    
    NsString commandName;

    NS_IMPLEMENT_INLINE_REFLECTION(EventBindingExtension, BaseComponent)
    {
        NsMeta<TypeId>("EventBinding");
        NsMeta<ContentPropertyMetaData>("CommandName");

        NsImpl<IMarkupExtension>();

        NsProp("CommandName", &EventBindingExtension::GetCommandName,
            &EventBindingExtension::SetCommandName);
    }
};

Re: Implementing Custom Markup Extension in C++

Posted: 21 Aug 2017, 08:02
by nikobarli
Thanks for the code.

If ProvideValue returns nullptr, Noesis complains that the object to assign is incompatible. Is there any way to avoid this error message ?

Re: Implementing Custom Markup Extension in C++

Posted: 21 Aug 2017, 13:27
by sfernandez
The problem is in our xaml parser, that is considering the event as another property and is trying to set the value. You can ignore that error right now.
Anyway, could you please report it in our bugtracker, we will solve it as soon as possible.

Re: Implementing Custom Markup Extension in C++

Posted: 22 Aug 2017, 08:42
by nikobarli
Ok, I submitted a bug here: #1136

Re: Implementing Custom Markup Extension in C++

Posted: 22 Aug 2017, 09:04
by nikobarli
Sorry, an additional question on the details.

I implemented a routine to find the command object inside the data context as follows:
# I intentionaly stripped out null checking to shorten the code
FindCommand.PNG
Is it already correct ? Is there any helper API that I can use to shorten the code (especially the one that iterate through type's hierarchy)

Thanks.

Re: Implementing Custom Markup Extension in C++

Posted: 23 Aug 2017, 21:00
by sfernandez
Yes, your code seems to be correct. To simplify it you can use Reflection::FindProperty, it returns the TypeProperty and TypeClass where the property was found.

But maybe you can define an interface that your viewmodels can use to optimize that:
NS_INTERFACE ICommandFinder: public Noesis::Interface
{
  virtual ICommand* FindCommand(const NsChar* commandName) const = 0;
  
  NS_IMPLEMENT_INLINE_REFLECTION_(ICommandFinder, Noesis::Interface)
};
And then you can use it like this (null checks removed for clarity):
static ICommand* GetCommand(const FrameworkElement* element, const NsChar* commandName) {
  auto context = element->GetDataContext();
  return NsDynamicCast<ICommandFinder*>(context)->FindCommand(commandName);
}

Re: Implementing Custom Markup Extension in C++

Posted: 24 Aug 2017, 03:39
by nikobarli
Great. Thanks for the tips !